diff options
-rw-r--r-- | po/POTFILES.in | 2 | ||||
-rw-r--r-- | po/it.po | 102 | ||||
-rw-r--r-- | service/activityregistryservice.py | 12 | ||||
-rw-r--r-- | service/bundleregistry.py | 106 | ||||
-rw-r--r-- | src/view/home/FriendsBox.py | 26 | ||||
-rw-r--r-- | src/view/home/HomeBox.py | 130 | ||||
-rw-r--r-- | src/view/home/HomeWindow.py | 31 | ||||
-rw-r--r-- | src/view/home/Makefile.am | 4 | ||||
-rw-r--r-- | src/view/home/MeshBox.py | 45 | ||||
-rw-r--r-- | src/view/home/activitieslist.py | 22 | ||||
-rw-r--r-- | src/view/home/favoriteslayout.py | 273 | ||||
-rw-r--r-- | src/view/home/favoritesview.py (renamed from src/view/home/activitiesring.py) | 298 | ||||
-rw-r--r-- | src/view/home/grid.py | 223 | ||||
-rw-r--r-- | src/view/home/launchbox.py | 8 | ||||
-rw-r--r-- | src/view/home/spreadlayout.py | 197 | ||||
-rw-r--r-- | src/view/home/transitionbox.py | 13 | ||||
-rw-r--r-- | src/view/keyhandler.py | 23 | ||||
-rw-r--r-- | src/view/palettes.py | 4 |
18 files changed, 1028 insertions, 491 deletions
diff --git a/po/POTFILES.in b/po/POTFILES.in index 322e691..ce883b2 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -28,6 +28,6 @@ src/controlpanel/view/language.py src/controlpanel/view/network.py src/view/devices/network/mesh.py src/view/frame/activitiestray.py -src/view/home/activitiesring.py +src/view/home/favoritesview.py src/view/palettes.py @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-06-05 00:30-0400\n" -"PO-Revision-Date: 2008-06-06 11:16-0400\n" +"POT-Creation-Date: 2008-06-14 00:30-0400\n" +"PO-Revision-Date: 2008-06-18 04:33-0400\n" "Last-Translator: Carlo Falciola <cfalciola@yahoo.it>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" @@ -65,9 +65,8 @@ msgstr "Apri" #. self._stop_item.connect('activate', self._stop_item_activate_cb) #. self.append_menu_item(self._stop_item) #: ../src/view/clipboardmenu.py:63 -#, fuzzy msgid "Keep" -msgstr "Conserva" +msgstr "Memorizza" #: ../src/view/clipboardmenu.py:84 msgid "Open with" @@ -90,24 +89,24 @@ msgstr "Tipo di Autenticazione:" msgid "Encryption Type:" msgstr "Tipo di Crittografia:" -#: ../src/view/Shell.py:259 +#: ../src/view/Shell.py:262 msgid "Screenshot" msgstr "Schermata" # Ruota, piuttosto che anello per mantenere consistenza con lo shortcut da tastiera... cf 27_05_08 -#: ../src/view/home/HomeBox.py:149 +#: ../src/view/home/HomeBox.py:148 msgid "Ring view" msgstr "Vista Ruota" -#: ../src/view/home/HomeBox.py:150 +#: ../src/view/home/HomeBox.py:149 msgid "<Ctrl>R" msgstr "<Ctrl>R" -#: ../src/view/home/HomeBox.py:158 +#: ../src/view/home/HomeBox.py:157 msgid "List view" msgstr "Vista Lista" -#: ../src/view/home/HomeBox.py:159 +#: ../src/view/home/HomeBox.py:158 msgid "<Ctrl>L" msgstr "<Ctrl>L" @@ -157,7 +156,7 @@ msgstr "Disconnessione..." msgid "Resume" msgstr "Riprendi" -#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:162 +#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219 #, fuzzy msgid "Join" msgstr "Associa" @@ -276,21 +275,15 @@ msgstr "Cancella" msgid "Ok" msgstr "Ok" -#: ../src/controlpanel/sectionview.py:34 -msgid "Changes require a sugar restart to take effect." -msgstr "" -"Le modifiche effettuate necessitano di un riavvio di Sugar per avere " -"effetto." +#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250 +msgid "Changes require restart" +msgstr "Le modifiche rendono necessario un riavvio" -#: ../src/controlpanel/gui.py:248 +#: ../src/controlpanel/gui.py:249 msgid "Warning" msgstr "Attenzione" -#: ../src/controlpanel/gui.py:249 -msgid "Changes require restart to take effect" -msgstr "Le modifiche effettuate necessitano di un riavvio per avere effetto" - -#: ../src/controlpanel/gui.py:252 +#: ../src/controlpanel/gui.py:253 msgid "Cancel changes" msgstr "Annulla modifiche" @@ -298,6 +291,10 @@ msgstr "Annulla modifiche" msgid "Later" msgstr "Dopo" +#: ../src/controlpanel/gui.py:261 +msgid "Restart now" +msgstr "Riavvia adesso" + #: ../src/controlpanel/model/aboutme.py:44 msgid "You must enter a name." msgstr "Devi inserire un nome." @@ -339,7 +336,8 @@ msgid "Error timezone does not exist." msgstr "Errore, timezone non esistente." #: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60 -msgid "Value must be an int." +#, fuzzy +msgid "Value must be an integer." msgstr "Valore deve essere un intero." #: ../src/controlpanel/model/language.py:28 @@ -424,17 +422,22 @@ msgid "instantaneous" msgstr "istantaneamente" #: ../src/controlpanel/view/frame.py:32 -msgid "Delay in milliseconds:" -msgstr "Ritardo in millisecondi:" +#, python-format +#, fuzzy +msgid "%s seconds" +msgstr "%d secondi" -#: ../src/controlpanel/view/frame.py:66 -msgid "Hot Corners" -msgstr "Angoli sensibili" +#: ../src/controlpanel/view/frame.py:56 +msgid "Activation Delay" +msgstr "Ritardo attivazione" -#: ../src/controlpanel/view/frame.py:117 -#, fuzzy -msgid "Warm Edges" -msgstr "Bordi Attivi" +#: ../src/controlpanel/view/frame.py:80 +msgid "Corner" +msgstr "Angolo" + +#: ../src/controlpanel/view/frame.py:115 +msgid "Edge" +msgstr "Margine" #: ../src/controlpanel/view/language.py:29 #: ../src/controlpanel/view/language.py:74 @@ -466,29 +469,24 @@ msgid "Connected to a School Mesh Portal" msgstr "Connesso ad un Portale Mesh di scuola" #: ../src/view/devices/network/mesh.py:110 -#, fuzzy msgid "Looking for a School Mesh Portal..." msgstr "Ricerca di un Portale Mesh di scuola..." #: ../src/view/devices/network/mesh.py:113 -#, fuzzy msgid "Connected to an XO Mesh Portal" msgstr "Connesso ad un Portale Mesh XO" #: ../src/view/devices/network/mesh.py:115 -#, fuzzy msgid "Looking for an XO Mesh Portal..." msgstr "Sto cercando un Portale Mesh XO..." # Diretto? #: ../src/view/devices/network/mesh.py:118 -#, fuzzy msgid "Connected to a Simple Mesh" msgstr "Connesso ad un Mesh Semplice" # Diretto? #: ../src/view/devices/network/mesh.py:120 -#, fuzzy msgid "Starting a Simple Mesh" msgstr "Attivazione Mesh Semplice" @@ -496,23 +494,23 @@ msgstr "Attivazione Mesh Semplice" msgid "Unknown Mesh" msgstr "Mesh sconosciuto" -#: ../src/view/frame/activitiestray.py:167 +#: ../src/view/frame/activitiestray.py:224 msgid "Decline" msgstr "Rinuncia" -#: ../src/view/home/activitiesring.py:310 +#: ../src/view/home/activitiesring.py:305 msgid "Control Panel" msgstr "Pannello di Controllo" -#: ../src/view/home/activitiesring.py:321 +#: ../src/view/home/activitiesring.py:316 msgid "Restart" msgstr "Riavvia" -#: ../src/view/home/activitiesring.py:326 +#: ../src/view/home/activitiesring.py:321 msgid "Shutdown" msgstr "Spegni" -#: ../src/view/home/activitiesring.py:332 +#: ../src/view/home/activitiesring.py:327 msgid "Register" msgstr "Registra" @@ -545,6 +543,24 @@ msgstr "Mostra i contenuti" msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB Liberi" +#~ msgid "Changes require a sugar restart to take effect." +#~ msgstr "" +#~ "Le modifiche effettuate necessitano di un riavvio di Sugar per avere " +#~ "effetto." + +#~ msgid "Changes require restart to take effect" +#~ msgstr "Le modifiche effettuate necessitano di un riavvio per avere effetto" + +#~ msgid "Delay in milliseconds:" +#~ msgstr "Ritardo in millisecondi:" + +#~ msgid "Hot Corners" +#~ msgstr "Angoli sensibili" + +#, fuzzy +#~ msgid "Warm Edges" +#~ msgstr "Bordi Attivi" + #~ msgid "off" #~ msgstr "spento" @@ -745,10 +761,6 @@ msgstr "%(free_space)d MB Liberi" #~ msgid "%d second" #~ msgstr "%d secondo" -#, python-format -#~ msgid "%d seconds" -#~ msgstr "%d secondi" - #~ msgid " and " #~ msgstr " e " diff --git a/service/activityregistryservice.py b/service/activityregistryservice.py index bf98ef9..6ba5598 100644 --- a/service/activityregistryservice.py +++ b/service/activityregistryservice.py @@ -109,6 +109,12 @@ class ActivityRegistry(dbus.service.Object): registry = bundleregistry.get_registry() registry.set_bundle_favorite(bundle_id, version, favorite) + @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, + in_signature='sidd', out_signature='') + def SetActivityPosition(self, bundle_id, version, x, y): + registry = bundleregistry.get_registry() + registry.set_bundle_position(bundle_id, version, float(x), float(y)) + @dbus.service.signal(_ACTIVITY_REGISTRY_IFACE, signature='a{sv}') def ActivityAdded(self, activity_info): pass @@ -125,6 +131,8 @@ class ActivityRegistry(dbus.service.Object): registry = bundleregistry.get_registry() favorite = registry.is_bundle_favorite(bundle.get_bundle_id(), bundle.get_activity_version()) + x, y = registry.get_bundle_position(bundle.get_bundle_id(), + bundle.get_activity_version()) return {'name': bundle.get_name(), 'icon': bundle.get_icon(), 'bundle_id': bundle.get_bundle_id(), @@ -133,7 +141,9 @@ class ActivityRegistry(dbus.service.Object): 'command': bundle.get_command(), 'show_launcher': bundle.get_show_launcher(), 'favorite': favorite, - 'installation_time': bundle.get_installation_time()} + 'installation_time': bundle.get_installation_time(), + 'position_x': x, + 'position_y': y} def _bundle_added_cb(self, bundle_registry, bundle): self.ActivityAdded(self._bundle_to_dict(bundle)) diff --git a/service/bundleregistry.py b/service/bundleregistry.py index 8b7f09b..e7c30a8 100644 --- a/service/bundleregistry.py +++ b/service/bundleregistry.py @@ -16,6 +16,7 @@ import os import logging +import traceback import gobject import simplejson @@ -48,7 +49,14 @@ class BundleRegistry(gobject.GObject): self._scan_directory(activity_dir) self._last_defaults_mtime = -1 - self._favorite_bundles = self._load_favorites() + self._favorite_bundles = {} + + try: + self._load_favorites() + except Exception, e: + logging.error('Error while loading favorite_activities\n%s.' \ + % traceback.format_exc()) + self._merge_default_favorites() def _get_activity_directories(self): @@ -74,24 +82,34 @@ class BundleRegistry(gobject.GObject): return defaults + def _get_favorite_key(self, bundle_id, version): + """We use a string as a composite key for the favorites dictionary + because JSON doesn't support tuples and python won't accept a list + as a dictionary key. + """ + if ' ' in bundle_id: + raise ValueError('bundle_id cannot contain spaces') + return '%s %s' % (bundle_id, version) + def _load_favorites(self): - favorite_bundles = [] favorites_path = env.get_profile_path('favorite_activities') if os.path.exists(favorites_path): - try: - favorites_data = simplejson.load(open(favorites_path)) - except ValueError, e: - logging.error('Error while loading favorite_activities: %r.' % - e) - else: - # Old structure used to be a list, instead of a dictionary. - if isinstance(favorites_data, list): - favorite_bundles = favorites_data - else: - favorite_bundles = favorites_data['favorites'] - self._last_defaults_mtime = favorites_data['defaults-mtime'] + favorites_data = simplejson.load(open(favorites_path)) + + favorite_bundles = favorites_data['favorites'] + if not isinstance(favorite_bundles, dict): + raise ValueError('Invalid format in %s.' % favorites_path) + if favorite_bundles: + first_key = favorite_bundles.keys()[0] + if not isinstance(first_key, basestring): + raise ValueError('Invalid format in %s.' % favorites_path) - return favorite_bundles + first_value = favorite_bundles.values()[0] + if first_value is not None and not isinstance(first_value, dict): + raise ValueError('Invalid format in %s.' % favorites_path) + + self._last_defaults_mtime = float(favorites_data['defaults-mtime']) + self._favorite_bundles = favorite_bundles def _merge_default_favorites(self): default_activities = [] @@ -117,9 +135,9 @@ class BundleRegistry(gobject.GObject): max_version < bundle.get_activity_version(): max_version = bundle.get_activity_version() - if max_version > -1 and \ - [bundle_id, max_version] not in self._favorite_bundles: - self._favorite_bundles.append([bundle_id, max_version]) + key = self._get_favorite_key(bundle_id, max_version) + if max_version > -1 and key not in self._favorite_bundles: + self._favorite_bundles[key] = None logging.debug('After merging: %r' % self._favorite_bundles) @@ -205,24 +223,56 @@ class BundleRegistry(gobject.GObject): (bundle_id, version)) def set_bundle_favorite(self, bundle_id, version, favorite): + key = self._get_favorite_key(bundle_id, version) + if favorite and not key in self._favorite_bundles: + self._favorite_bundles[key] = None + elif not favorite and key in self._favorite_bundles: + del self._favorite_bundles[key] + else: + return + + self._write_favorites_file() bundle = self._find_bundle(bundle_id, version) - if favorite and not [bundle_id, version] in self._favorite_bundles: - self._favorite_bundles.append([bundle_id, version]) - self.emit('bundle-changed', bundle) - self._write_favorites_file() - elif not favorite and [bundle_id, version] in self._favorite_bundles: - self._favorite_bundles.remove([bundle_id, version]) - self.emit('bundle-changed', bundle) - self._write_favorites_file() + self.emit('bundle-changed', bundle) def is_bundle_favorite(self, bundle_id, version): - return [bundle_id, version] in self._favorite_bundles + key = self._get_favorite_key(bundle_id, version) + return key in self._favorite_bundles + + def set_bundle_position(self, bundle_id, version, x, y): + key = self._get_favorite_key(bundle_id, version) + if key not in self._favorite_bundles: + raise ValueError('Bundle %s %s not favorite' % (bundle_id, version)) + + if self._favorite_bundles[key] is None: + self._favorite_bundles[key] = {} + if 'position' not in self._favorite_bundles[key] or \ + [x, y] != self._favorite_bundles[key]['position']: + self._favorite_bundles[key]['position'] = [x, y] + else: + return + + self._write_favorites_file() + bundle = self._find_bundle(bundle_id, version) + self.emit('bundle-changed', bundle) + + def get_bundle_position(self, bundle_id, version): + """Get the coordinates where the user wants the representation of this + bundle to be displayed. Coordinates are relative to a 1000x1000 area. + """ + key = self._get_favorite_key(bundle_id, version) + if key not in self._favorite_bundles or \ + self._favorite_bundles[key] is None or \ + 'position' not in self._favorite_bundles[key]: + return (-1, -1) + else: + return tuple(self._favorite_bundles[key]['position']) def _write_favorites_file(self): path = env.get_profile_path('favorite_activities') favorites_data = {'defaults-mtime': self._last_defaults_mtime, 'favorites': self._favorite_bundles} - simplejson.dump(favorites_data, open(path, 'w')) + simplejson.dump(favorites_data, open(path, 'w'), indent=1) _instance = None diff --git a/src/view/home/FriendsBox.py b/src/view/home/FriendsBox.py index 7c6648a..c45c1c6 100644 --- a/src/view/home/FriendsBox.py +++ b/src/view/home/FriendsBox.py @@ -14,6 +14,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import gobject import gtk import hippo @@ -26,16 +27,19 @@ from model import shellmodel from view.home.FriendView import FriendView from view.home.spreadlayout import SpreadLayout -class FriendsBox(hippo.CanvasBox): +class FriendsBox(hippo.Canvas): __gtype_name__ = 'SugarFriendsBox' def __init__(self): - hippo.CanvasBox.__init__(self, - background_color=style.COLOR_WHITE.get_int()) + gobject.GObject.__init__(self) + + self._box = hippo.CanvasBox() + self._box.props.background_color = style.COLOR_WHITE.get_int() + self.set_root(self._box) self._friends = {} self._layout = SpreadLayout() - self.set_layout(self._layout) + self._box.set_layout(self._layout) self._owner_icon = CanvasIcon(icon_name='computer-xo', cache=True, xo_color=profile.get_color()) @@ -46,7 +50,7 @@ class FriendsBox(hippo.CanvasBox): palette = Palette(None, primary_text=profile.get_nick_name(), icon=palette_icon) self._owner_icon.set_palette(palette) - self._layout.add_center(self._owner_icon) + self._layout.add(self._owner_icon) friends = shellmodel.get_instance().get_friends() @@ -71,3 +75,15 @@ class FriendsBox(hippo.CanvasBox): del self._friends[key] icon.destroy() + def do_size_allocate(self, allocation): + width = allocation.width + height = allocation.height + + min_w_, icon_width = self._owner_icon.get_width_request() + min_h_, icon_height = self._owner_icon.get_height_request(icon_width) + x = (width - icon_width) / 2 + y = (height - icon_height) / 2 + self._layout.move(self._owner_icon, x, y) + + hippo.Canvas.do_size_allocate(self, allocation) + diff --git a/src/view/home/HomeBox.py b/src/view/home/HomeBox.py index fa22a11..61ceee9 100644 --- a/src/view/home/HomeBox.py +++ b/src/view/home/HomeBox.py @@ -18,36 +18,38 @@ from gettext import gettext as _ import gobject import gtk -import hippo from sugar.graphics import style from sugar.graphics import iconentry +from sugar.graphics.palette import Palette +from sugar.graphics.menuitem import MenuItem from sugar.graphics.radiotoolbutton import RadioToolButton -from view.home.activitiesring import ActivitiesRing +from view.home import favoritesview from view.home.activitieslist import ActivitiesList -_RING_VIEW = 0 +_FAVORITES_VIEW = 0 _LIST_VIEW = 1 _AUTOSEARCH_TIMEOUT = 1000 -class HomeBox(hippo.CanvasBox, hippo.CanvasItem): +class HomeBox(gtk.VBox): __gtype_name__ = 'SugarHomeBox' def __init__(self): - hippo.CanvasBox.__init__(self) + gobject.GObject.__init__(self) - self._ring_view = ActivitiesRing() + self._favorites_view = favoritesview.FavoritesView() self._list_view = ActivitiesList() self._enable_xo_palette = False self._toolbar = HomeToolbar() self._toolbar.connect('query-changed', self.__toolbar_query_changed_cb) self._toolbar.connect('view-changed', self.__toolbar_view_changed_cb) - self.append(hippo.CanvasWidget(widget=self._toolbar)) + self.pack_start(self._toolbar, expand=False) + self._toolbar.show() - self._set_view(_RING_VIEW) + self._set_view(_FAVORITES_VIEW, favoritesview.RANDOM_LAYOUT) def __toolbar_query_changed_cb(self, toolbar, query): if self._list_view is None: @@ -55,49 +57,39 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem): query = query.lower() self._list_view.set_filter(query) - def __toolbar_view_changed_cb(self, toolbar, view): - self._set_view(view) + def __toolbar_view_changed_cb(self, toolbar, view, layout): + self._set_view(view, layout) - def _set_view(self, view): - if view == _RING_VIEW: + def _set_view(self, view, layout): + if view == _FAVORITES_VIEW: if self._list_view in self.get_children(): self.remove(self._list_view) - if self._enable_xo_palette: - self._ring_view.enable_xo_palette() + self._favorites_view.layout = layout - self.append(self._ring_view, hippo.PACK_EXPAND) + if self._enable_xo_palette: + self._favorites_view.enable_xo_palette() + if self._favorites_view not in self.get_children(): + self.add(self._favorites_view) + self._favorites_view.show() elif view == _LIST_VIEW: - if self._ring_view in self.get_children(): - self.remove(self._ring_view) + if self._favorites_view in self.get_children(): + self.remove(self._favorites_view) - self.append(self._list_view, hippo.PACK_EXPAND) + self.add(self._list_view) + self._list_view.show() else: raise ValueError('Invalid view: %r' % view) _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes def resume(self): - # TODO: Do we need this? - #if self._redraw_id is None: - # self._redraw_id = gobject.timeout_add(self._REDRAW_TIMEOUT, - # self._redraw_activity_ring) - # self._redraw_activity_ring() pass def suspend(self): - # TODO: Do we need this? - #if self._redraw_id is not None: - # gobject.source_remove(self._redraw_id) - # self._redraw_id = None pass - def _redraw_activity_ring(self): - # TODO: Do we need this? - #self._donut.redraw() - return True - def has_activities(self): # TODO: Do we need this? #return self._donut.has_activities() @@ -105,8 +97,8 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem): def enable_xo_palette(self): self._enable_xo_palette = True - if self._ring_view is not None: - self._ring_view.enable_xo_palette() + if self._favorites_view is not None: + self._favorites_view.enable_xo_palette() class HomeToolbar(gtk.Toolbar): __gtype_name__ = 'SugarHomeToolbar' @@ -117,7 +109,7 @@ class HomeToolbar(gtk.Toolbar): ([str])), 'view-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([int])) + ([object, object])) } def __init__(self): @@ -144,16 +136,14 @@ class HomeToolbar(gtk.Toolbar): self._add_separator(expand=True) - ring_button = RadioToolButton(named_icon='view-radial', group=None) - ring_button.props.tooltip = _('Ring view') - ring_button.props.accelerator = _('<Ctrl>R') - ring_button.connect('toggled', self.__view_button_toggled_cb, - _RING_VIEW) - self.insert(ring_button, -1) - ring_button.show() + favorites_button = FavoritesButton() + favorites_button.connect('toggled', self.__view_button_toggled_cb, + _FAVORITES_VIEW) + self.insert(favorites_button, -1) + favorites_button.show() list_button = RadioToolButton(named_icon='view-list') - list_button.props.group = ring_button + list_button.props.group = favorites_button list_button.props.tooltip = _('List view') list_button.props.accelerator = _('<Ctrl>L') list_button.connect('toggled', self.__view_button_toggled_cb, @@ -165,8 +155,11 @@ class HomeToolbar(gtk.Toolbar): def __view_button_toggled_cb(self, button, view): if button.props.active: - self.emit('view-changed', view) - + if view == _FAVORITES_VIEW: + self.emit('view-changed', view, button.layout) + else: + self.emit('view-changed', view, None) + def _add_separator(self, expand=False): separator = gtk.SeparatorToolItem() separator.props.draw = False @@ -201,3 +194,50 @@ class HomeToolbar(gtk.Toolbar): self._search_entry.activate() return False +class FavoritesButton(RadioToolButton): + __gtype_name__ = 'SugarFavoritesButton' + + def __init__(self): + RadioToolButton.__init__(self) + + self.props.named_icon = 'view-radial' + self.props.tooltip = _('Favorites view') + self.props.accelerator = _('<Ctrl>R') + self.props.group = None + + self._layout = favoritesview.RANDOM_LAYOUT + + # TRANS: label for the free layout in the favorites view + menu_item = MenuItem(_('Free'), 'activity-start') + menu_item.connect('activate', self.__layout_activate_cb, + favoritesview.RANDOM_LAYOUT) + self.props.palette.menu.append(menu_item) + menu_item.show() + + # TRANS: label for the ring layout in the favorites view + menu_item = MenuItem(_('Ring'), 'view-radial') + menu_item.connect('activate', self.__layout_activate_cb, + favoritesview.RING_LAYOUT) + self.props.palette.menu.append(menu_item) + menu_item.show() + + def __layout_activate_cb(self, menu_item, layout): + if self._layout == layout and self.props.active: + return + elif self._layout != layout: + if layout == favoritesview.RANDOM_LAYOUT: + self.props.named_icon = 'activity-start' + elif layout == favoritesview.RING_LAYOUT: + self.props.named_icon = 'view-radial' + else: + raise ValueError('Invalid layout: %r' % layout) + self._layout = layout + if not self.props.active: + self.props.active = True + else: + self.emit('toggled') + + def _get_layout(self): + return self._layout + layout = property(_get_layout, None) + diff --git a/src/view/home/HomeWindow.py b/src/view/home/HomeWindow.py index 9151d46..03a571e 100644 --- a/src/view/home/HomeWindow.py +++ b/src/view/home/HomeWindow.py @@ -15,7 +15,6 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import gtk -import hippo from sugar.graphics import style from sugar.graphics import palettegroup @@ -44,10 +43,6 @@ class HomeWindow(gtk.Window): self._active = False self._level = ShellModel.ZOOM_HOME - self._canvas = hippo.Canvas() - self.add(self._canvas) - self._canvas.show() - self.set_default_size(gtk.gdk.screen_width(), gtk.gdk.screen_height()) @@ -70,8 +65,9 @@ class HomeWindow(gtk.Window): self.launch_box = LaunchBox() self._activate_view() - self._canvas.set_root(self._home_box) - + self.add(self._home_box) + self._home_box.show() + self._transition_box.connect('completed', self._transition_completed_cb) @@ -122,8 +118,9 @@ class HomeWindow(gtk.Window): self._deactivate_view() self._level = level self._activate_view() - - self._canvas.set_root(self._transition_box) + + self.remove(self.get_child()) + self.add(self._transition_box) if level == ShellModel.ZOOM_HOME: size = style.XLARGE_ICON_SIZE @@ -138,14 +135,22 @@ class HomeWindow(gtk.Window): def _transition_completed_cb(self, transition_box): if self._level == ShellModel.ZOOM_HOME: - self._canvas.set_root(self._home_box) + self.remove(self.get_child()) + self.add(self._home_box) + self._home_box.show() elif self._level == ShellModel.ZOOM_FRIENDS: - self._canvas.set_root(self._friends_box) + self.remove(self.get_child()) + self.add(self._friends_box) + self._friends_box.show() elif self._level == ShellModel.ZOOM_MESH: - self._canvas.set_root(self._mesh_box) + self.remove(self.get_child()) + self.add(self._mesh_box) + self._mesh_box.show() self._mesh_box.focus_search_entry() elif self._level == ShellModel.ZOOM_ACTIVITY: - self._canvas.set_root(self.launch_box) + self.remove(self.get_child()) + self.add(self.launch_box) + self.launch_box.show() def get_home_box(self): return self._home_box diff --git a/src/view/home/Makefile.am b/src/view/home/Makefile.am index 5306e2b..80781d7 100644 --- a/src/view/home/Makefile.am +++ b/src/view/home/Makefile.am @@ -2,7 +2,9 @@ sugardir = $(pkgdatadir)/shell/view/home sugar_PYTHON = \ __init__.py \ activitieslist.py \ - activitiesring.py \ + favoritesview.py \ + favoriteslayout.py \ + grid.py \ FriendView.py \ FriendsBox.py \ launchbox.py \ diff --git a/src/view/home/MeshBox.py b/src/view/home/MeshBox.py index db2f80e..5e05623 100644 --- a/src/view/home/MeshBox.py +++ b/src/view/home/MeshBox.py @@ -429,9 +429,10 @@ class MeshToolbar(gtk.Toolbar): self.search_entry.activate() return False -class MeshBox(hippo.CanvasBox): +class MeshBox(gtk.VBox): + __gtype_name__ = 'SugarMeshBox' def __init__(self): - hippo.CanvasBox.__init__(self) + gobject.GObject.__init__(self) self._model = shellmodel.get_instance().get_mesh() self._buddies = {} @@ -441,14 +442,20 @@ class MeshBox(hippo.CanvasBox): self._buddy_to_activity = {} self._suspended = True self._query = '' - + self._owner_icon = None + self._toolbar = MeshToolbar() self._toolbar.connect('query-changed', self._toolbar_query_changed_cb) - self.append(hippo.CanvasWidget(widget=self._toolbar)) + self.pack_start(self._toolbar, expand=False) + self._toolbar.show() + + canvas = hippo.Canvas() + self.add(canvas) + canvas.show() self._layout_box = hippo.CanvasBox( \ background_color=style.COLOR_WHITE.get_int()) - self.append(self._layout_box, hippo.PACK_EXPAND) + canvas.set_root(self._layout_box) self._layout = SpreadLayout() self._layout_box.set_layout(self._layout) @@ -477,21 +484,31 @@ class MeshBox(hippo.CanvasBox): if self._model.get_mesh(): self._mesh_added_cb(self._model, self._model.get_mesh()) - self._model.connect('mesh-added', - self._mesh_added_cb) - self._model.connect('mesh-removed', - self._mesh_removed_cb) + self._model.connect('mesh-added', self.__mesh_added_cb) + self._model.connect('mesh-removed', self.__mesh_removed_cb) - def _mesh_added_cb(self, model, meshdev): + def __mesh_added_cb(self, model, meshdev): self._add_mesh_icon(meshdev, 1) self._add_mesh_icon(meshdev, 6) self._add_mesh_icon(meshdev, 11) - def _mesh_removed_cb(self, model): + def __mesh_removed_cb(self, model): self._remove_mesh_icon(1) self._remove_mesh_icon(6) self._remove_mesh_icon(11) + def do_size_allocate(self, allocation): + width = allocation.width + height = allocation.height + + min_w_, icon_width = self._owner_icon.get_width_request() + min_h_, icon_height = self._owner_icon.get_height_request(icon_width) + x = (width - icon_width) / 2 + y = (height - icon_height) / 2 - style.GRID_CELL_SIZE + self._layout.move(self._owner_icon, x, y) + + gtk.VBox.do_size_allocate(self, allocation) + def _buddy_added_cb(self, model, buddy_model): self._add_alone_buddy(buddy_model) @@ -533,10 +550,8 @@ class MeshBox(hippo.CanvasBox): def _add_alone_buddy(self, buddy_model): icon = BuddyIcon(buddy_model) if buddy_model.is_owner(): - vertical_offset = - style.GRID_CELL_SIZE - self._layout.add_center(icon, vertical_offset) - else: - self._layout.add(icon) + self._owner_icon = icon + self._layout.add(icon) if hasattr(icon, 'set_filter'): icon.set_filter(self._query) diff --git a/src/view/home/activitieslist.py b/src/view/home/activitieslist.py index 8a824fd..5dab09d 100644 --- a/src/view/home/activitieslist.py +++ b/src/view/home/activitieslist.py @@ -27,19 +27,25 @@ from sugar.graphics.icon import CanvasIcon import view.Shell from view.palettes import ActivityPalette -class ActivitiesList(hippo.CanvasScrollbars): +class ActivitiesList(gtk.ScrolledWindow): __gtype_name__ = 'SugarActivitiesList' def __init__(self): - hippo.CanvasScrollbars.__init__(self) + gobject.GObject.__init__(self) - self.set_policy(hippo.ORIENTATION_HORIZONTAL, hippo.SCROLLBAR_NEVER) - self.props.widget.connect('key-press-event', self.__key_press_event_cb) + self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.set_shadow_type(gtk.SHADOW_NONE) + self.connect('key-press-event', self.__key_press_event_cb) + + canvas = hippo.Canvas() + self.add_with_viewport(canvas) + self.child.set_shadow_type(gtk.SHADOW_NONE) + canvas.show() self._query = '' - self._box = hippo.CanvasBox( \ - background_color=style.COLOR_WHITE.get_int()) - self.set_root(self._box) + self._box = hippo.CanvasBox() + self._box.props.background_color = style.COLOR_WHITE.get_int() + canvas.set_root(self._box) registry = activity.get_registry() registry.get_activities_async(reply_handler=self._get_activities_cb) @@ -81,7 +87,7 @@ class ActivitiesList(hippo.CanvasScrollbars): def __key_press_event_cb(self, widget, event): keyname = gtk.gdk.keyval_name(event.keyval) - vadjustment = self.props.widget.props.vadjustment + vadjustment = self.props.vadjustment if keyname == 'Up': if vadjustment.props.value > vadjustment.props.lower: vadjustment.props.value -= vadjustment.props.step_increment diff --git a/src/view/home/favoriteslayout.py b/src/view/home/favoriteslayout.py new file mode 100644 index 0000000..0400350 --- /dev/null +++ b/src/view/home/favoriteslayout.py @@ -0,0 +1,273 @@ +# Copyright (C) 2008 One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging +import math +import hashlib + +import gobject +import gtk +import hippo + +from sugar.graphics import style +from sugar import activity + +from view.home.grid import Grid + +_logger = logging.getLogger('FavoritesLayout') + +_CELL_SIZE = 4 +_BASE_SCALE = 1000 + +class FavoritesLayout(gobject.GObject, hippo.CanvasLayout): + __gtype_name__ = 'FavoritesLayout' + + def __init__(self): + gobject.GObject.__init__(self) + self.box = None + self.fixed_positions = {} + + def do_set_box(self, box): + self.box = box + + def do_get_height_request(self, for_width): + return 0, gtk.gdk.screen_height() - style.GRID_CELL_SIZE + + def do_get_width_request(self): + return 0, gtk.gdk.screen_width() + + def compare_activities(self, icon_a, icon_b): + return 0 + + def append(self, icon, locked=False): + self.box.insert_sorted(icon, 0, self.compare_activities) + if hasattr(icon, 'fixed_position'): + relative_x, relative_y = icon.fixed_position + if relative_x >= 0 and relative_y >= 0: + min_width_, width = self.box.get_width_request() + min_height_, height = self.box.get_height_request(width) + self.fixed_positions[icon] = \ + (int(relative_x * _BASE_SCALE / float(width)), + int(relative_y * _BASE_SCALE / float(height))) + + def remove(self, icon): + del self.fixed_positions[icon] + self.box.remove(icon) + + def move_icon(self, icon, x, y, locked=False): + if icon not in self.box.get_children(): + raise ValueError('Child not in box.') + + if hasattr(icon, 'get_bundle_id') and hasattr(icon, 'get_version'): + min_width_, width = self.box.get_width_request() + min_height_, height = self.box.get_height_request(width) + registry = activity.get_registry() + registry.set_activity_position( + icon.get_bundle_id(), icon.get_version(), + x * width / float(_BASE_SCALE), + y * height / float(_BASE_SCALE)) + self.fixed_positions[icon] = (x, y) + + def do_allocate(self, x, y, width, height, req_width, req_height, + origin_changed): + raise NotImplementedError() + + def allow_dnd(self): + return False + +class RandomLayout(FavoritesLayout): + __gtype_name__ = 'RandomLayout' + + def __init__(self): + FavoritesLayout.__init__(self) + + min_width_, width = self.do_get_width_request() + min_height_, height = self.do_get_height_request(width) + + self._grid = Grid(width / _CELL_SIZE, height / _CELL_SIZE) + self._grid.connect('child-changed', self.__grid_child_changed_cb) + + def __grid_child_changed_cb(self, grid, child): + child.emit_request_changed() + + def append(self, icon, locked=False): + FavoritesLayout.append(self, icon, locked) + + min_width_, child_width = icon.get_width_request() + min_height_, child_height = icon.get_height_request(child_width) + min_width_, width = self.box.get_width_request() + min_height_, height = self.box.get_height_request(width) + + if icon in self.fixed_positions: + x, y = self.fixed_positions[icon] + x = min(x, width - child_width) + y = min(y, height - child_height) + elif hasattr(icon, 'get_bundle_id'): + name_hash = hashlib.md5(icon.get_bundle_id()) + x = int(name_hash.hexdigest()[:5], 16) % (width - child_width) + y = int(name_hash.hexdigest()[-5:], 16) % (height - child_height) + else: + x = None + y = None + + if x is None or y is None: + self._grid.add(icon, + child_width / _CELL_SIZE, child_height / _CELL_SIZE) + else: + self._grid.add(icon, + child_width / _CELL_SIZE, child_height / _CELL_SIZE, + x / _CELL_SIZE, y / _CELL_SIZE) + + def remove(self, icon): + self._grid.remove(icon) + FavoritesLayout.remove(self, icon) + + def move_icon(self, icon, x, y, locked=False): + self._grid.move(icon, x / _CELL_SIZE, y / _CELL_SIZE, locked) + FavoritesLayout.move_icon(self, icon, x, y, locked) + + def do_allocate(self, x, y, width, height, req_width, req_height, + origin_changed): + for child in self.box.get_layout_children(): + # We need to always get requests to not confuse hippo + min_w_, child_width = child.get_width_request() + min_h_, child_height = child.get_height_request(child_width) + + rect = self._grid.get_child_rect(child.item) + child.allocate(rect.x * _CELL_SIZE, + rect.y * _CELL_SIZE, + child_width, + child_height, + origin_changed) + + def allow_dnd(self): + return True + +_MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \ + style.STANDARD_ICON_SIZE * 2 +_MAXIMUM_RADIUS = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) / 2 - \ + style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING + +class RingLayout(FavoritesLayout): + __gtype_name__ = 'RingLayout' + + def __init__(self): + FavoritesLayout.__init__(self) + self._locked_children = {} + + def append(self, icon, locked=False): + FavoritesLayout.append(self, icon, locked) + if locked: + child = self.box.find_box_child(icon) + self._locked_children[child] = (0, 0) + + def remove(self, icon): + child = self.box.find_box_child(icon) + if child in self._locked_children: + del self._locked_children[child] + FavoritesLayout.remove(self, icon) + + def move_icon(self, icon, x, y, locked=False): + FavoritesLayout.move_icon(self, icon, x, y, locked) + if locked: + child = self.box.find_box_child(icon) + self._locked_children[child] = (x, y) + + def _calculate_radius_and_icon_size(self, children_count): + angle = 2 * math.pi / children_count + + # what's the radius required without downscaling? + distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING + icon_size = style.STANDARD_ICON_SIZE + + if children_count == 1: + radius = 0 + else: + radius = math.sqrt(distance ** 2 / + (math.sin(angle) ** 2 + (math.cos(angle) - 1) ** 2)) + + if radius < _MINIMUM_RADIUS: + # we can upscale, if we want + icon_size += style.STANDARD_ICON_SIZE * \ + (0.5 * (_MINIMUM_RADIUS - radius) / _MINIMUM_RADIUS) + radius = _MINIMUM_RADIUS + elif radius > _MAXIMUM_RADIUS: + radius = _MAXIMUM_RADIUS + # need to downscale. what's the icon size required? + distance = math.sqrt((radius * math.sin(angle)) ** 2 + \ + (radius * (math.cos(angle) - 1)) ** 2) + icon_size = distance - style.DEFAULT_SPACING + + return radius, icon_size + + def _calculate_position(self, radius, icon_size, index, children_count): + width, height = self.box.get_allocation() + angle = index * (2 * math.pi / children_count) - math.pi / 2 + x = radius * math.cos(angle) + (width - icon_size) / 2 + y = radius * math.sin(angle) + (height - icon_size - + style.GRID_CELL_SIZE) / 2 + return x, y + + def _get_children_in_ring(self): + children_in_ring = [child for child in self.box.get_layout_children() \ + if child not in self._locked_children] + return children_in_ring + + def _update_icon_sizes(self): + children_in_ring = self._get_children_in_ring() + radius_, icon_size = \ + self._calculate_radius_and_icon_size(len(children_in_ring)) + + for child in children_in_ring: + child.item.props.size = icon_size + + def do_allocate(self, x, y, width, height, req_width, req_height, + origin_changed): + children_in_ring = self._get_children_in_ring() + if children_in_ring: + radius, icon_size = \ + self._calculate_radius_and_icon_size(len(children_in_ring)) + + for n in range(len(children_in_ring)): + child = children_in_ring[n] + + x, y = self._calculate_position(radius, icon_size, n, + len(children_in_ring)) + + # We need to always get requests to not confuse hippo + min_w_, child_width = child.get_width_request() + min_h_, child_height = child.get_height_request(child_width) + + child.allocate(int(x), int(y), child_width, child_height, + origin_changed) + + for child in self._locked_children.keys(): + x, y = self._locked_children[child] + + # We need to always get requests to not confuse hippo + min_w_, child_width = child.get_width_request() + min_h_, child_height = child.get_height_request(child_width) + + child.allocate(int(x), int(y), child_width, child_height, + origin_changed) + + def compare_activities(self, icon_a, icon_b): + if hasattr(icon_a, 'installation_time') and \ + hasattr(icon_b, 'installation_time'): + return icon_b.installation_time - icon_a.installation_time + else: + return 0 + diff --git a/src/view/home/activitiesring.py b/src/view/home/favoritesview.py index dccb066..a6e2268 100644 --- a/src/view/home/activitiesring.py +++ b/src/view/home/favoritesview.py @@ -34,48 +34,66 @@ import view.Shell from view.palettes import JournalPalette from view.palettes import CurrentActivityPalette, ActivityPalette from view.home.MyIcon import MyIcon +from view.home import favoriteslayout from model import shellmodel from model.shellmodel import ShellModel from hardware import schoolserver from controlpanel.gui import ControlPanel from session import get_session_manager -_logger = logging.getLogger('ActivitiesRing') +_logger = logging.getLogger('FavoritesView') -class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): - __gtype_name__ = 'SugarActivitiesRing' +_ICON_DND_TARGET = ('activity-icon', gtk.TARGET_SAME_WIDGET, 0) - def __init__(self): - hippo.CanvasBox.__init__(self, - background_color=style.COLOR_WHITE.get_int()) +RING_LAYOUT = 0 +RANDOM_LAYOUT = 1 - shell_model = shellmodel.get_instance() - shell_model.connect('notify::state', self._shell_state_changed_cb) +_LAYOUT_MAP = {RING_LAYOUT: favoriteslayout.RingLayout, + RANDOM_LAYOUT: favoriteslayout.RandomLayout} + +class FavoritesView(hippo.Canvas): + __gtype_name__ = 'SugarFavoritesView' - self._my_icon = _MyIcon(style.XLARGE_ICON_SIZE) - self.append(self._my_icon, hippo.PACK_FIXED) + def __init__(self, **kwargs): + gobject.GObject.__init__(self, **kwargs) - self._current_activity = CurrentActivityIcon() - self.append(self._current_activity, hippo.PACK_FIXED) + # DND stuff + self._pressed_button = None + self._press_start_x = None + self._press_start_y = None + self._last_clicked_icon = None - self.set_layout(RingLayout()) + self._box = hippo.CanvasBox() + self._box.props.background_color = style.COLOR_WHITE.get_int() + self.set_root(self._box) + + self._my_icon = None + self._current_activity = None + self._layout = None + self._set_layout(RANDOM_LAYOUT) registry = activity.get_registry() - registry.get_activities_async(reply_handler=self._get_activities_cb) registry.connect('activity-added', self.__activity_added_cb) registry.connect('activity-removed', self.__activity_removed_cb) registry.connect('activity-changed', self.__activity_changed_cb) - def _compare_activities(self, icon_a, icon_b): - if hasattr(icon_a, 'installation_time') and \ - hasattr(icon_b, 'installation_time'): - return icon_b.installation_time - icon_a.installation_time - else: - return 0 + shell_model = shellmodel.get_instance() + shell_model.connect('notify::state', self._shell_state_changed_cb) + + # More DND stuff + self.add_events(gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.POINTER_MOTION_HINT_MASK) + self.connect('motion-notify-event', self.__motion_notify_event_cb) + self.connect('button-press-event', self.__button_press_event_cb) + self.connect('drag-begin', self.__drag_begin_cb) + self.connect('drag-motion', self.__drag_motion_cb) + self.connect('drag-drop', self.__drag_drop_cb) + self.connect('drag-data-received', self.__drag_data_received_cb) def _add_activity(self, activity_info): icon = ActivityIcon(activity_info) - self.insert_sorted(icon, 0, self._compare_activities) + icon.props.size = style.STANDARD_ICON_SIZE + self._layout.append(icon) def _get_activities_cb(self, activity_list): for info in activity_list: @@ -88,7 +106,7 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): self._add_activity(activity_info) def _find_activity_icon(self, bundle_id, version): - for icon in self.get_children(): + for icon in self._box.get_children(): if isinstance(icon, ActivityIcon) and \ icon.bundle_id == bundle_id and icon.version == version: return icon @@ -98,16 +116,16 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): icon = self._find_activity_icon(activity_info.bundle_id, activity_info.version) if icon is not None: - self.remove(icon) + self._layout.remove(icon) def __activity_changed_cb(self, activity_registry, activity_info): - if activity_info.bundle_id == "org.laptop.JournalActivity": + if activity_info.bundle_id == 'org.laptop.JournalActivity': return icon = self._find_activity_icon(activity_info.bundle_id, activity_info.version) - if icon is not None and not activity_info.favorite: - self.remove(icon) - elif icon is None and activity_info.favorite: + if icon is not None: + self._box.remove(icon) + if activity_info.favorite: self._add_activity(activity_info) def _shell_state_changed_cb(self, model, pspec): @@ -115,23 +133,137 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): if model.props.state == ShellModel.STATE_SHUTDOWN: pass - def do_allocate(self, width, height, origin_changed): - hippo.CanvasBox.do_allocate(self, width, height, origin_changed) + def do_size_allocate(self, allocation): + width = allocation.width + height = allocation.height - [my_icon_width, my_icon_height] = self._my_icon.get_allocation() + min_w_, my_icon_width = self._my_icon.get_width_request() + min_h_, my_icon_height = self._my_icon.get_height_request(my_icon_width) x = (width - my_icon_width) / 2 y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 - self.set_position(self._my_icon, x, y) + self._layout.move_icon(self._my_icon, x, y, locked=True) - [icon_width, icon_height] = self._current_activity.get_allocation() + min_w_, icon_width = self._current_activity.get_width_request() + min_h_, icon_height = \ + self._current_activity.get_height_request(icon_width) x = (width - icon_width) / 2 - y = (height + my_icon_height + style.DEFAULT_PADDING \ - - style.GRID_CELL_SIZE) / 2 - self.set_position(self._current_activity, x, y) + y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 + \ + my_icon_height + style.DEFAULT_PADDING + self._layout.move_icon(self._current_activity, x, y, locked=True) + + hippo.Canvas.do_size_allocate(self, allocation) def enable_xo_palette(self): self._my_icon.enable_palette() + # TODO: Dnd methods. This should be merged somehow inside hippo-canvas. + def __button_press_event_cb(self, widget, event): + if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: + self._last_clicked_icon = self._get_icon_at_coords(event.x, event.y) + if self._last_clicked_icon is not None: + self._pressed_button = event.button + self._press_start_x = event.x + self._press_start_y = event.y + + return False + + def _get_icon_at_coords(self, x, y): + for icon in self._box.get_children(): + icon_x, icon_y = icon.get_context().translate_to_widget(icon) + icon_width, icon_height = icon.get_allocation() + + if (x >= icon_x ) and (x <= icon_x + icon_width) and \ + (y >= icon_y ) and (y <= icon_y + icon_height) and \ + isinstance(icon, ActivityIcon): + return icon + return None + + def __motion_notify_event_cb(self, widget, event): + if not self._pressed_button: + return False + + # if the mouse button is not pressed, no drag should occurr + if not event.state & gtk.gdk.BUTTON1_MASK: + self._pressed_button = None + return False + + if event.is_hint: + x, y, state_ = event.window.get_pointer() + else: + x = event.x + y = event.y + + if widget.drag_check_threshold(int(self._press_start_x), + int(self._press_start_y), + int(x), + int(y)): + context_ = widget.drag_begin([_ICON_DND_TARGET], + gtk.gdk.ACTION_MOVE, + 1, + event) + return False + + def __drag_begin_cb(self, widget, context): + icon_file_name = self._last_clicked_icon.props.file_name + # TODO: we should get the pixbuf from the widget, so it has colors, etc + pixbuf = gtk.gdk.pixbuf_new_from_file(icon_file_name) + + hot_spot = style.zoom(10) + context.set_icon_pixbuf(pixbuf, hot_spot, hot_spot) + + def __drag_motion_cb(self, widget, context, x, y, time): + if self._last_clicked_icon is not None: + context.drag_status(context.suggested_action, time) + return True + else: + return False + + def __drag_drop_cb(self, widget, context, x, y, time): + if self._last_clicked_icon is not None: + self.drag_get_data(context, _ICON_DND_TARGET[0]) + + self._layout.move_icon(self._last_clicked_icon, x, y) + + self._pressed_button = None + self._press_start_x = None + self._press_start_y = None + self._last_clicked_icon = None + + return True + else: + return False + + def __drag_data_received_cb(self, widget, context, x, y, selection_data, + info, time): + context.drop_finish(success=True, time=time) + + def _set_layout(self, layout): + if layout not in _LAYOUT_MAP: + raise ValueError('Unknown favorites layout: %r' % layout) + if type(self._layout) != _LAYOUT_MAP[layout]: + self._box.clear() + self._layout = _LAYOUT_MAP[layout]() + self._box.set_layout(self._layout) + + self._my_icon = _MyIcon(style.XLARGE_ICON_SIZE) + self._my_icon.log = True + self._layout.append(self._my_icon, locked=True) + + self._current_activity = CurrentActivityIcon() + self._layout.append(self._current_activity, locked=True) + + registry = activity.get_registry() + registry.get_activities_async(reply_handler=self._get_activities_cb) + + if self._layout.allow_dnd(): + self.drag_source_set(0, [], 0) + self.drag_dest_set(0, [], 0) + else: + self.drag_source_unset() + self.drag_dest_unset() + + layout = property(None, _set_layout) + class ActivityIcon(CanvasIcon): def __init__(self, activity_info): CanvasIcon.__init__(self, cache=True, file_name=activity_info.icon) @@ -167,6 +299,10 @@ class ActivityIcon(CanvasIcon): return self._activity_info.installation_time installation_time = property(_get_installation_time, None) + def _get_fixed_position(self): + return self._activity_info.position + fixed_position = property(_get_fixed_position, None) + class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): def __init__(self): CanvasIcon.__init__(self, cache=True) @@ -184,12 +320,11 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): self._home_model.get_active_activity().get_window().activate(1) def _update(self, home_activity): - _logger.debug('CurrentActivityIcon._update') self.props.file_name = home_activity.get_icon_path() self.props.xo_color = home_activity.get_icon_color() self.props.size = style.STANDARD_ICON_SIZE - if home_activity.get_type() == "org.laptop.JournalActivity": + if home_activity.get_type() == 'org.laptop.JournalActivity': palette = JournalPalette(home_activity) else: palette = CurrentActivityPalette(home_activity) @@ -198,95 +333,6 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): def __active_activity_changed_cb(self, home_model, home_activity): self._update(home_activity) -class RingLayout(gobject.GObject, hippo.CanvasLayout): - __gtype_name__ = 'SugarRingLayout' - def __init__(self): - gobject.GObject.__init__(self) - self._box = None - - def do_set_box(self, box): - self._box = box - - def do_get_height_request(self, for_width): - return 0, gtk.gdk.screen_height() - style.GRID_CELL_SIZE - - def do_get_width_request(self): - return 0, gtk.gdk.screen_width() - - def _calculate_radius_and_icon_size(self, children_count): - minimum_radius = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \ - style.STANDARD_ICON_SIZE * 2 - maximum_radius = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) \ - / 2 - style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING - angle = 2 * math.pi / children_count - - _logger.debug('minimum_radius %r maximum_radius %r angle %r' % \ - (minimum_radius, maximum_radius, angle)) - - # what's the radius required without downscaling? - distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING - icon_size = style.STANDARD_ICON_SIZE - - if children_count == 1: - radius = 0 - else: - radius = math.sqrt(distance ** 2 / - (math.sin(angle) ** 2 + (math.cos(angle) - 1) ** 2)) - - _logger.debug('radius 1 %r' % radius) - - if radius < minimum_radius: - # we can upscale, if we want - icon_size += style.STANDARD_ICON_SIZE * \ - (0.5 * (minimum_radius - radius)/minimum_radius) - radius = minimum_radius - elif radius > maximum_radius: - radius = maximum_radius - # need to downscale. what's the icon size required? - distance = math.sqrt((radius * math.sin(angle)) ** 2 + \ - (radius * (math.cos(angle) - 1)) ** 2) - icon_size = distance - style.DEFAULT_SPACING - - _logger.debug('radius 2 %r icon_size %r' % (radius, icon_size)) - - return radius, icon_size - - def _calculate_position(self, radius, icon_size, index, children_count): - width, height = self._box.get_allocation() - angle = index * (2 * math.pi / children_count) - math.pi/2 - x = radius * math.cos(angle) + (width - icon_size) / 2 - y = radius * math.sin(angle) + (height - icon_size - - style.GRID_CELL_SIZE) / 2 - return x, y - - def do_allocate(self, x, y, width, height, req_width, req_height, - origin_changed): - _logger.debug('RingLayout.do_allocate: %r %r %r %r %r %r %r' % (x, y, - width, height, req_width, req_height, origin_changed)) - - children = self._box.get_layout_children() - if not children: - return - - radius, icon_size = self._calculate_radius_and_icon_size(len(children)) - - for n in range(len(children)): - child = children[n] - # TODO: We get here a glib warning and I don't know why. - child.item.props.size = icon_size - - x, y = self._calculate_position(radius, icon_size, n, len(children)) - - # We need to always get requests to not confuse hippo - min_w, child_width = child.get_width_request() - min_h, child_height = child.get_height_request(child_width) - - child.allocate(int(x), - int(y), - child_width, - child_height, - origin_changed) - class _MyIcon(MyIcon): def __init__(self, scale): MyIcon.__init__(self, scale) diff --git a/src/view/home/grid.py b/src/view/home/grid.py new file mode 100644 index 0000000..abea706 --- /dev/null +++ b/src/view/home/grid.py @@ -0,0 +1,223 @@ +# Copyright (C) 2007 Red Hat, Inc. +# Copyright (C) 2008 One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from numpy import array +import random + +import gobject +import gtk + +_PLACE_TRIALS = 20 +_MAX_WEIGHT = 255 +_REFRESH_RATE = 200 +_MAX_COLLISIONS_PER_REFRESH = 20 + +class Grid(gobject.GObject): + __gsignals__ = { + 'child-changed' : (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + def __init__(self, width, height): + gobject.GObject.__init__(self) + + self.width = width + self.height = height + self._children = [] + self._child_rects = {} + self._locked_children = set() + self._collisions = [] + self._collisions_sid = 0 + + self._array = array([0], dtype='b') + self._array.resize(width * height) + + def add(self, child, width, height, x=None, y=None, locked=False): + if x is not None and y is not None: + rect = gtk.gdk.Rectangle(x, y, width, height) + weight = self._compute_weight(rect) + else: + trials = _PLACE_TRIALS + weight = _MAX_WEIGHT + while trials > 0 and weight: + x = int(random.random() * (self.width - width)) + y = int(random.random() * (self.height - height)) + + rect = gtk.gdk.Rectangle(x, y, width, height) + new_weight = self._compute_weight(rect) + if weight > new_weight: + weight = new_weight + + trials -= 1 + + self._child_rects[child] = rect + self._children.append(child) + self._add_weight(self._child_rects[child]) + if locked: + self._locked_children.add(child) + + if weight > 0: + self._detect_collisions(child) + + def remove(self, child): + self._children.remove(child) + self._remove_weight(self._child_rects[child]) + self._locked_children.discard(child) + del self._child_rects[child] + + def move(self, child, x, y, locked=False): + self._remove_weight(self._child_rects[child]) + + rect = self._child_rects[child] + rect.x = x + rect.y = y + + weight = self._compute_weight(rect) + self._add_weight(self._child_rects[child]) + + if locked: + self._locked_children.add(child) + else: + self._locked_children.discard(child) + + if weight > 0: + self._detect_collisions(child) + + def _shift_child(self, child, weight): + rect = self._child_rects[child] + + new_rects = [] + + # Get rects right, left, bottom and top + if (rect.x + rect.width < self.width - 1): + new_rects.append(gtk.gdk.Rectangle(rect.x + 1, rect.y, + rect.width, rect.height)) + + if (rect.x - 1 > 0): + new_rects.append(gtk.gdk.Rectangle(rect.x - 1, rect.y, + rect.width, rect.height)) + + if (rect.y + rect.height < self.height - 1): + new_rects.append(gtk.gdk.Rectangle(rect.x, rect.y + 1, + rect.width, rect.height)) + + if (rect.y - 1 > 0): + new_rects.append(gtk.gdk.Rectangle(rect.x, rect.y - 1, + rect.width, rect.height)) + + # Get diagonal rects + if rect.x + rect.width < self.width - 1 and \ + rect.y + rect.height < self.height - 1: + new_rects.append(gtk.gdk.Rectangle(rect.x + 1, rect.y + 1, + rect.width, rect.height)) + + if rect.x - 1 > 0 and rect.y + rect.height < self.height - 1: + new_rects.append(gtk.gdk.Rectangle(rect.x - 1, rect.y + 1, + rect.width, rect.height)) + + if rect.x + rect.width < self.width - 1 and rect.y - 1 > 0: + new_rects.append(gtk.gdk.Rectangle(rect.x + 1, rect.y - 1, + rect.width, rect.height)) + + if rect.x - 1 > 0 and rect.y - 1 > 0: + new_rects.append(gtk.gdk.Rectangle(rect.x - 1, rect.y - 1, + rect.width, rect.height)) + + random.shuffle(new_rects) + + best_rect = None + for new_rect in new_rects: + new_weight = self._compute_weight(new_rect) + if new_weight < weight: + best_rect = new_rect + weight = new_weight + + if best_rect: + self._child_rects[child] = best_rect + weight = self._shift_child(child, weight) + + return weight + + def __solve_collisions_cb(self): + for i in range(_MAX_COLLISIONS_PER_REFRESH): + collision = self._collisions.pop(0) + + old_rect = self._child_rects[collision] + self._remove_weight(old_rect) + weight = self._compute_weight(old_rect) + weight = self._shift_child(collision, weight) + self._add_weight(self._child_rects[collision]) + + # TODO: we shouldn't give up the first time we failed to find a + # better position. + if old_rect != self._child_rects[collision]: + self._detect_collisions(collision) + self.emit('child-changed', collision) + if weight > 0: + self._collisions.append(collision) + + if not self._collisions: + self._collisions_sid = 0 + return False + + return True + + def _detect_collisions(self, child): + collision_found = False + child_rect = self._child_rects[child] + for c in self._children: + intersection = child_rect.intersect(self._child_rects[c]) + if c != child and intersection.width > 0: + if c not in self._locked_children and c not in self._collisions: + collision_found = True + self._collisions.append(c) + + if collision_found: + if child not in self._collisions: + self._collisions.append(child) + + if len(self._collisions) and not self._collisions_sid: + self._collisions_sid = gobject.timeout_add(_REFRESH_RATE, + self.__solve_collisions_cb, priority=gobject.PRIORITY_LOW) + + def _add_weight(self, rect): + for i in range(rect.x, rect.x + rect.width): + for j in range(rect.y, rect.y + rect.height): + self[j, i] += 1 + + def _remove_weight(self, rect): + for i in range(rect.x, rect.x + rect.width): + for j in range(rect.y, rect.y + rect.height): + self[j, i] -= 1 + + def _compute_weight(self, rect): + weight = 0 + + for i in range(rect.x, rect.x + rect.width): + for j in range(rect.y, rect.y + rect.height): + weight += self[j, i] + + return weight + + def __getitem__(self, (row, col)): + return self._array[col + row * self.width] + + def __setitem__(self, (row, col), value): + self._array[col + row * self.width] = value + + def get_child_rect(self, child): + return self._child_rects[child] diff --git a/src/view/home/launchbox.py b/src/view/home/launchbox.py index a70cb61..dfbec40 100644 --- a/src/view/home/launchbox.py +++ b/src/view/home/launchbox.py @@ -25,10 +25,9 @@ from sugar.graphics.xocolor import XoColor from model import shellmodel from view.pulsingicon import CanvasPulsingIcon -class LaunchBox(hippo.CanvasBox): +class LaunchBox(hippo.Canvas): def __init__(self): - gobject.GObject.__init__( - self, background_color=style.COLOR_WHITE.get_int()) + gobject.GObject.__init__(self) self._activity_icon = CanvasPulsingIcon() @@ -38,8 +37,9 @@ class LaunchBox(hippo.CanvasBox): style.COLOR_TRANSPARENT.get_svg())) vbox = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL) + vbox.props.background_color = style.COLOR_WHITE.get_int() vbox.append(self._activity_icon, hippo.PACK_EXPAND) - self.append(vbox, hippo.PACK_EXPAND) + self.set_root(vbox) self._animator = animator.Animator(1.0) diff --git a/src/view/home/spreadlayout.py b/src/view/home/spreadlayout.py index 0ad350a..f7584ab 100644 --- a/src/view/home/spreadlayout.py +++ b/src/view/home/spreadlayout.py @@ -14,159 +14,15 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -from numpy import array -from random import random - import hippo import gobject import gtk from sugar.graphics import style -_PLACE_TRIALS = 20 -_MAX_WEIGHT = 255 -_CELL_SIZE = 4 - -class _Grid(gobject.GObject): - __gsignals__ = { - 'child-changed' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) - } - def __init__(self, width, height): - gobject.GObject.__init__(self) - - self.width = width - self.height = height - self._children = [] - self._collisions = [] - self._collisions_sid = 0 - - self._array = array([0], dtype='b') - self._array.resize(width * height) - - def add(self, child, width, height): - trials = _PLACE_TRIALS - weight = _MAX_WEIGHT - while trials > 0 and weight: - x = int(random() * (self.width - width)) - y = int(random() * (self.height - height)) - - rect = gtk.gdk.Rectangle(x, y, width, height) - new_weight = self._compute_weight(rect) - if weight > new_weight: - weight = new_weight - - trials -= 1 - - child.grid_rect = rect - child.locked = False - - self._add_child(child) - - if weight > 0: - self._detect_collisions(child) - - def remove(self, child): - self._children.remove(child) - self._remove_weight(child.grid_rect) - child.grid_rect = None - - def _add_child(self, child): - self._children.append(child) - self.add_weight(child.grid_rect) - - def _move_child(self, child, new_rect): - self._remove_weight(child.grid_rect) - self.add_weight(new_rect) - - child.grid_rect = new_rect - - self.emit('child-changed', child) - - def _shift_child(self, child): - rect = child.grid_rect - weight = self._compute_weight(rect) - new_rects = [] - - if (rect.x + rect.width < self.width - 1): - new_rects.append(gtk.gdk.Rectangle(rect.x + 1, rect.y, - rect.width, rect.height)) - - if (rect.x - 1 > 0): - new_rects.append(gtk.gdk.Rectangle(rect.x - 1, rect.y, - rect.width, rect.height)) - - if (rect.y + rect.height < self.height - 1): - new_rects.append(gtk.gdk.Rectangle(rect.x, rect.y + 1, - rect.width, rect.height)) - - if (rect.y - 1 > 0): - new_rects.append(gtk.gdk.Rectangle(rect.x, rect.y - 1, - rect.width, rect.height)) - - best_rect = None - for new_rect in new_rects: - new_weight = self._compute_weight(new_rect) - if new_weight < weight: - best_rect = new_rect - weight = new_weight - - if best_rect: - self._move_child(child, best_rect) - - return weight - - - def _solve_collisions(self): - for collision in self._collisions[:]: - weight = self._shift_child(collision) - if not weight: - self._collisions.remove(collision) - - return (len(self._collisions) > 0) - - def _detect_collisions(self, child): - collision_found = False - for c in self._children: - intersection = child.grid_rect.intersect(c.grid_rect) - if c != child and intersection.width > 0: - if c not in self._collisions: - collision_found = True - self._collisions.append(c) - - if collision_found: - if child not in self._collisions: - self._collisions.append(child) - -# if len(self._collisions) and not self._collisions_sid: -# self._collisions_sid = gobject.idle_add(self._solve_collisions) - - def add_weight(self, rect): - for i in range(rect.x, rect.x + rect.width): - for j in range(rect.y, rect.y + rect.height): - self[j, i] += 1 - - def _remove_weight(self, rect): - for i in range(rect.x, rect.x + rect.width): - for j in range(rect.y, rect.y + rect.height): - self[j, i] -= 1 - - def _compute_weight(self, rect): - weight = 0 - - for i in range(rect.x, rect.x + rect.width): - for j in range(rect.y, rect.y + rect.height): - weight += self[j, i] - - return weight - - def __getitem__(self, (row, col)): - return self._array[col + row * self.width] - - def __setitem__(self, (row, col), value): - self._array[col + row * self.width] = value +from view.home.grid import Grid +_CELL_SIZE = 4 class SpreadLayout(gobject.GObject, hippo.CanvasLayout): __gtype_name__ = 'SugarSpreadLayout' @@ -177,35 +33,22 @@ class SpreadLayout(gobject.GObject, hippo.CanvasLayout): min_width, width = self.do_get_width_request() min_height, height = self.do_get_height_request(width) - self._grid = _Grid(width / _CELL_SIZE, height / _CELL_SIZE) + self._grid = Grid(width / _CELL_SIZE, height / _CELL_SIZE) self._grid.connect('child-changed', self._grid_child_changed_cb) - def add_center(self, child, vertical_offset=0): - self._box.append(child) - - width, height = self._get_child_grid_size(child) - rect = gtk.gdk.Rectangle(int((self._grid.width - width) / 2), - int((self._grid.height - height) / 2), - width + 1, height + 1) - self._grid.add_weight(rect) - - box_child = self._box.find_box_child(child) - box_child.grid_rect = None - box_child.vertical_offset = vertical_offset - def add(self, child): self._box.append(child) width, height = self._get_child_grid_size(child) - box_child = self._box.find_box_child(child) - self._grid.add(box_child, width, height) + self._grid.add(child, width, height) def remove(self, child): - box_child = self._box.find_box_child(child) - self._grid.remove(box_child) - + self._grid.remove(child) self._box.remove(child) + def move(self, child, x, y): + self._grid.move(child, x / _CELL_SIZE, y / _CELL_SIZE, locked=True) + def do_set_box(self, box): self._box = box @@ -222,19 +65,12 @@ class SpreadLayout(gobject.GObject, hippo.CanvasLayout): min_w, child_width = child.get_width_request() min_h, child_height = child.get_height_request(child_width) - rect = child.grid_rect - if child.grid_rect: - child.allocate(rect.x * _CELL_SIZE, - rect.y * _CELL_SIZE, - rect.width * _CELL_SIZE, - rect.height * _CELL_SIZE, - origin_changed) - else: - vertical_offset = child.vertical_offset - child_x = x + (width - child_width) / 2 - child_y = y + (height - child_height + vertical_offset) / 2 - child.allocate(child_x, child_y, child_width, child_height, - origin_changed) + rect = self._grid.get_child_rect(child.item) + child.allocate(rect.x * _CELL_SIZE, + rect.y * _CELL_SIZE, + rect.width * _CELL_SIZE, + rect.height * _CELL_SIZE, + origin_changed) def _get_child_grid_size(self, child): min_width, width = child.get_width_request() @@ -242,5 +78,6 @@ class SpreadLayout(gobject.GObject, hippo.CanvasLayout): return int(width / _CELL_SIZE), int(height / _CELL_SIZE) - def _grid_child_changed_cb(self, grid, box_child): - box_child.item.emit_request_changed() + def _grid_child_changed_cb(self, grid, child): + child.emit_request_changed() + diff --git a/src/view/home/transitionbox.py b/src/view/home/transitionbox.py index c4ef6ca..fb351f8 100644 --- a/src/view/home/transitionbox.py +++ b/src/view/home/transitionbox.py @@ -59,7 +59,7 @@ class _Layout(gobject.GObject, hippo.CanvasLayout): y + (height - child_height) / 2, child_width, child_height, origin_changed) -class TransitionBox(hippo.CanvasBox): +class TransitionBox(hippo.Canvas): __gtype_name__ = 'SugarTransitionBox' __gsignals__ = { @@ -68,16 +68,19 @@ class TransitionBox(hippo.CanvasBox): } def __init__(self): - hippo.CanvasBox.__init__(self, - background_color=style.COLOR_WHITE.get_int()) + gobject.GObject.__init__(self) + + self._box = hippo.CanvasBox() + self._box.props.background_color = style.COLOR_WHITE.get_int() + self.set_root(self._box) self._size = style.XLARGE_ICON_SIZE self._layout = _Layout() - self.set_layout(self._layout) + self._box.set_layout(self._layout) self._my_icon = MyIcon(self._size) - self.append(self._my_icon) + self._box.append(self._my_icon) self._animator = animator.Animator(0.3) self._animator.connect('completed', self._animation_completed_cb) diff --git a/src/view/keyhandler.py b/src/view/keyhandler.py index d9feda1..c7d3001 100644 --- a/src/view/keyhandler.py +++ b/src/view/keyhandler.py @@ -43,26 +43,25 @@ _actions_table = { 'F4' : 'zoom_activity', 'F9' : 'brightness_down', 'F10' : 'brightness_up', - '<ctrl>F9' : 'brightness_min', - '<ctrl>F10' : 'brightness_max', + '<alt>F9' : 'brightness_min', + '<alt>F10' : 'brightness_max', 'F11' : 'volume_down', 'F12' : 'volume_up', - '<ctrl>F11' : 'volume_min', - '<ctrl>F12' : 'volume_max', + '<alt>F11' : 'volume_min', + '<alt>F12' : 'volume_max', '<alt>1' : 'screenshot', - '<alt>f' : 'frame', '0x93' : 'frame', '0xEB' : 'rotate', - '<alt>r' : 'rotate', - '<alt>q' : 'quit_emulator', '<alt>Tab' : 'next_window', - '<alt>n' : 'next_window', '<alt><shift>Tab': 'previous_window', - '<alt>p' : 'previous_window', - '<ctrl>Escape' : 'close_window', - '<ctrl>q' : 'close_window', + '<alt>Escape' : 'close_window', '0xDC' : 'open_search', - '<alt>s' : 'say_text' +# the following are intended for emulator users + '<alt><shift>f' : 'frame', + '<alt><shift>q' : 'quit_emulator', + '<alt><shift>o' : 'open_search', + '<alt><shift>r' : 'rotate', + '<alt><shift>s' : 'say_text', } J_DBUS_SERVICE = 'org.laptop.Journal' diff --git a/src/view/palettes.py b/src/view/palettes.py index bda7f95..8406fd0 100644 --- a/src/view/palettes.py +++ b/src/view/palettes.py @@ -116,11 +116,11 @@ class ActivityPalette(Palette): def _update_favorite_item(self): label = self._favorite_item.child if self._favorite: - label.set_text(_('Remove from ring')) + label.set_text(_('Remove favorite')) xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(), style.COLOR_TRANSPARENT.get_svg())) else: - label.set_text(_('Add to ring')) + label.set_text(_('Make favorite')) xo_color = profile.get_color() self._favorite_icon.props.xo_color = xo_color |