From f3cf928f318a4bd1fe30267949fd234f3ff5bde9 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Tue, 20 Feb 2007 16:38:25 +0100 Subject: [PATCH] Added Rollover control. --- shell/view/Shell.py | 6 ++ shell/view/frame/ActivitiesBox.py | 25 +++----- shell/view/frame/ZoomBox.py | 55 ++++++++-------- shell/view/frame/frame.py | 14 +++++ sugar/graphics/button.py | 100 +++++++++++++++++++++++++----- sugar/graphics/optionmenu.py | 9 ++- sugar/graphics/rollover.py | 84 +++++++++++++++++++++++++ sugar/graphics/rollovercontext.py | 44 +++++++++++++ 8 files changed, 272 insertions(+), 65 deletions(-) create mode 100644 sugar/graphics/rollover.py create mode 100644 sugar/graphics/rollovercontext.py diff --git a/shell/view/Shell.py b/shell/view/Shell.py index 8d0fd493..a7acde9a 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -21,6 +21,7 @@ import wnck from view.home.HomeWindow import HomeWindow from sugar.presence import PresenceService +from sugar.graphics.rollovercontext import RolloverContext from view.ActivityHost import ActivityHost from sugar.activity import ActivityFactory from view.frame.frame import Frame @@ -54,6 +55,8 @@ class Shell(gobject.GObject): home_model.connect('active-activity-changed', self._active_activity_changed_cb) + self._rollover_context = RolloverContext() + self._frame = Frame(self) self._frame.show_and_hide(3) @@ -99,6 +102,9 @@ class Shell(gobject.GObject): def get_frame(self): return self._frame + def get_rollover_context(self): + return self._rollover_context + def _join_success_cb(self, handler, activity, activity_ps, activity_id, activity_type): logging.debug("Joining activity %s (%s)" % (activity_id, activity_type)) activity.join(activity_ps.object_path()) diff --git a/shell/view/frame/ActivitiesBox.py b/shell/view/frame/ActivitiesBox.py index d63eee6a..29ca0c72 100644 --- a/shell/view/frame/ActivitiesBox.py +++ b/shell/view/frame/ActivitiesBox.py @@ -19,24 +19,16 @@ import logging from sugar.graphics import units from sugar.graphics.iconcolor import IconColor -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.button import Button from sugar.presence import PresenceService from sugar import profile -class ActivityItem(CanvasIcon): +class ActivityButton(Button): def __init__(self, activity): - icon_name = activity.get_icon() - CanvasIcon.__init__(self, icon_name=icon_name, - box_width=units.grid_to_pixels(1), - box_height=units.grid_to_pixels(1), - color=IconColor('white')) + Button.__init__(self, icon_name=activity.get_icon()) self._activity = activity - self._normal_color = self.get_property('color') - self._prelight_color = profile.get_color() - self.connect('motion-notify-event', self._mouse_motion_event_cb) - def _mouse_motion_event_cb(self, item, event): if event.detail == hippo.MOTION_DETAIL_ENTER: self.set_property('color', self._prelight_color) @@ -46,14 +38,11 @@ class ActivityItem(CanvasIcon): def get_bundle_id(self): return self._activity.get_service_name() -class InviteItem(CanvasIcon): +class InviteButton(Button): def __init__(self, activity, invite): - CanvasIcon.__init__(self, icon_name=activity.get_icon(), - box_width=units.grid_to_pixels(1), - box_height=units.grid_to_pixels(1)) + Button.__init__(self, icon_name=activity.get_icon()) self.props.color = activity.get_color() - self._invite = invite def get_activity_id(self): @@ -104,7 +93,7 @@ class ActivitiesBox(hippo.CanvasBox): self.add_activity(bundle) def add_activity(self, activity): - item = ActivityItem(activity) + item = ActivityButton(activity) item.connect('activated', self._activity_clicked_cb) self.append(item, 0) @@ -112,7 +101,7 @@ class ActivitiesBox(hippo.CanvasBox): mesh = self._shell_model.get_mesh() activity = mesh.get_activity(invite.get_activity_id()) if activity: - item = InviteItem(activity, invite) + item = InviteButton(activity, invite) item.connect('activated', self._invite_clicked_cb) self.append(item, 0) diff --git a/shell/view/frame/ZoomBox.py b/shell/view/frame/ZoomBox.py index 6f061b3d..1c35f6e1 100644 --- a/shell/view/frame/ZoomBox.py +++ b/shell/view/frame/ZoomBox.py @@ -15,65 +15,62 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import logging +from gettext import gettext as _ import hippo -from sugar.graphics.canvasicon import CanvasIcon +from sugar.graphics.rollover import Rollover from sugar.graphics.menuicon import MenuIcon from sugar.graphics.menu import Menu from sugar.graphics.iconcolor import IconColor from sugar.graphics.button import Button import sugar -class ActivityMenu(Menu): +class ActivityRollover(Rollover): ACTION_SHARE = 1 ACTION_CLOSE = 2 def __init__(self, activity_model): - Menu.__init__(self, activity_model.get_title()) + Rollover.__init__(self, activity_model.get_title()) if not activity_model.get_shared(): - self._add_mesh_action() + self.add_item(ActivityRollover.ACTION_SHARE, _('Share'), + 'theme:stock-share-mesh') - self._add_close_action() + self.add_item(ActivityRollover.ACTION_CLOSE, _('Close'), + 'theme:stock-close') - def _add_mesh_action(self): - icon = CanvasIcon(icon_name='theme:stock-share-mesh', - color=IconColor('#ffffff,#000000')) - self.add_action(icon, ActivityMenu.ACTION_SHARE) - - def _add_close_action(self): - icon = CanvasIcon(icon_name='theme:stock-close', - color=IconColor('#ffffff,#000000')) - self.add_action(icon, ActivityMenu.ACTION_CLOSE) - -class ActivityIcon(MenuIcon): - def __init__(self, shell, menu_shell, activity_model): +class ActivityButton(Button): + def __init__(self, shell, activity_model): self._shell = shell self._activity_model = activity_model icon_name = self._activity_model.get_icon_name() icon_color = self._activity_model.get_icon_color() - MenuIcon.__init__(self, menu_shell, icon_name=icon_name, - color=icon_color) + Button.__init__(self, icon_name=icon_name, color=icon_color) - def create_menu(self): - menu = ActivityMenu(self._activity_model) - menu.connect('action', self._action_cb) - return menu - - def _action_cb(self, menu, action): - self.popdown() + def get_rollover(self): + rollover = ActivityRollover(self._activity_model) + #rollover.connect('action', self._action_cb) + return rollover + + def get_rollover_context(self): + return self._shell.get_rollover_context() + + def _action_cb(self, menu, data): + [action_id, label] = data + # TODO: Wouldn't be better to share/close the activity associated with + # this button instead of asking for the current activity? activity = self._shell.get_current_activity() if activity == None: logging.error('No active activity.') return - if action == ActivityMenu.ACTION_SHARE: + if action_id == ActivityRollover.ACTION_SHARE: activity.share() - if action == ActivityMenu.ACTION_CLOSE: + elif action_id == ActivityRollover.ACTION_CLOSE: activity.close() class ZoomBox(hippo.CanvasBox): @@ -110,7 +107,7 @@ class ZoomBox(hippo.CanvasBox): self.remove(self._activity_icon) if home_activity: - icon = ActivityIcon(self._shell, self._menu_shell, home_activity) + icon = ActivityButton(self._shell, home_activity) self.append(icon) self._activity_icon = icon else: diff --git a/shell/view/frame/frame.py b/shell/view/frame/frame.py index 37b3cc07..4aab4ef2 100644 --- a/shell/view/frame/frame.py +++ b/shell/view/frame/frame.py @@ -70,6 +70,12 @@ class Frame: shell.get_model().connect('notify::state', self._shell_state_changed_cb) + rollover_context = shell.get_rollover_context() + rollover_context.connect('activated', + self._rollover_context_activated_cb) + rollover_context.connect('deactivated', + self._rollover_context_deactivated_cb) + def _create_top_panel(self): panel = self._create_panel(hippo.ORIENTATION_HORIZONTAL) menu_shell = panel.get_menu_shell() @@ -165,6 +171,13 @@ class Frame: if self._mode != Frame.STICKY and not self._hover_frame: self._timeline.play('before_slide_out', 'slide_out') + def _rollover_context_activated_cb(self, rollover_context): + self._timeline.goto('slide_in', True) + + def _rollover_context_deactivated_cb(self, rollover_context): + if self._mode != Frame.STICKY and not self._hover_frame: + self._timeline.play('before_slide_out', 'slide_out') + def _enter_notify_cb(self, window, event): self._enter_notify() logging.debug('Frame._enter_notify_cb ' + str(self._mode)) @@ -193,6 +206,7 @@ class Frame: def _leave_notify(self, panel): self._hover_frame = False if not panel.get_menu_shell().is_active() and \ + not self._shell.get_rollover_context().is_active() and \ (self._mode == Frame.HIDE_ON_LEAVE or \ self._mode == Frame.AUTOMATIC): self._timeline.play('before_slide_out', 'slide_out') diff --git a/sugar/graphics/button.py b/sugar/graphics/button.py index a6bacbf7..068ce7cc 100644 --- a/sugar/graphics/button.py +++ b/sugar/graphics/button.py @@ -23,6 +23,7 @@ import hippo from canvasicon import CanvasIcon from iconcolor import IconColor from sugar.graphics import units +from sugar.graphics.timeline import Timeline from sugar import profile STANDARD_SIZE = 0 @@ -41,15 +42,20 @@ class Button(hippo.CanvasBox): gobject.PARAM_READWRITE) } - def __init__(self, icon_name): - self._normal_color = IconColor('white') + def __init__(self, icon_name, color=None): + if color: + self._normal_color = color + else: + self._normal_color = IconColor('white') + self._prelight_color = profile.get_color() self._inactive_color = IconColor('#808080,#424242') self._active = True + self._rollover = None + self._hover_rollover = False self._icon = CanvasIcon(icon_name=icon_name, cache=True, color=self._normal_color) - self._connect_signals(self._icon) hippo.CanvasBox.__init__(self) @@ -57,6 +63,81 @@ class Button(hippo.CanvasBox): self.append(self._icon, hippo.PACK_EXPAND) + self._timeline = Timeline(self) + self._timeline.add_tag('popup', 6, 6) + self._timeline.add_tag('before_popdown', 7, 7) + self._timeline.add_tag('popdown', 8, 8) + + self.connect('motion-notify-event', self._motion_notify_event_cb) + self.connect('button-press-event', self._button_press_event_cb) + + def get_rollover(self): + return self._rollover + + def get_rollover_context(self): + return None + + def do_popup(self, current, n_frames): + if self._rollover: + return + + rollover = self.get_rollover() + if not rollover: + return + + rollover_context = self.get_rollover_context() + if rollover_context: + rollover_context.popped_up(rollover) + + rollover.connect('motion-notify-event', + self._rollover_motion_notify_event_cb) + rollover.connect('action-completed', + self._rollover_action_completed_cb) + + context = self._icon.get_context() + #[x, y] = context.translate_to_screen(self._icon) + [x, y] = context.translate_to_widget(self._icon) + + # TODO: Any better place to do this? + rollover.props.box_width = max(rollover.props.box_width, + self.get_width_request()) + + [width, height] = self._icon.get_allocation() + rollover.popup(x, y + height) + + self._rollover = rollover + + def do_popdown(self, current, frame): + if self._rollover: + self._rollover.popdown() + + rollover_context = self.get_rollover_context() + if rollover_context: + rollover_context.popped_down(self._rollover) + + self._rollover = None + + def popdown(self): + self._timeline.play('popdown', 'popdown') + + def _motion_notify_event_cb(self, button, event): + if event.detail == hippo.MOTION_DETAIL_ENTER: + self._timeline.play(None, 'popup') + elif event.detail == hippo.MOTION_DETAIL_LEAVE: + if not self._hover_rollover: + self._timeline.play('before_popdown', 'popdown') + + def _rollover_motion_notify_event_cb(self, rollover, event): + if event.detail == hippo.MOTION_DETAIL_ENTER: + self._hover_rollover = True + self._timeline.play('popup', 'popup') + elif event.detail == hippo.MOTION_DETAIL_LEAVE: + self._hover_rollover = False + self._timeline.play('popdown', 'popdown') + + def _rollover_action_completed_cb(self, rollover): + self.popdown() + def _set_size(self, size): if size == SMALL_SIZE: self.props.box_width = -1 @@ -93,17 +174,6 @@ class Button(hippo.CanvasBox): else: return hippo.CanvasBox.get_property(self, pspec) - def _connect_signals(self, item): - item.connect('button-release-event', self._button_release_event_cb) - # TODO: Prelighting is disabled by now. Need to figure how we want it to behave. - #item.connect('motion-notify-event', self._motion_notify_event_cb) - - def _button_release_event_cb(self, widget, event): + def _button_press_event_cb(self, widget, event): if self._active: self.emit_activated() - - def _motion_notify_event_cb(self, widget, event): - if self._active and event.detail == hippo.MOTION_DETAIL_ENTER: - self._icon.props.color = self._prelight_color - elif self._active and event.detail == hippo.MOTION_DETAIL_LEAVE: - self._icon.props.color = self._normal_color diff --git a/sugar/graphics/optionmenu.py b/sugar/graphics/optionmenu.py index d38971d3..3ee8ac25 100644 --- a/sugar/graphics/optionmenu.py +++ b/sugar/graphics/optionmenu.py @@ -60,7 +60,7 @@ class Menu(hippo.CanvasBox, hippo.CanvasItem): canvas_text.props.font_desc = font.DEFAULT.get_pango_desc() box.append(canvas_text) - box.connect('button-press-event', self._button_press_event_cb, + box.connect('button-press-event', self._item_button_press_event_cb, [action_id, label]) self.append(box) @@ -82,7 +82,7 @@ class Menu(hippo.CanvasBox, hippo.CanvasItem): self._window.destroy() self._window = None - def _button_press_event_cb(self, item, event, data): + def _item_button_press_event_cb(self, item, event, data): self.emit('action', data) self.hide() @@ -152,8 +152,11 @@ class OptionMenu(hippo.CanvasBox, hippo.CanvasItem): context = self._round_box.get_context() #[x, y] = context.translate_to_screen(self._round_box) [x, y] = context.translate_to_widget(self._round_box) - [width, height] = self._round_box.get_allocation() + + # TODO: Any better place to do this? self._menu.props.box_width = self.get_width_request() + + [width, height] = self._round_box.get_allocation() self._menu.show(x, y + height) def _menu_action_cb(self, menu, data): diff --git a/sugar/graphics/rollover.py b/sugar/graphics/rollover.py new file mode 100644 index 00000000..a90397d8 --- /dev/null +++ b/sugar/graphics/rollover.py @@ -0,0 +1,84 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +import sys +import logging + +import gobject +import gtk +import hippo + +from sugar.graphics import style +from sugar.graphics.roundbox import RoundBox +from sugar.graphics import button +from sugar.graphics import color +from sugar.graphics import font +from sugar.graphics.canvasicon import CanvasIcon + +class Rollover(hippo.CanvasBox, hippo.CanvasItem): + __gtype_name__ = 'SugarRollover' + + __gsignals__ = { + 'action-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) + } + + def __init__(self, title): + hippo.CanvasBox.__init__(self) + self.props.background_color = color.MENU_BACKGROUND.get_int() + self.props.border_color = color.MENU_BORDER.get_int() + #TODO: how we should specify the border thickness? + self.props.border = style.separator_thickness + self._window = None + + def add_item(self, action_id, label, icon_name=None, icon_color=None): + box = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL) + box.props.padding = 5 + box.props.spacing = 5 + if icon_name: + icon = CanvasIcon(icon_name=icon_name, scale=style.small_icon_scale) + if icon_color: + icon.props.color = icon_color + box.append(icon) + + canvas_text = hippo.CanvasText() + canvas_text.props.text = label + canvas_text.props.color = color.LABEL_TEXT.get_int() + canvas_text.props.font_desc = font.DEFAULT.get_pango_desc() + box.append(canvas_text) + + box.connect('button-press-event', self._item_button_press_event_cb) + self.append(box) + + def add_separator(self): + box = hippo.CanvasBox() + box.props.background_color = color.MENU_SEPARATOR.get_int() + box.props.box_height = style.separator_thickness + self.append(box) + + def popup(self, x, y): + if not self._window: + self._window = hippo.CanvasWindow(gtk.WINDOW_POPUP) + self._window.move(x, y) + self._window.set_root(self) + self._window.show() + + def popdown(self): + if self._window: + self._window.destroy() + self._window = None + + def _item_button_press_event_cb(self, item, event): + self.emit('action-completed') diff --git a/sugar/graphics/rollovercontext.py b/sugar/graphics/rollovercontext.py new file mode 100644 index 00000000..c84ee5bf --- /dev/null +++ b/sugar/graphics/rollovercontext.py @@ -0,0 +1,44 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +import gobject +import hippo + +class RolloverContext(gobject.GObject): + __gtype_name__ = 'SugarRolloverContext' + + __gsignals__ = { + 'activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'deactivated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) + } + + def __init__(self): + self._active_control = None + gobject.GObject.__init__(self) + + def popped_up(self, control): + if self._active_control: + self._active_control.popdown() + self._active_control = control + self.emit('activated') + + def popped_down(self, control): + if self._active_control == control: + self._active_control = None + self.emit('deactivated') + + def is_active(self): + return self._active_control != None