diff options
Diffstat (limited to 'src/sugar/graphics/toolbarbox.py')
-rw-r--r-- | src/sugar/graphics/toolbarbox.py | 404 |
1 files changed, 404 insertions, 0 deletions
diff --git a/src/sugar/graphics/toolbarbox.py b/src/sugar/graphics/toolbarbox.py new file mode 100644 index 0000000..3daca70 --- /dev/null +++ b/src/sugar/graphics/toolbarbox.py @@ -0,0 +1,404 @@ +# Copyright (C) 2009, Aleksey Lim +# +# 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 gtk +import gobject +import logging +from gobject import SIGNAL_RUN_FIRST, TYPE_NONE + +from sugar.graphics import style +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.palette import _PopupAnimation, _PopdownAnimation +from sugar.graphics.palette import MouseSpeedDetector, Invoker +from sugar.graphics import animator +from sugar.graphics import palettegroup + +class ToolbarButton(ToolButton): + def __init__(self, **kwargs): + self._page = None + + ToolButton.__init__(self, **kwargs) + + if self.palette is None: + self.palette = _Palette(self) + + self.connect('clicked', + lambda widget: self.set_expanded(not self.expanded)) + + def get_toolbar(self): + if not hasattr(self.parent, 'owner'): + return None + return self.parent.owner + + toolbar = property(get_toolbar) + + def get_page(self): + return self._page.child.child + + def set_page(self, page): + self._page = _align(_Box, page) + self._page._toolitem = self + page.show() + + page = gobject.property(type=object, getter=get_page, setter=set_page) + + def get_expanded(self): + return bool(self.toolbar) and bool(self._page) and \ + self.toolbar._expanded_page() == self._page + + def set_expanded(self, value): + if not self.toolbar or not self._page or self.get_expanded() == value: + return + + if isinstance(self.palette, _Palette) and self.palette.is_up(): + self.palette.popdown(immediate=True) + + if not value: + self.toolbar._shrink_page(self._page) + return + + expanded = self.toolbar._expanded_page() + if expanded and expanded._toolitem.window: + expanded._toolitem.window.invalidate_rect(None, True) + + if self._page.parent: + self.palette.remove(self._page) + + self.modify_bg(gtk.STATE_NORMAL, self.toolbar._bg) + + self.toolbar._expand_page(self._page) + + expanded = property(get_expanded, set_expanded) + + def do_expose_event(self, event): + if not self.expanded or self.palette and self.palette.is_up(): + ToolButton.do_expose_event(self, event) + if self.palette and self.palette.is_up(): + _paint_arrow(self, event, gtk.ARROW_UP) + else: + _paint_arrow(self, event, gtk.ARROW_DOWN) + return + + alloc = self.allocation + + self.get_style().paint_box(event.window, + gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self, + 'palette-invoker', alloc.x, 0, + alloc.width, alloc.height + style._FOCUS_LINE_WIDTH) + + if self.child.state != gtk.STATE_PRELIGHT: + self.get_style().paint_box(event.window, + gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None, + alloc.x + style._FOCUS_LINE_WIDTH, style._FOCUS_LINE_WIDTH, + alloc.width - style._FOCUS_LINE_WIDTH*2, alloc.height) + + gtk.ToolButton.do_expose_event(self, event) + _paint_arrow(self, event, gtk.ARROW_UP) + +class ToolbarBox(gtk.VBox): + def __init__(self, padding=style.TOOLBOX_HORIZONTAL_PADDING): + gtk.VBox.__init__(self) + + self.__top = gtk.Toolbar() + self.__top.owner = self + + top_widget = _align(gtk.EventBox, self.__top) + self.pack_start(top_widget) + + self.props.padding = padding + self.modify_bg(gtk.STATE_NORMAL, + style.COLOR_TOOLBAR_GREY.get_gdk_color()) + + self.__notebook = gtk.Notebook() + self.__notebook.set_show_border(False) + self.__notebook.set_show_tabs(False) + self.__notebook.show() + + self.__top.connect('remove', self.__remove_cb) + + top = property(lambda self: self.__top) + + def get_padding(self): + return self.__top.parent.props.left_padding + + def set_padding(self, pad): + self.__top.parent.set_padding(0, 0, pad, pad) + + padding = gobject.property(type=object, + getter=get_padding, setter=set_padding) + + def get_subs(self): + out = [] + for i in range(self.top.get_n_items()): + page = self.top.get_nth_item(i) + if isinstance(page, ToolbarButton): + out.append(page) + return out + + subs = property(get_subs) + + def modify_bg(self, state, color): + if state == gtk.STATE_NORMAL: + self._bg = color + self.__top.parent.parent.modify_bg(state, color) + self.__top.modify_bg(state, color) + + def __remove_cb(self, sender, widget): + if not isinstance(widget, ToolbarButton): + return + page_no = self.__notebook.page_num(widget._page) + if page_no != -1: + self.__notebook.remove_page(page_no) + if widget.palette: + widget.palette.popdown(immediate=True) + + def _expanded_page(self): + if self.__notebook.parent is None: + return None + page_no = self.__notebook.get_current_page() + return self.__notebook.get_nth_page(page_no) + + def _shrink_page(self, page): + page_no = self.__notebook.page_num(page) + if page_no == -1: + return + self.__notebook.remove_page(page_no) + self.remove(self.__notebook) + + def _expand_page(self, page): + for i in range(self.__notebook.get_n_pages()): + self.__notebook.remove_page(0) + + _setup_page(page, self._bg, self.props.padding) + self.__notebook.append_page(page) + + if self.__notebook.parent is None: + self.pack_start(self.__notebook) + +class _Box(gtk.EventBox): + def __init__(self): + gtk.EventBox.__init__(self) + self.connect('expose-event', self.do_expose_event) + self.set_app_paintable(True) + + def do_expose_event(self, widget, event): + a = self._toolitem.allocation + self.get_style().paint_box(event.window, + gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self, + 'palette-invoker', -style._FOCUS_LINE_WIDTH, 0, + self.allocation.width + style._FOCUS_LINE_WIDTH*2, + self.allocation.height + style._FOCUS_LINE_WIDTH) + self.get_style().paint_box(event.window, + gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None, + a.x + style._FOCUS_LINE_WIDTH, 0, + a.width - style._FOCUS_LINE_WIDTH*2, style._FOCUS_LINE_WIDTH) + +class _Palette(gtk.Window): + def __init__(self, toolitem, **kwargs): + gobject.GObject.__init__(self, **kwargs) + + self.set_decorated(False) + self.set_resizable(False) + self.set_border_width(0) + + self._toolitem = toolitem + self._invoker = None + self._up = False + self._invoker_hids = [] + self.__focus = 0 + + self._popup_anim = animator.Animator(.5, 10) + self._popup_anim.add(_PopupAnimation(self)) + + self._popdown_anim = animator.Animator(0.6, 10) + self._popdown_anim.add(_PopdownAnimation(self)) + + accel_group = gtk.AccelGroup() + self.set_data('sugar-accel-group', accel_group) + self.add_accel_group(accel_group) + + self.connect('show', self.__show_cb) + self.connect('hide', self.__hide_cb) + self.connect('realize', self.__realize_cb) + self.connect('enter-notify-event', self.__enter_notify_event_cb) + self.connect('leave-notify-event', self.__leave_notify_event_cb) + + self._mouse_detector = MouseSpeedDetector(self, 200, 5) + self._mouse_detector.connect('motion-slow', self._mouse_slow_cb) + + group = palettegroup.get_group('default') + group.connect('popdown', self.__group_popdown_cb) + + def is_up(self): + return self._up + + def get_rect(self): + win_x, win_y = self.window.get_origin() + rectangle = self.get_allocation() + + x = win_x + rectangle.x + y = win_y + rectangle.y + width = rectangle.width + height = rectangle.height + + return gtk.gdk.Rectangle(x, y, width, height) + + def set_invoker(self, invoker): + for hid in self._invoker_hids[:]: + self._invoker.disconnect(hid) + self._invoker_hids.remove(hid) + + self._invoker = invoker + if invoker is not None: + self._invoker_hids.append(self._invoker.connect( + 'mouse-enter', self.__invoker_mouse_enter_cb)) + self._invoker_hids.append(self._invoker.connect( + 'mouse-leave', self.__invoker_mouse_leave_cb)) + self._invoker_hids.append(self._invoker.connect( + 'right-click', self.__invoker_right_click_cb)) + + def get_invoker(self): + return self._invoker + + invoker = gobject.property(type=object, + getter=get_invoker, + setter=set_invoker) + + def do_size_request(self, requisition): + gtk.Window.do_size_request(self, requisition) + if self._toolitem.toolbar: + requisition.width = self._toolitem.toolbar.allocation.width + + def __realize_cb(self, widget): + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + #accept_focus = len(self._content.get_children()) + #if self.window: + # self.window.set_accept_focus(accept_focus) + + def popup(self, immediate=False): + self._popdown_anim.stop() + + toolbar = self._toolitem.toolbar + page = self._toolitem._page + + if not self._invoker or self._toolitem.expanded or not toolbar: + return + + _setup_page(page, style.COLOR_BLACK.get_gdk_color(), + toolbar.props.padding) + if self.child is None: + self.add(page) + + x, y = toolbar.window.get_origin() + self.move(x + toolbar.allocation.x, y + toolbar.top.allocation.height) + self.set_transient_for(self._invoker.get_toplevel()) + + if not immediate: + self._popup_anim.start() + else: + self.show() + + def popdown(self, immediate=False): + self._popup_anim.stop() + self._mouse_detector.stop() + + if not immediate: + self._popdown_anim.start() + else: + self.hide() + + def _mouse_slow_cb(self, widget): + self._mouse_detector.stop() + + if self.is_up(): + self._popdown_anim.stop() + return + + self.popup(immediate=False) + + def __handle_focus(self, delta): + self.__focus += delta + if self.__focus not in (0, 1): + logging.error('_Palette.__focus=%s not in (0, 1)' % self.__focus) + + if self.__focus == 0: + group = palettegroup.get_group('default') + if not group.is_up(): + self.popdown() + + def __group_popdown_cb(self, group): + if self.__focus == 0: + self.popdown(immediate=True) + + def __invoker_mouse_enter_cb(self, invoker): + self._mouse_detector.start() + self.__handle_focus(+1) + + def __invoker_mouse_leave_cb(self, invoker): + self._mouse_detector.stop() + self.__handle_focus(-1) + + def __invoker_right_click_cb(self, invoker): + self.popup(immediate=True) + + def __enter_notify_event_cb(self, widget, event): + if event.detail != gtk.gdk.NOTIFY_INFERIOR and \ + event.mode == gtk.gdk.CROSSING_NORMAL: + self._popdown_anim.stop() + self.__handle_focus(+1) + + def __leave_notify_event_cb(self, widget, event): + if event.detail != gtk.gdk.NOTIFY_INFERIOR and \ + event.mode == gtk.gdk.CROSSING_NORMAL: + self.__handle_focus(-1) + + def __show_cb(self, widget): + self._invoker.notify_popup() + self._up = True + + def __hide_cb(self, widget): + if self._invoker: + self._invoker.notify_popdown() + self._up = False + +def _setup_page(page, color, hpad): + vpad = style._FOCUS_LINE_WIDTH + page.child.set_padding(vpad, vpad, hpad, hpad) + page.child.child.modify_bg(gtk.STATE_NORMAL, color) + page.modify_bg(gtk.STATE_NORMAL, color) + page.modify_bg(gtk.STATE_PRELIGHT, color) + +def _align(box_class, widget): + widget.show() + alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0) + alignment.add(widget) + alignment.show() + box = box_class() + box.modify_bg(gtk.STATE_ACTIVE, style.COLOR_BUTTON_GREY.get_gdk_color()) + box.add(alignment) + box.show() + return box + +def _paint_arrow(widget, event, type): + alloc = widget.allocation + x = alloc.x + alloc.width / 2 - style.TOOLBAR_ARROW_SIZE / 2 + y = alloc.y + alloc.height - int(style.TOOLBAR_ARROW_SIZE * .85) + + widget.get_style().paint_arrow(event.window, + gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, widget, + None, type, True, + x, y, style.TOOLBAR_ARROW_SIZE, style.TOOLBAR_ARROW_SIZE) |