Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSascha Silbe <silbe@activitycentral.com>2011-03-21 11:57:35 (GMT)
committer Sascha Silbe <silbe@activitycentral.com>2011-12-20 13:19:23 (GMT)
commita884783172da16ed1181f3d01dcf3b3311c38c55 (patch)
tree589036f71d8460dc4050e7ae1d2f72d734391e0c
parentf766da335c7d4f43c76ec063362a97ef9b56d504 (diff)
add MultiModeWindow
-rw-r--r--src/sugar/graphics/window.py349
1 files changed, 349 insertions, 0 deletions
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
+