From a884783172da16ed1181f3d01dcf3b3311c38c55 Mon Sep 17 00:00:00 2001 From: Sascha Silbe Date: Mon, 21 Mar 2011 11:57:35 +0000 Subject: add MultiModeWindow --- diff --git a/src/sugar/graphics/window.py b/src/sugar/graphics/window.py index dbac464..506c046 100644 --- a/src/sugar/graphics/window.py +++ b/src/sugar/graphics/window.py @@ -20,12 +20,16 @@ STABLE. """ +import time + import gobject import gtk +import logging import warnings from sugar.graphics.icon import Icon from sugar.graphics import palettegroup +from sugar.logger import trace _UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT = 2 @@ -295,3 +299,348 @@ class Window(gtk.Window): return self._toolbar_box toolbox = property(get_toolbox, set_toolbox) + + +class MultiModeWindow(gtk.Window): + """ + A window that can switch between several independent views (modes) + + A transition effect may be provided when switching between modes. + """ + + _SCROLL_INTERVAL_MS = 40 + _SCROLL_TIME_MS = 500 + _SCROLL_STEPS = _SCROLL_TIME_MS // _SCROLL_INTERVAL_MS + + def __init__(self, modes, **args): + self._enable_fullscreen_mode = True + + gtk.Window.__init__(self, **args) + + self.set_decorated(False) + self.maximize() + self.connect('realize', self.__window_realize_cb) + self.connect('key-press-event', self.__key_press_cb) + + self._modes = modes + self._toolbar_boxes = {} + self._alerts = dict([(mode, []) for mode in modes]) + self._canvasses = {} + self._trays = {} + self.__vboxes = {} + self.__hboxes = {} + self.__event_boxes = {} + self._current_mode = modes[0] + self._next_mode = None + self.__layout = gtk.Layout() + self.__layout.show() + self.__width = 0 + self.__height = 0 + self.__scroll_step = 0 + self.__scroll_direction = 1 + self.__scroll_start_time = None + + for mode in modes: + self.__vboxes[mode] = gtk.VBox() + self.__hboxes[mode] = gtk.HBox() + self.__vboxes[mode].pack_start(self.__hboxes[mode]) + self.__hboxes[mode].show() + + event_box = gtk.EventBox() + self.__event_boxes[mode] = event_box + self.__hboxes[mode].pack_start(event_box) + event_box.show() + event_box.add_events(gtk.gdk.POINTER_MOTION_HINT_MASK + | gtk.gdk.POINTER_MOTION_MASK) + event_box.connect('motion-notify-event', self.__motion_notify_cb) + + self.__vboxes[mode].show() + + self.connect('configure-event', self.__configure_cb) + self.add(self.__vboxes[self._current_mode]) + + self._is_fullscreen = False + self._unfullscreen_button = UnfullscreenButton() + self._unfullscreen_button.set_transient_for(self) + self._unfullscreen_button.connect_button_press( + self.__unfullscreen_button_pressed) + self._unfullscreen_button_timeout_id = None + + @trace() + def switch_to_mode(self, mode): + if mode == self._current_mode: + return + + if False: # TODO: config option for disabling scrolling + self.remove(self.__vboxes[self._current_mode]) + self._current_mode = mode + self.add(self.__vboxes[mode]) + return + + if self.child != self.__layout: + # work around a race condition on start-up + self.remove(self.__vboxes[self._current_mode]) + self.__layout.add(self.__vboxes[self._current_mode]) + self.add(self.__layout) + + if self._next_mode is not None: + self._switch_mode_now() + + self._next_mode = mode + self.__scroll_step = 0 + self.__scroll_start_time = time.time() + if self._modes.index(self._current_mode) < self._modes.index(mode): + self.__scroll_direction = 1 + else: + self.__scroll_direction = -1 + + self.__vboxes[mode].hide() + self.__layout.put(self.__vboxes[mode], + self.__width * self.__scroll_direction, 0) + self.__vboxes[mode].resize_children() + self.__vboxes[mode].show() + gobject.timeout_add(MultiModeWindow._SCROLL_INTERVAL_MS, + self.__scroll_cb) + + @trace() + def reveal(self): + """ + Make window active. + + In contrast with present(), brings window to the top + even after invoking on response on non-gtk events. + See SL#1423. + """ + if self.window is None: + self.show() + return + timestamp = gtk.get_current_event_time() + if not timestamp: + timestamp = gtk.gdk.x11_get_server_time(self.window) + self.window.focus(timestamp) + + def fullscreen(self): + palettegroup.popdown_all() + if self._current_mode in self._toolbar_boxes: + self._toolbar_boxes[self._current_mode].hide() + if self._current_mode in self._trays: + self._trays[self._current_mode].hide() + + self._is_fullscreen = True + + if self.props.enable_fullscreen_mode: + self._unfullscreen_button.show() + + if self._unfullscreen_button_timeout_id is not None: + gobject.source_remove(self._unfullscreen_button_timeout_id) + self._unfullscreen_button_timeout_id = None + + self._unfullscreen_button_timeout_id = \ + gobject.timeout_add_seconds( \ + _UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT, \ + self.__unfullscreen_button_timeout_cb) + + def unfullscreen(self): + if self._current_mode in self._toolbar_boxes: + self._toolbar_boxes[self._current_mode].show() + if self._current_mode in self._trays: + self._trays[self._current_mode].show() + + self._is_fullscreen = False + + if self.props.enable_fullscreen_mode: + self._unfullscreen_button.hide() + + if self._unfullscreen_button_timeout_id: + gobject.source_remove(self._unfullscreen_button_timeout_id) + self._unfullscreen_button_timeout_id = None + + @trace() + def get_canvas(self, mode): + return self._canvas.get(mode) + + @trace() + def set_canvas(self, mode, canvas): + if mode in self._canvasses: + old_canvas = self._canvasses.pop(self._current_mode) + self.__event_boxes[mode].remove(old_canvas) + + if not canvas: + return + + self.__event_boxes[mode].add(canvas) + self._canvasses[mode] = canvas + self.__vboxes[mode].set_focus_child(canvas) + + @trace() + def get_toolbar_box(self, mode): + return self._toolbar_boxes.get(mode) + + @trace() + def set_toolbar_box(self, mode, toolbar_box): + if mode in self._toolbar_boxes: + self.__vboxes[mode].remove(self._toolbar_boxes.pop(mode)) + + if not toolbar_box: + return + + self.__vboxes[mode].pack_start(toolbar_box, False) + self.__vboxes[mode].reorder_child(toolbar_box, 0) + self._toolbar_boxes[mode] = toolbar_box + + @trace() + def get_tray(self, mode): + return self._trays.get(mode) + + @trace() + def set_tray(self, mode, tray, position): + if mode in self._trays: + old_tray = self._trays.pop(mode) + box = old_tray.get_parent() + box.remove(old_tray) + + if position == gtk.POS_LEFT: + self.__hboxes[mode].pack_start(tray, False) + elif position == gtk.POS_RIGHT: + self.__hboxes[mode].pack_end(tray, False) + elif position == gtk.POS_BOTTOM: + self.__vboxes[mode].pack_end(tray, False) + + self._trays[mode] = tray + + def add_alert(self, mode, alert): + self._alerts[mode].append(alert) + if len(self._alerts) > 1: + return + + self.__vboxes[mode].pack_start(alert, False) + if mode in self._toolbar_boxes: + self.__vboxes[mode].reorder_child(alert, 1) + else: + self.__vboxes[mode].reorder_child(alert, 0) + + def remove_alert(self, mode, alert): + if alert not in self._alerts[mode]: + return + + self._alerts[mode].remove(alert) + if alert.get_parent() is None: + return + + # the alert is the visible one on top of the queue + self.__vboxes[mode].remove(alert) + if not self._alerts[mode]: + return + + self.__vboxes[mode].pack_start(self._alerts[mode][0], False) + if mode in self._toolbar_boxes: + self.__vboxes[mode].reorder_child(self._alerts[mode][0], 1) + else: + self.__vboxes[mode].reorder_child(self._alerts[mode][0], 0) + + @trace() + def __window_realize_cb(self, window): + group = gtk.Window() + group.realize() + window.window.set_group(group.window) + + def __key_press_cb(self, widget, event): + key = gtk.gdk.keyval_name(event.keyval) + if event.state & gtk.gdk.MOD1_MASK: + if self._current_mode in self._trays and key == 'space': + tray = self._trays[self._current_mode] + tray.props.visible = not tray.props.visible + return True + + elif key == 'Escape' and self._is_fullscreen and \ + self.props.enable_fullscreen_mode: + self.unfullscreen() + return True + + return False + + def __unfullscreen_button_pressed(self, widget, event): + self.unfullscreen() + + def __motion_notify_cb(self, widget, event): + if self._is_fullscreen and self.props.enable_fullscreen_mode: + if not self._unfullscreen_button.props.visible: + self._unfullscreen_button.show() + else: + # Reset the timer + if self._unfullscreen_button_timeout_id is not None: + gobject.source_remove(self._unfullscreen_button_timeout_id) + self._unfullscreen_button_timeout_id = None + + self._unfullscreen_button_timeout_id = \ + gobject.timeout_add_seconds( \ + _UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT, \ + self.__unfullscreen_button_timeout_cb) + return False + + def __unfullscreen_button_timeout_cb(self): + self._unfullscreen_button.hide() + self._unfullscreen_button_timeout_id = None + return False + + def set_enable_fullscreen_mode(self, enable_fullscreen_mode): + self._enable_fullscreen_mode = enable_fullscreen_mode + + def get_enable_fullscreen_mode(self): + return self._enable_fullscreen_mode + + enable_fullscreen_mode = gobject.property(type=object, + setter=set_enable_fullscreen_mode, getter=get_enable_fullscreen_mode) + + @trace() + def __configure_cb(self, widget_, event): + if self.child != self.__layout: + # work around a race condition on start-up + self.remove(self.__vboxes[self._current_mode]) + self.__layout.add(self.__vboxes[self._current_mode]) + self.add(self.__layout) + + if self.__width == event.width and self.__height == event.height: + return + + self.__width, self.__height = event.width, event.height + for vbox in self.__vboxes.values(): + vbox.set_size_request(self.__width, self.__height) + + @trace() + def __scroll_cb(self): + self.__scroll_step += 1 + + # skip frames if we're too slow + elapsed_time = time.time() - self.__scroll_start_time + acceptable_time_per_step = MultiModeWindow._SCROLL_INTERVAL_MS * 1.1 + while elapsed_time > self.__scroll_step * acceptable_time_per_step \ + and self.__scroll_step < MultiModeWindow._SCROLL_STEPS: + logging.debug('MultiModeWindow: Too slow, skipping frame!') + self.__scroll_step += 1 + + if self.__scroll_step >= MultiModeWindow._SCROLL_STEPS: + self._switch_mode_now() + return False + + pixel_per_step = self.__width // MultiModeWindow._SCROLL_STEPS + scroll_pixels = self.__scroll_step * pixel_per_step + if self.__scroll_direction == 1: + self.__layout.move(self.__vboxes[self._current_mode], + -scroll_pixels, 0) + self.__layout.move(self.__vboxes[self._next_mode], + self.__width - scroll_pixels, 0) + else: + self.__layout.move(self.__vboxes[self._current_mode], + scroll_pixels, 0) + self.__layout.move(self.__vboxes[self._next_mode], + scroll_pixels - self.__width, 0) + return True + + @trace() + def _switch_mode_now(self): + self.__layout.remove(self.__vboxes[self._current_mode]) + self.__layout.move(self.__vboxes[self._next_mode], 0, 0) + self._current_mode = self._next_mode + self._next_mode = None + -- cgit v0.9.1