diff options
author | Tomeu Vizoso <tomeu@tomeuvizoso.net> | 2008-04-02 12:17:28 (GMT) |
---|---|---|
committer | Tomeu Vizoso <tomeu@tomeuvizoso.net> | 2008-04-02 12:17:28 (GMT) |
commit | d129582f9ed36a6a2d0de9a9c2aee7cb6195f5f0 (patch) | |
tree | 1f96f094a16d6598bac6d8ada051e88999c5b293 /src | |
parent | 7d8d1de47e435dd402461f6344fc2bb15d024841 (diff) |
New home view.
Diffstat (limited to 'src')
-rw-r--r-- | src/model/BuddyModel.py | 2 | ||||
-rw-r--r-- | src/view/BuddyIcon.py | 2 | ||||
-rw-r--r-- | src/view/BuddyMenu.py | 10 | ||||
-rw-r--r-- | src/view/home/FriendsBox.py | 3 | ||||
-rw-r--r-- | src/view/home/HomeBox.py | 307 | ||||
-rw-r--r-- | src/view/home/HomeWindow.py | 2 | ||||
-rw-r--r-- | src/view/home/Makefile.am | 3 | ||||
-rw-r--r-- | src/view/home/MeshBox.py | 17 | ||||
-rwxr-xr-x | src/view/home/activitiesdonut.py | 556 | ||||
-rw-r--r-- | src/view/home/activitieslist.py | 29 | ||||
-rw-r--r-- | src/view/home/activitiesring.py | 210 | ||||
-rw-r--r-- | src/view/home/snowflakelayout.py | 5 | ||||
-rw-r--r-- | src/view/home/transitionbox.py | 2 |
13 files changed, 389 insertions, 759 deletions
diff --git a/src/model/BuddyModel.py b/src/model/BuddyModel.py index 11c6567..c989aa7 100644 --- a/src/model/BuddyModel.py +++ b/src/model/BuddyModel.py @@ -20,7 +20,7 @@ from sugar.presence import presenceservice from sugar.graphics.xocolor import XoColor import gobject -_NOT_PRESENT_COLOR = "#888888,#BBBBBB" +_NOT_PRESENT_COLOR = "#d5d5d5,#FFFFFF" class BuddyModel(gobject.GObject): __gsignals__ = { diff --git a/src/view/BuddyIcon.py b/src/view/BuddyIcon.py index 3734001..be79264 100644 --- a/src/view/BuddyIcon.py +++ b/src/view/BuddyIcon.py @@ -43,7 +43,7 @@ class BuddyIcon(CanvasIcon): def _update_color(self): if self._greyed_out: self.props.stroke_color = '#D5D5D5' - self.props.fill_color = '#E5E5E5' + self.props.fill_color = style.COLOR_TRANSPARENT.get_svg() else: self.props.xo_color = self._buddy.get_color() diff --git a/src/view/BuddyMenu.py b/src/view/BuddyMenu.py index e7e12ca..d72ed5b 100644 --- a/src/view/BuddyMenu.py +++ b/src/view/BuddyMenu.py @@ -76,7 +76,15 @@ class BuddyMenu(Palette): self._update_invite_menu(activity) def _update_invite_menu(self, activity): - if activity is None: + buddy_activity = self._buddy.get_current_activity() + if buddy_activity is not None: + buddy_activity_id = buddy_activity.props.id + else: + buddy_activity_id = None + + if activity is None or \ + activity.get_type() == 'org.laptop.JournalActivity' or \ + activity.get_activity_id() == buddy_activity_id: self._invite_menu.hide() else: title = activity.get_title() diff --git a/src/view/home/FriendsBox.py b/src/view/home/FriendsBox.py index e9efc57..2ab5c04 100644 --- a/src/view/home/FriendsBox.py +++ b/src/view/home/FriendsBox.py @@ -30,7 +30,8 @@ from view.home.spreadlayout import SpreadLayout class FriendsBox(hippo.CanvasBox): __gtype_name__ = 'SugarFriendsBox' def __init__(self, shell): - hippo.CanvasBox.__init__(self, background_color=0xe2e2e2ff) + hippo.CanvasBox.__init__(self, + background_color=style.COLOR_WHITE.get_int()) self._shell = shell self._friends = {} diff --git a/src/view/home/HomeBox.py b/src/view/home/HomeBox.py index d2105fe..c735550 100644 --- a/src/view/home/HomeBox.py +++ b/src/view/home/HomeBox.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-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 @@ -14,92 +14,97 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import os -import logging -import signal from gettext import gettext as _ -import re -import gobject import gtk +import gobject import hippo -import dbus -from hardware import hardwaremanager from sugar.graphics import style -from sugar.graphics.palette import Palette -from sugar.profile import get_profile -from sugar import env +from sugar.graphics import iconentry +from sugar.graphics.radiotoolbutton import RadioToolButton -from view.home.activitiesdonut import ActivitiesDonut -from view.home.MyIcon import MyIcon -from model.shellmodel import ShellModel -from hardware import schoolserver +from view.home.activitiesring import ActivitiesRing +from view.home.activitieslist import ActivitiesList -_logger = logging.getLogger('HomeBox') +_RING_VIEW = 0 +_LIST_VIEW = 1 class HomeBox(hippo.CanvasBox, hippo.CanvasItem): __gtype_name__ = 'SugarHomeBox' def __init__(self, shell): - hippo.CanvasBox.__init__(self, background_color=0xe2e2e2ff) + hippo.CanvasBox.__init__(self) - self._redraw_id = None + self._shell = shell + self._ring_view = None + self._list_view = None + self._enable_xo_palette = False - shell_model = shell.get_model() + 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)) - top_box = hippo.CanvasBox(box_height=style.GRID_CELL_SIZE * 2.5) - self.append(top_box) + self._set_view(_RING_VIEW) - center_box = hippo.CanvasBox(yalign=hippo.ALIGNMENT_CENTER) - self.append(center_box, hippo.PACK_EXPAND) + def __toolbar_view_changed_cb(self, toolbar, view): + self._set_view(view) - bottom_box = hippo.CanvasBox(box_height=style.GRID_CELL_SIZE * 2.5) - self.append(bottom_box) + def _set_view(self, view): + if view == _RING_VIEW: + if self._list_view in self.get_children(): + self.remove(self._list_view) - self._donut = ActivitiesDonut(shell) - center_box.append(self._donut) + if self._ring_view is None: + self._ring_view = ActivitiesRing(self._shell) + if self._enable_xo_palette: + self._ring_view.enable_xo_palette() - self._my_icon = _MyIcon(shell, style.XLARGE_ICON_SIZE) - self.append(self._my_icon, hippo.PACK_FIXED) + self.append(self._ring_view, hippo.PACK_EXPAND) - shell_model.connect('notify::state', - self._shell_state_changed_cb) + elif view == _LIST_VIEW: + if self._ring_view in self.get_children(): + self.remove(self._ring_view) - def _shell_state_changed_cb(self, model, pspec): - # FIXME implement this - if model.props.state == ShellModel.STATE_SHUTDOWN: - pass + if self._list_view is None: + self._list_view = ActivitiesList() - def do_allocate(self, width, height, origin_changed): - hippo.CanvasBox.do_allocate(self, width, height, origin_changed) + self.append(self._list_view, hippo.PACK_EXPAND) + else: + raise ValueError('Invalid view: %r' % view) - [icon_width, icon_height] = self._my_icon.get_allocation() - self.set_position(self._my_icon, (width - icon_width) / 2, - (height - icon_height) / 2) - _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes def resume(self): - if self._redraw_id is None: - self._redraw_id = gobject.timeout_add(self._REDRAW_TIMEOUT, - self._redraw_activity_ring) - self._redraw_activity_ring() + # 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): - if self._redraw_id is not None: - gobject.source_remove(self._redraw_id) - self._redraw_id = None + # 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): - self._donut.redraw() + # TODO: Do we need this? + #self._donut.redraw() return True def has_activities(self): - return self._donut.has_activities() + # TODO: Do we need this? + #return self._donut.has_activities() + return False def enable_xo_palette(self): - self._my_icon.enable_palette() + self._enable_xo_palette = True + if self._ring_view is not None: + self._ring_view.enable_xo_palette() def grab_and_rotate(self): pass @@ -110,143 +115,67 @@ class HomeBox(hippo.CanvasBox, hippo.CanvasItem): def release(self): pass -class _MyIcon(MyIcon): - def __init__(self, shell, scale): - MyIcon.__init__(self, scale) - - self._power_manager = None - self._shell = shell - self._profile = get_profile() - - def enable_palette(self): - palette = Palette(self._profile.nick_name) - - item = gtk.MenuItem(_('Reboot')) - item.connect('activate', self._reboot_activate_cb) - palette.menu.append(item) - item.show() - - item = gtk.MenuItem(_('Shutdown')) - item.connect('activate', self._shutdown_activate_cb) - palette.menu.append(item) - item.show() - - if not self._profile.is_registered(): - item = gtk.MenuItem(_('Register')) - item.connect('activate', self._register_activate_cb) - palette.menu.append(item) - item.show() - - item = gtk.MenuItem(_('About this XO')) - item.connect('activate', self._about_activate_cb) - palette.menu.append(item) - item.show() - - self.set_palette(palette) - - def _reboot_activate_cb(self, menuitem): - model = self._shell.get_model() - model.props.state = ShellModel.STATE_SHUTDOWN - - pm = self._get_power_manager() - - hw_manager = hardwaremanager.get_manager() - hw_manager.shutdown() - - if env.is_emulator(): - self._close_emulator() +class HomeToolbar(gtk.Toolbar): + __gtype_name__ = 'SugarHomeToolbar' + + __gsignals__ = { + 'query-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])), + 'view-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([int])) + } + + def __init__(self): + gtk.Toolbar.__init__(self) + + self._add_separator() + + tool_item = gtk.ToolItem() + self.insert(tool_item, -1) + tool_item.show() + + self._search_entry = iconentry.IconEntry() + self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, + 'system-search') + self._search_entry.add_clear_button() + self._search_entry.set_width_chars(25) + #self._search_entry.connect('activate', self._entry_activated_cb) + #self._search_entry.connect('changed', self._entry_changed_cb) + tool_item.add(self._search_entry) + self._search_entry.show() + + self._add_separator(expand=True) + + ring_button = RadioToolButton(named_icon='view-radial', group=None) + ring_button.props.label = _('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() + + list_button = RadioToolButton(named_icon='view-list') + list_button.props.group = ring_button + list_button.props.label = _('List view') + list_button.props.accelerator = _('<Ctrl>L') + list_button.connect('toggled', self.__view_button_toggled_cb, _LIST_VIEW) + self.insert(list_button, -1) + list_button.show() + + self._add_separator() + + def __view_button_toggled_cb(self, button, view): + if button.props.active: + self.emit('view-changed', view) + + def _add_separator(self, expand=False): + separator = gtk.SeparatorToolItem() + separator.props.draw = False + if expand: + separator.set_expand(True) else: - pm.Reboot() - - def _shutdown_activate_cb(self, menuitem): - model = self._shell.get_model() - model.props.state = ShellModel.STATE_SHUTDOWN - - pm = self._get_power_manager() - - hw_manager = hardwaremanager.get_manager() - hw_manager.shutdown() + separator.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) + self.insert(separator, -1) + separator.show() - if env.is_emulator(): - self._close_emulator() - else: - pm.Shutdown() - - def _register_activate_cb(self, menuitem): - schoolserver.register_laptop() - if self._profile.is_registered(): - self.get_palette().menu.remove(menuitem) - - def _about_activate_cb(self, menuitem): - dialog = gtk.Dialog(_('About this XO'), - self.palette, - gtk.DIALOG_MODAL | - gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_OK, gtk.RESPONSE_OK)) - - not_available = _('Not available') - build = self._read_file('/boot/olpc_build') - if build is None: - build = not_available - label_build = gtk.Label('Build: %s' % build) - label_build.set_alignment(0, 0.5) - label_build.show() - dialog.vbox.pack_start(label_build) - - firmware = self._read_file('/ofw/openprom/model') - if firmware is None: - firmware = not_available - else: - firmware = re.split(" +", firmware) - if len(firmware) == 3: - firmware = firmware[1] - label_firmware = gtk.Label('Firmware: %s' % firmware) - label_firmware.set_alignment(0, 0.5) - label_firmware.show() - dialog.vbox.pack_start(label_firmware) - - serial = self._read_file('/ofw/serial-number') - if serial is None: - serial = not_available - label_serial = gtk.Label('Serial Number: %s' % serial) - label_serial.set_alignment(0, 0.5) - label_serial.show() - dialog.vbox.pack_start(label_serial) - - dialog.set_default_response(gtk.RESPONSE_OK) - dialog.connect('response', self._response_cb) - dialog.show() - - def _read_file(self, path): - if os.access(path, os.R_OK) == 0: - _logger.error('read_file() No such file or directory: %s', path) - return None - - fd = open(path, 'r') - value = fd.read() - fd.close() - if value: - value = value.strip('\n') - return value - else: - _logger.error('read_file() No information in file or directory: %s', path) - return None - - def _response_cb(self, widget, response_id): - if response_id == gtk.RESPONSE_OK: - widget.destroy() - - def _close_emulator(self): - if os.environ.has_key('SUGAR_EMULATOR_PID'): - pid = int(os.environ['SUGAR_EMULATOR_PID']) - os.kill(pid, signal.SIGTERM) - - def _get_power_manager(self): - if self._power_manager is None: - bus = dbus.SystemBus() - proxy = bus.get_object('org.freedesktop.Hal', - '/org/freedesktop/Hal/devices/computer') - self._power_manager = dbus.Interface(proxy, \ - 'org.freedesktop.Hal.Device.SystemPowerManagement') - - return self._power_manager diff --git a/src/view/home/HomeWindow.py b/src/view/home/HomeWindow.py index dbd20c2..cbefb7a 100644 --- a/src/view/home/HomeWindow.py +++ b/src/view/home/HomeWindow.py @@ -36,6 +36,8 @@ class HomeWindow(gtk.Window): def __init__(self, shell): gtk.Window.__init__(self) + self.add_accel_group(gtk.AccelGroup()) + self._shell = shell self._active = False self._level = ShellModel.ZOOM_HOME diff --git a/src/view/home/Makefile.am b/src/view/home/Makefile.am index 6916806..4ae3410 100644 --- a/src/view/home/Makefile.am +++ b/src/view/home/Makefile.am @@ -1,7 +1,8 @@ sugardir = $(pkgdatadir)/shell/view/home sugar_PYTHON = \ __init__.py \ - activitiesdonut.py \ + activitieslist.py \ + activitiesring.py \ FriendView.py \ FriendsBox.py \ HomeBox.py \ diff --git a/src/view/home/MeshBox.py b/src/view/home/MeshBox.py index 3b7c4a7..7d78b41 100644 --- a/src/view/home/MeshBox.py +++ b/src/view/home/MeshBox.py @@ -329,7 +329,7 @@ class ActivityView(hippo.CanvasBox): self._model.activity.props.type.lower() if text_to_check.find(query) == -1: self._icon.props.stroke_color = '#D5D5D5' - self._icon.props.fill_color = '#E5E5E5' + self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg() else: self._icon.props.xo_color = self._model.get_color() @@ -372,24 +372,27 @@ class MeshToolbar(gtk.Toolbar): self._add_separator() tool_item = gtk.ToolItem() - tool_item.set_expand(True) self.insert(tool_item, -1) tool_item.show() self._search_entry = iconentry.IconEntry() self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, 'system-search') self._search_entry.add_clear_button() + self._search_entry.set_width_chars(25) self._search_entry.connect('activate', self._entry_activated_cb) self._search_entry.connect('changed', self._entry_changed_cb) tool_item.add(self._search_entry) self._search_entry.show() - self._add_separator() + self._add_separator(expand=True) - def _add_separator(self): + def _add_separator(self, expand=False): separator = gtk.SeparatorToolItem() - separator.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) separator.props.draw = False + if expand: + separator.set_expand(True) + else: + separator.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) self.insert(separator, -1) separator.show() @@ -435,7 +438,7 @@ class MeshBox(hippo.CanvasBox): self._toolbar.connect('query-changed', self._toolbar_query_changed_cb) self.append(hippo.CanvasWidget(widget=self._toolbar)) - self._layout_box = hippo.CanvasBox(background_color=0xe2e2e2ff) + self._layout_box = hippo.CanvasBox(background_color=style.COLOR_WHITE.get_int()) self.append(self._layout_box, hippo.PACK_EXPAND) self._layout = SpreadLayout() @@ -557,7 +560,7 @@ class MeshBox(hippo.CanvasBox): activity = self._activities[activity_model.get_id()] icon = BuddyIcon(self._shell, buddy_model, - style.SMALL_ICON_SIZE) + style.STANDARD_ICON_SIZE) activity.add_buddy_icon(buddy_model.get_key(), icon) if hasattr(icon, 'set_filter'): diff --git a/src/view/home/activitiesdonut.py b/src/view/home/activitiesdonut.py deleted file mode 100755 index 8e09006..0000000 --- a/src/view/home/activitiesdonut.py +++ /dev/null @@ -1,556 +0,0 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. -# -# 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 colorsys -from gettext import gettext as _ -import logging -import math -import os - -import hippo -import gobject -import gtk - -from sugar.graphics.icon import CanvasIcon -from sugar.graphics.menuitem import MenuItem -from sugar.graphics.palette import Palette -from sugar.graphics import style -from sugar.graphics import xocolor -from sugar import profile -import proc_smaps - -_MAX_ACTIVITIES = 6 -_MIN_WEDGE_SIZE = 1.0 / _MAX_ACTIVITIES -_DONUT_SIZE = style.zoom(450) - -# TODO: rgb_to_html and html_to_rgb are useful elsewhere -# we should put this in a common module -def rgb_to_html(r, g, b): - """ (r, g, b) tuple (in float format) -> #RRGGBB """ - return '#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255)) - -def html_to_rgb(html_color): - """ #RRGGBB -> (r, g, b) tuple (in float format) """ - html_color = html_color.strip() - if html_color[0] == '#': - html_color = html_color[1:] - if len(html_color) != 6: - raise ValueError, "input #%s is not in #RRGGBB format" % html_color - r, g, b = html_color[:2], html_color[2:4], html_color[4:] - r, g, b = [int(n, 16) for n in (r, g, b)] - r, g, b = (r / 255.0, g / 255.0, b / 255.0) - return (r, g, b) - -class ActivityIcon(CanvasIcon): - _INTERVAL = 200 - - __gsignals__ = { - 'resume': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'stop': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) - } - - def __init__(self, activity): - self._orig_color = activity.get_icon_color() - self._icon_colors = self._compute_icon_colors() - - self._direction = 0 - self._level_max = len(self._icon_colors) - 1 - self._level = self._level_max - color = self._icon_colors[self._level] - - CanvasIcon.__init__(self, xo_color=color, cache=True, - size=style.MEDIUM_ICON_SIZE) - - icon_path = activity.get_icon_path() - if icon_path: - self.props.file_name = icon_path - else: - self.props.icon_name = 'image-missing' - - self._activity = activity - self._pulse_id = 0 - - self.size = _MIN_WEDGE_SIZE - - palette = Palette(_('Starting...')) - self.set_palette(palette) - - if activity.props.launching: - self._start_pulsing() - activity.connect('notify::launching', self._launching_changed_cb) - else: - self._setup_palette() - - def _setup_palette(self): - palette = self.get_palette() - - palette.set_primary_text(self._activity.get_title()) - - resume_menu_item = MenuItem(_('Resume'), 'activity-start') - resume_menu_item.connect('activate', self._resume_activate_cb) - palette.menu.append(resume_menu_item) - resume_menu_item.show() - - # FIXME: kludge - if self._activity.get_type() != "org.laptop.JournalActivity": - stop_menu_item = MenuItem(_('Stop'), 'activity-stop') - stop_menu_item.connect('activate', self._stop_activate_cb) - palette.menu.append(stop_menu_item) - stop_menu_item.show() - - def _launching_changed_cb(self, activity, pspec): - if not activity.props.launching: - self._stop_pulsing() - self._setup_palette() - - def __del__(self): - self._cleanup() - - def _cleanup(self): - if self._pulse_id: - gobject.source_remove(self._pulse_id) - self._pulse_id = 0 - - def _compute_icon_colors(self): - _LEVEL_MAX = 1.6 - _LEVEL_STEP = 0.16 - _LEVEL_MIN = 0.0 - icon_colors = {} - level = _LEVEL_MIN - for i in range(0, int(_LEVEL_MAX / _LEVEL_STEP)): - icon_colors[i] = self._get_icon_color_for_level(level) - level += _LEVEL_STEP - return icon_colors - - def _get_icon_color_for_level(self, level): - factor = math.sin(level) - h, s, v = colorsys.rgb_to_hsv(*html_to_rgb(self._orig_color.get_fill_color())) - new_fill = rgb_to_html(*colorsys.hsv_to_rgb(h, s * factor, v)) - h, s, v = colorsys.rgb_to_hsv(*html_to_rgb(self._orig_color.get_stroke_color())) - new_stroke = rgb_to_html(*colorsys.hsv_to_rgb(h, s * factor, v)) - return xocolor.XoColor("%s,%s" % (new_stroke, new_fill)) - - def _pulse_cb(self): - if self._direction == 1: - self._level += 1 - if self._level > self._level_max: - self._direction = 0 - self._level = self._level_max - elif self._direction == 0: - self._level -= 1 - if self._level <= 0: - self._direction = 1 - self._level = 0 - - self.props.xo_color = self._icon_colors[self._level] - self.emit_paint_needed(0, 0, -1, -1) - return True - - def _start_pulsing(self): - if self._pulse_id: - return - - self._pulse_id = gobject.timeout_add(self._INTERVAL, self._pulse_cb) - - def _stop_pulsing(self): - if not self._pulse_id: - return - - self._cleanup() - self._level = 100.0 - self.props.xo_color = self._orig_color - - def _resume_activate_cb(self, menuitem): - self.emit('resume') - - def _stop_activate_cb(self, menuitem): - self.emit('stop') - - def get_activity(self): - return self._activity - -class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem): - __gtype_name__ = 'SugarActivitiesDonut' - def __init__(self, shell, **kwargs): - hippo.CanvasBox.__init__(self, **kwargs) - - self._activities = [] - self._shell = shell - self._angles = [] - self._shell_mappings = proc_smaps.get_shared_mapping_names(os.getpid()) - - self._layout = _Layout() - self.set_layout(self._layout) - - self._model = shell.get_model().get_home() - self._model.connect('activity-added', self._activity_added_cb) - self._model.connect('activity-removed', self._activity_removed_cb) - self._model.connect('pending-activity-changed', self._activity_changed_cb) - - self.connect('button-release-event', self._button_release_event_cb) - - def _get_icon_from_activity(self, activity): - for icon in self._activities: - if icon.get_activity().equals(activity): - return icon - - def _activity_added_cb(self, model, activity): - self._add_activity(activity) - - def _activity_removed_cb(self, model, activity): - self._remove_activity(activity) - - def _activity_changed_cb(self, model, activity): - self.emit_paint_needed(0, 0, -1, -1) - - def _remove_activity(self, activity): - icon = self._get_icon_from_activity(activity) - if icon: - self.remove(icon) - icon._cleanup() - self._activities.remove(icon) - self._compute_angles() - - def _add_activity(self, activity): - icon = ActivityIcon(activity) - icon.connect('resume', self._activity_icon_resumed_cb) - icon.connect('stop', self._activity_icon_stop_cb) - self.append(icon, hippo.PACK_FIXED) - - self._activities.append(icon) - self._compute_angles() - - def _activity_icon_resumed_cb(self, icon): - activity = icon.get_activity() - activity_host = self._shell.get_activity(activity.get_activity_id()) - if activity_host: - activity_host.present() - else: - logging.error("Could not find ActivityHost for activity %s" % - activity.get_activity_id()) - - def _activity_icon_stop_cb(self, icon): - activity = icon.get_activity() - activity_host = self._shell.get_activity(activity.get_activity_id()) - if activity_host: - activity_host.close() - else: - logging.error("Could not find ActivityHost for activity %s" % - activity.get_activity_id()) - - def _get_activity(self, x, y): - # Compute the distance from the center. - [width, height] = self.get_allocation() - x -= width / 2 - y -= height / 2 - r = math.hypot(x, y) - - # Ignore the click if it's not inside the donut - if r < self._get_inner_radius() or r > self._get_radius(): - return None - - # Now figure out where in the donut the click was. - angle = math.atan2(-y, -x) + math.pi - - # Unfortunately, _get_angles() doesn't count from 0 to 2pi, it - # counts from roughly pi/2 to roughly 5pi/2. So we have to - # compare its return values against both angle and angle+2pi - high_angle = angle + 2 * math.pi - - for index, activity in enumerate(self._model): - [angle_start, angle_end] = self._get_angles(index) - if angle_start < angle and angle_end > angle: - return activity - elif angle_start < high_angle and angle_end > high_angle: - return activity - - return None - - def _button_release_event_cb(self, item, event): - activity = self._get_activity(event.x, event.y) - if activity is None: - return False - - activity_host = self._shell.get_activity(activity.get_activity_id()) - if activity_host: - activity_host.present() - return True - - def _set_fixed_arc_size(self): - """Set fixed arc size""" - - n = len(self._activities) - if n > _MAX_ACTIVITIES: - size = 1.0 / n - else: - size = 1.0 / _MAX_ACTIVITIES - - for act in self._activities: - act.size = size - - def _update_activity_sizes(self): - """Currently the size of an activity on the donut does not - represent it's memory usage. This is disabled because it was - either not working perfectly or a little confusing. See #3605""" - self._set_fixed_arc_size() - return - - # Get the memory mappings of each process that hosts an - # activity, and count how many activity instances each - # activity process hosts, and how many processes are mapping - # each shared library, etc - process_mappings = {} - num_activities = {} - num_mappings = {} - unknown_size_activities = 0 - for activity in self._model: - pid = activity.get_pid() - if not pid: - # Still starting up, hasn't opened a window yet - unknown_size_activities += 1 - continue - - if num_activities.has_key(pid): - num_activities[pid] += 1 - continue - - try: - mappings = proc_smaps.get_mappings(pid, self._shell_mappings) - for mapping in mappings: - if mapping.shared > 0: - if num_mappings.has_key(mapping.name): - num_mappings[mapping.name] += 1 - else: - num_mappings[mapping.name] = 1 - process_mappings[pid] = mappings - num_activities[pid] = 1 - except Exception, e: - logging.warn('ActivitiesDonut: could not read /proc/%s/smaps: %r' - % (pid, e)) - - # Compute total memory used per process - process_size = {} - total_activity_size = 0 - for activity in self._model: - pid = activity.get_pid() - if not process_mappings.has_key(pid): - continue - - mappings = process_mappings[pid] - size = 0 - for mapping in mappings: - size += mapping.private - if mapping.shared > 0: - num = num_mappings[mapping.name] - size += mapping.shared / num - process_size[pid] = size - total_activity_size += size / num_activities[pid] - - # Now, see how much free memory is left. - free_memory = 0 - try: - meminfo = open('/proc/meminfo') - for line in meminfo.readlines(): - if line.startswith('MemFree:') or line.startswith('SwapFree:'): - free_memory += int(line[9:-3]) - meminfo.close() - except IOError: - logging.warn('ActivitiesDonut: could not read /proc/meminfo') - except (IndexError, ValueError): - logging.warn('ActivitiesDonut: /proc/meminfo was not in ' + - 'expected format') - - total_memory = float(total_activity_size + free_memory) - - # Each activity has an ideal size of: - # process_size[pid] / num_activities[pid] / total_memory - # (And the free memory wedge is ideally free_memory / - # total_memory) However, no activity wedge is allowed to be - # smaller than _MIN_WEDGE_SIZE. This means the small - # activities will use up extra space, which would make the - # ring overflow. We fix that by reducing the large activities - # and the free space proportionately. If there are activities - # of unknown size, they are simply carved out of the free - # space. - - free_percent = free_memory / total_memory - activity_sizes = [] - overflow = 0.0 - reducible = free_percent - for icon in self._activities: - pid = icon.get_activity().get_pid() - if process_size.has_key(pid): - icon.size = (process_size[pid] / num_activities[pid] / - total_memory) - if icon.size < _MIN_WEDGE_SIZE: - overflow += _MIN_WEDGE_SIZE - icon.size - icon.size = _MIN_WEDGE_SIZE - else: - reducible += icon.size - _MIN_WEDGE_SIZE - else: - icon.size = _MIN_WEDGE_SIZE - - if reducible > 0.0: - reduction = overflow / reducible - if unknown_size_activities > 0: - unknown_percent = _MIN_WEDGE_SIZE * unknown_size_activities - if (free_percent * (1 - reduction) < unknown_percent): - # The free wedge won't be large enough to fit the - # unknown-size activities. So adjust things - overflow += unknown_percent - free_percent - reducible -= free_percent - reduction = overflow / reducible - - if reduction > 0.0: - for icon in self._activities: - if icon.size > _MIN_WEDGE_SIZE: - icon.size -= (icon.size - _MIN_WEDGE_SIZE) * reduction - - def _compute_angles(self): - self._angles = [] - if len(self._activities) == 0: - return - - # Normally we don't _update_activity_sizes() when launching a - # new activity; but if the new wedge would overflow the ring - # then we have no choice. - total = reduce(lambda s1,s2: s1 + s2, - [icon.size for icon in self._activities]) - if total > 1.0: - self._update_activity_sizes() - - # The first wedge (Journal) should be centered at 6 o'clock - size = self._activities[0].size or _MIN_WEDGE_SIZE - angle = (math.pi - size * 2 * math.pi) / 2 - self._angles.append(angle) - - for icon in self._activities: - size = icon.size or _MIN_WEDGE_SIZE - self._angles.append(self._angles[-1] + size * 2 * math.pi) - - def redraw(self): - self._update_activity_sizes() - self._compute_angles() - self.emit_request_changed() - - def _get_angles(self, index): - return [self._angles[index], - self._angles[(index + 1) % len(self._angles)]] - - def _get_radius(self): - [width, height] = self.get_allocation() - return min(width, height) / 2 - - def _get_inner_radius(self): - return self._get_radius() * 0.5 - - def do_paint_below_children(self, cr, damaged_box): - [width, height] = self.get_allocation() - - cr.translate(width / 2, height / 2) - - radius = self._get_radius() - - # Outer Ring - cr.set_source_rgb(0xf1 / 255.0, 0xf1 / 255.0, 0xf1 / 255.0) - cr.arc(0, 0, radius, 0, 2 * math.pi) - cr.fill() - - # Selected Wedge - current_activity = self._model.get_pending_activity() - if current_activity is not None: - selected_index = self._model.index(current_activity) - [angle_start, angle_end] = self._get_angles(selected_index) - - cr.new_path() - cr.move_to(0, 0) - cr.line_to(radius * math.cos(angle_start), - radius * math.sin(angle_start)) - cr.arc(0, 0, radius, angle_start, angle_end) - cr.line_to(0, 0) - cr.set_source_rgb(1, 1, 1) - cr.fill() - - # Edges - if len(self._model): - n_edges = len(self._model) + 1 - else: - n_edges = 0 - - for i in range(0, n_edges): - cr.new_path() - cr.move_to(0, 0) - [angle, unused_angle] = self._get_angles(i) - cr.line_to(radius * math.cos(angle), - radius * math.sin(angle)) - - cr.set_source_rgb(0xe2 / 255.0, 0xe2 / 255.0, 0xe2 / 255.0) - cr.set_line_width(4) - cr.stroke_preserve() - - # Inner Ring - cr.new_path() - cr.arc(0, 0, self._get_inner_radius(), 0, 2 * math.pi) - cr.set_source_rgb(0xe2 / 255.0, 0xe2 / 255.0, 0xe2 / 255.0) - cr.fill() - - def do_allocate(self, width, height, origin_changed): - hippo.CanvasBox.do_allocate(self, width, height, origin_changed) - - radius = (self._get_inner_radius() + self._get_radius()) / 2 - - for i, icon in enumerate(self._activities): - [angle_start, angle_end] = self._get_angles(i) - angle = angle_start + (angle_end - angle_start) / 2 - - [icon_width, icon_height] = icon.get_allocation() - - x = int(radius * math.cos(angle)) - icon_width / 2 - y = int(radius * math.sin(angle)) - icon_height / 2 - - self.set_position(icon, x + width / 2, y + height / 2) - -class _Layout(gobject.GObject,hippo.CanvasLayout): - __gtype_name__ = 'SugarDonutLayout' - def __init__(self): - gobject.GObject.__init__(self) - - def do_set_box(self, box): - self._box = box - - def do_get_height_request(self, for_width): - return _DONUT_SIZE, _DONUT_SIZE - - def do_get_width_request(self): - return _DONUT_SIZE, _DONUT_SIZE - - def do_allocate(self, x, y, width, height, - req_width, req_height, origin_changed): - for child in self._box.get_layout_children(): - min_width, child_width = child.get_width_request() - min_height, child_height = child.get_height_request(child_width) - - [angle_start, angle_end] = self._box._get_angles(i) - angle = angle_start + (angle_end - angle_start) / 2 - - x = int(radius * math.cos(angle)) - icon_width / 2 - y = int(radius * math.sin(angle)) - icon_height / 2 - - child.allocate(x + (width - child_width) / 2, - y + (height - child_height) / 2, - icon_width, icon_height, origin_changed) diff --git a/src/view/home/activitieslist.py b/src/view/home/activitieslist.py new file mode 100644 index 0000000..9adb1de --- /dev/null +++ b/src/view/home/activitieslist.py @@ -0,0 +1,29 @@ +# 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 gtk +import hippo + +_logger = logging.getLogger('ActivitiesList') + +class ActivitiesList(hippo.CanvasBox, hippo.CanvasItem): + __gtype_name__ = 'SugarActivitiesList' + + def __init__(self): + hippo.CanvasBox.__init__(self, background_color=0xe2e2e2ff) + diff --git a/src/view/home/activitiesring.py b/src/view/home/activitiesring.py new file mode 100644 index 0000000..5967623 --- /dev/null +++ b/src/view/home/activitiesring.py @@ -0,0 +1,210 @@ +# Copyright (C) 2006-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 + +import os +import logging +import signal +from gettext import gettext as _ +import re + +import gobject +import gtk +import hippo +import dbus + +from hardware import hardwaremanager +from sugar.graphics import style +from sugar.graphics.palette import Palette +from sugar.profile import get_profile +from sugar import env + +from view.home.MyIcon import MyIcon +from model.shellmodel import ShellModel +from hardware import schoolserver + +_logger = logging.getLogger('ActivitiesRing') + +class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): + __gtype_name__ = 'SugarActivitiesRing' + + def __init__(self, shell): + hippo.CanvasBox.__init__(self, background_color=0xe2e2e2ff) + + shell_model = shell.get_model() + + self._my_icon = _MyIcon(shell, style.XLARGE_ICON_SIZE) + self.append(self._my_icon, hippo.PACK_FIXED) + + shell_model.connect('notify::state', + self._shell_state_changed_cb) + + def _shell_state_changed_cb(self, model, pspec): + # FIXME implement this + 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) + + [icon_width, icon_height] = self._my_icon.get_allocation() + self.set_position(self._my_icon, (width - icon_width) / 2, + (height - icon_height) / 2) + + def enable_xo_palette(self): + self._my_icon.enable_palette() + +class _MyIcon(MyIcon): + def __init__(self, shell, scale): + MyIcon.__init__(self, scale) + + self._power_manager = None + self._shell = shell + self._profile = get_profile() + + def enable_palette(self): + palette = Palette(self._profile.nick_name) + + item = gtk.MenuItem(_('Reboot')) + item.connect('activate', self._reboot_activate_cb) + palette.menu.append(item) + item.show() + + item = gtk.MenuItem(_('Shutdown')) + item.connect('activate', self._shutdown_activate_cb) + palette.menu.append(item) + item.show() + + if not self._profile.is_registered(): + item = gtk.MenuItem(_('Register')) + item.connect('activate', self._register_activate_cb) + palette.menu.append(item) + item.show() + + item = gtk.MenuItem(_('About this XO')) + item.connect('activate', self._about_activate_cb) + palette.menu.append(item) + item.show() + + self.set_palette(palette) + + def _reboot_activate_cb(self, menuitem): + model = self._shell.get_model() + model.props.state = ShellModel.STATE_SHUTDOWN + + pm = self._get_power_manager() + + hw_manager = hardwaremanager.get_manager() + hw_manager.shutdown() + + if env.is_emulator(): + self._close_emulator() + else: + pm.Reboot() + + def _shutdown_activate_cb(self, menuitem): + model = self._shell.get_model() + model.props.state = ShellModel.STATE_SHUTDOWN + + pm = self._get_power_manager() + + hw_manager = hardwaremanager.get_manager() + hw_manager.shutdown() + + if env.is_emulator(): + self._close_emulator() + else: + pm.Shutdown() + + def _register_activate_cb(self, menuitem): + schoolserver.register_laptop() + if self._profile.is_registered(): + self.get_palette().menu.remove(menuitem) + + def _about_activate_cb(self, menuitem): + dialog = gtk.Dialog(_('About this XO'), + self.palette, + gtk.DIALOG_MODAL | + gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK)) + + not_available = _('Not available') + build = self._read_file('/boot/olpc_build') + if build is None: + build = not_available + label_build = gtk.Label('Build: %s' % build) + label_build.set_alignment(0, 0.5) + label_build.show() + dialog.vbox.pack_start(label_build) + + firmware = self._read_file('/ofw/openprom/model') + if firmware is None: + firmware = not_available + else: + firmware = re.split(" +", firmware) + if len(firmware) == 3: + firmware = firmware[1] + label_firmware = gtk.Label('Firmware: %s' % firmware) + label_firmware.set_alignment(0, 0.5) + label_firmware.show() + dialog.vbox.pack_start(label_firmware) + + serial = self._read_file('/ofw/serial-number') + if serial is None: + serial = not_available + label_serial = gtk.Label('Serial Number: %s' % serial) + label_serial.set_alignment(0, 0.5) + label_serial.show() + dialog.vbox.pack_start(label_serial) + + dialog.set_default_response(gtk.RESPONSE_OK) + dialog.connect('response', self._response_cb) + dialog.show() + + def _read_file(self, path): + if os.access(path, os.R_OK) == 0: + _logger.error('read_file() No such file or directory: %s', path) + return None + + fd = open(path, 'r') + value = fd.read() + fd.close() + if value: + value = value.strip('\n') + return value + else: + _logger.error('read_file() No information in file or directory: %s', path) + return None + + def _response_cb(self, widget, response_id): + if response_id == gtk.RESPONSE_OK: + widget.destroy() + + def _close_emulator(self): + if os.environ.has_key('SUGAR_EMULATOR_PID'): + pid = int(os.environ['SUGAR_EMULATOR_PID']) + os.kill(pid, signal.SIGTERM) + + def _get_power_manager(self): + if self._power_manager is None: + bus = dbus.SystemBus() + proxy = bus.get_object('org.freedesktop.Hal', + '/org/freedesktop/Hal/devices/computer') + self._power_manager = dbus.Interface(proxy, \ + 'org.freedesktop.Hal.Device.SystemPowerManagement') + + return self._power_manager + diff --git a/src/view/home/snowflakelayout.py b/src/view/home/snowflakelayout.py index 63705fa..330bf68 100644 --- a/src/view/home/snowflakelayout.py +++ b/src/view/home/snowflakelayout.py @@ -21,7 +21,7 @@ import hippo from sugar.graphics import style -_BASE_DISTANCE = style.zoom(15) +_BASE_DISTANCE = style.zoom(25) _CHILDREN_FACTOR = style.zoom(3) class SnowflakeLayout(gobject.GObject,hippo.CanvasLayout): @@ -76,6 +76,9 @@ class SnowflakeLayout(gobject.GObject,hippo.CanvasLayout): else: angle = 2 * math.pi * index / self._nflakes + if self._nflakes != 2: + angle -= math.pi / 2 + dx = math.cos(angle) * r dy = math.sin(angle) * r diff --git a/src/view/home/transitionbox.py b/src/view/home/transitionbox.py index f1ba4fb..622cd0d 100644 --- a/src/view/home/transitionbox.py +++ b/src/view/home/transitionbox.py @@ -68,7 +68,7 @@ class TransitionBox(hippo.CanvasBox): } def __init__(self): - hippo.CanvasBox.__init__(self, background_color=0xe2e2e2ff) + hippo.CanvasBox.__init__(self, background_color=style.COLOR_WHITE.get_int()) self._size = style.XLARGE_ICON_SIZE |