Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/view/frame
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/frame')
-rw-r--r--src/view/frame/Makefile.am14
-rw-r--r--src/view/frame/__init__.py16
-rw-r--r--src/view/frame/activitiestray.py156
-rw-r--r--src/view/frame/activitybutton.py65
-rw-r--r--src/view/frame/clipboardbox.py193
-rw-r--r--src/view/frame/clipboardpanelwindow.py99
-rw-r--r--src/view/frame/eventarea.py106
-rw-r--r--src/view/frame/frame.py272
-rw-r--r--src/view/frame/frameinvoker.py39
-rw-r--r--src/view/frame/framewindow.py104
-rw-r--r--src/view/frame/friendstray.py142
-rw-r--r--src/view/frame/overlaybox.py32
-rw-r--r--src/view/frame/zoomtoolbar.py84
13 files changed, 1322 insertions, 0 deletions
diff --git a/src/view/frame/Makefile.am b/src/view/frame/Makefile.am
new file mode 100644
index 0000000..02951b9
--- /dev/null
+++ b/src/view/frame/Makefile.am
@@ -0,0 +1,14 @@
+sugardir = $(pkgdatadir)/shell/view/frame
+sugar_PYTHON = \
+ __init__.py \
+ activitiestray.py \
+ activitybutton.py \
+ clipboardbox.py \
+ clipboardpanelwindow.py \
+ frameinvoker.py \
+ friendstray.py \
+ eventarea.py \
+ frame.py \
+ overlaybox.py \
+ framewindow.py \
+ zoomtoolbar.py
diff --git a/src/view/frame/__init__.py b/src/view/frame/__init__.py
new file mode 100644
index 0000000..a9dd95a
--- /dev/null
+++ b/src/view/frame/__init__.py
@@ -0,0 +1,16 @@
+# 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
+
diff --git a/src/view/frame/activitiestray.py b/src/view/frame/activitiestray.py
new file mode 100644
index 0000000..3dbf955
--- /dev/null
+++ b/src/view/frame/activitiestray.py
@@ -0,0 +1,156 @@
+# 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 hippo
+import logging
+
+from sugar.graphics.tray import TrayButton
+from sugar.graphics.tray import HTray
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+from sugar import profile
+from sugar import activity
+from sugar import env
+
+from activitybutton import ActivityButton
+
+class InviteButton(TrayButton):
+ def __init__(self, activity_model, invite):
+ TrayButton.__init__(self)
+
+ icon = Icon(file=activity_model.get_icon_name(),
+ xo_color=activity_model.get_color())
+ self.set_icon_widget(icon)
+ icon.show()
+
+ self._invite = invite
+
+ def get_activity_id(self):
+ return self._invite.get_activity_id()
+
+ def get_bundle_id(self):
+ return self._invite.get_bundle_id()
+
+ def get_invite(self):
+ return self._invite
+
+class ActivitiesTray(hippo.CanvasBox):
+ def __init__(self, shell):
+ hippo.CanvasBox.__init__(self, orientation=hippo.ORIENTATION_HORIZONTAL)
+
+ self._shell = shell
+ self._shell_model = self._shell.get_model()
+ self._invite_to_item = {}
+ self._invites = self._shell_model.get_invites()
+ self._config = self._load_config()
+
+ self._tray = HTray()
+ self.append(hippo.CanvasWidget(widget=self._tray), hippo.PACK_EXPAND)
+ self._tray.show()
+
+ registry = activity.get_registry()
+ registry.get_activities_async(reply_handler=self._get_activities_cb)
+
+ registry.connect('activity-added', self._activity_added_cb)
+ registry.connect('activity-removed', self._activity_removed_cb)
+
+ for invite in self._invites:
+ self.add_invite(invite)
+ self._invites.connect('invite-added', self._invite_added_cb)
+ self._invites.connect('invite-removed', self._invite_removed_cb)
+
+ def _load_config(self):
+ config = []
+
+ f = open(env.get_data_path('activities.defaults'), 'r')
+ for line in f.readlines():
+ line = line.strip()
+ if line and not line.startswith('#'):
+ config.append(line)
+ f.close()
+
+ return config
+
+ def _get_activities_cb(self, activity_list):
+ known_activities = []
+ unknown_activities = []
+ name_to_activity = {}
+
+ while activity_list:
+ info = activity_list.pop()
+ name_to_activity[info.bundle_id] = info
+
+ if info.bundle_id in self._config:
+ known_activities.append(info)
+ else:
+ unknown_activities.append(info)
+
+ sorted_activities = []
+ for name in self._config:
+ if name in name_to_activity:
+ sorted_activities.append(name_to_activity[name])
+
+ for info in sorted_activities + unknown_activities:
+ if info.show_launcher:
+ self.add_activity(info)
+
+ def _activity_clicked_cb(self, icon):
+ self._shell.start_activity(icon.get_bundle_id())
+
+ def _invite_clicked_cb(self, icon):
+ self._invites.remove_invite(icon.get_invite())
+ self._shell.join_activity(icon.get_bundle_id(),
+ icon.get_activity_id())
+
+ def _invite_added_cb(self, invites, invite):
+ self.add_invite(invite)
+
+ def _invite_removed_cb(self, invites, invite):
+ self.remove_invite(invite)
+
+ def _remove_activity_cb(self, item):
+ self._tray.remove_item(item)
+
+ def _activity_added_cb(self, activity_registry, activity_info):
+ self.add_activity(activity_info)
+
+ def _activity_removed_cb(self, activity_registry, activity_info):
+ for item in self._tray.get_children():
+ if item.get_bundle_id() == activity_info.bundle_id:
+ self._tray.remove_item(item)
+ return
+
+ def add_activity(self, activity_info):
+ item = ActivityButton(activity_info)
+ item.connect('clicked', self._activity_clicked_cb)
+ item.connect('remove_activity', self._remove_activity_cb)
+ self._tray.add_item(item, -1)
+ item.show()
+
+ def add_invite(self, invite):
+ mesh = self._shell_model.get_mesh()
+ activity_model = mesh.get_activity(invite.get_activity_id())
+ if activity:
+ item = InviteButton(activity_model, invite)
+ item.connect('clicked', self._invite_clicked_cb)
+ self._tray.add_item(item, 0)
+ item.show()
+
+ self._invite_to_item[invite] = item
+
+ def remove_invite(self, invite):
+ self._tray.remove_item(self._invite_to_item[invite])
+ del self._invite_to_item[invite]
diff --git a/src/view/frame/activitybutton.py b/src/view/frame/activitybutton.py
new file mode 100644
index 0000000..0c7c7fb
--- /dev/null
+++ b/src/view/frame/activitybutton.py
@@ -0,0 +1,65 @@
+# 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 gtk
+import os
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics.palette import Palette
+from sugar.graphics.tray import TrayButton
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+
+from view.frame.frameinvoker import FrameWidgetInvoker
+
+class ActivityButton(TrayButton, gobject.GObject):
+ __gtype_name__ = 'SugarActivityButton'
+ __gsignals__ = {
+ 'remove_activity': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+
+ def __init__(self, activity_info):
+ TrayButton.__init__(self)
+
+ icon = Icon(file=activity_info.icon,
+ stroke_color=style.COLOR_WHITE.get_svg(),
+ fill_color=style.COLOR_TRANSPARENT.get_svg())
+ self.set_icon_widget(icon)
+ icon.show()
+
+ self._activity_info = activity_info
+ self.setup_rollover_options()
+
+ def get_bundle_id(self):
+ return self._activity_info.bundle_id
+
+ def setup_rollover_options(self):
+ palette = Palette(self._activity_info.name)
+ self.set_palette(palette)
+ palette.props.invoker = FrameWidgetInvoker(self)
+
+#TODO: Disabled this until later, see #4967
+# if os.path.dirname(self._activity_info.path) == os.path.expanduser('~/Activities'):
+# menu_item = gtk.MenuItem(_('Remove'))
+# menu_item.connect('activate', self.item_remove_cb)
+# palette.menu.append(menu_item)
+# menu_item.show()
+
+ def item_remove_cb(self, widget):
+ self.emit('remove_activity')
diff --git a/src/view/frame/clipboardbox.py b/src/view/frame/clipboardbox.py
new file mode 100644
index 0000000..7702759
--- /dev/null
+++ b/src/view/frame/clipboardbox.py
@@ -0,0 +1,193 @@
+# Copyright (C) 2007, 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 tempfile
+
+import hippo
+import gtk
+
+from sugar import util
+from sugar.clipboard import clipboardservice
+from sugar.graphics.tray import VTray
+from sugar.graphics import style
+
+from view.clipboardicon import ClipboardIcon
+
+class _ContextMap:
+ """Maps a drag context to the clipboard object involved in the dragging."""
+ def __init__(self):
+ self._context_map = {}
+
+ def add_context(self, context, object_id, data_types):
+ """Establishes the mapping. data_types will serve us for reference-
+ counting this mapping.
+ """
+ self._context_map[context] = [object_id, data_types]
+
+ def get_object_id(self, context):
+ """Retrieves the object_id associated with context.
+ Will release the association when this function was called as many times
+ as the number of data_types that this clipboard object contains.
+ """
+ [object_id, data_types_left] = self._context_map[context]
+
+ data_types_left = data_types_left - 1
+ if data_types_left == 0:
+ del self._context_map[context]
+ else:
+ self._context_map[context] = [object_id, data_types_left]
+
+ return object_id
+
+ def has_context(self, context):
+ return context in self._context_map
+
+class ClipboardBox(hippo.CanvasBox):
+
+ MAX_ITEMS = gtk.gdk.screen_height() / style.GRID_CELL_SIZE - 2
+
+ def __init__(self):
+ hippo.CanvasBox.__init__(self)
+ self._icons = {}
+ self._context_map = _ContextMap()
+
+ self._tray = VTray()
+ self.append(hippo.CanvasWidget(widget=self._tray), hippo.PACK_EXPAND)
+ self._tray.show()
+
+ cb_service = clipboardservice.get_instance()
+ cb_service.connect('object-added', self._object_added_cb)
+ cb_service.connect('object-deleted', self._object_deleted_cb)
+
+ def owns_clipboard(self):
+ for icon in self._icons.values():
+ if icon.owns_clipboard:
+ return True
+ return False
+
+ def _add_selection(self, object_id, selection):
+ if not selection.data:
+ return
+
+ logging.debug('ClipboardBox: adding type ' + selection.type)
+
+ cb_service = clipboardservice.get_instance()
+ if selection.type == 'text/uri-list':
+ uris = selection.data.split('\n')
+ if len(uris) > 1:
+ raise NotImplementedError('Multiple uris in text/uri-list still not supported.')
+
+ cb_service.add_object_format(object_id,
+ selection.type,
+ uris[0],
+ on_disk=True)
+ else:
+ cb_service.add_object_format(object_id,
+ selection.type,
+ selection.data,
+ on_disk=False)
+
+ def _object_added_cb(self, cb_service, object_id, name):
+ if self._icons:
+ group = self._icons.values()[0]
+ else:
+ group = None
+
+ icon = ClipboardIcon(object_id, name, group)
+ self._tray.add_item(icon, 0)
+ icon.show()
+ self._icons[object_id] = icon
+
+ objects_to_delete = self._tray.get_children()[ClipboardBox.MAX_ITEMS:]
+ for icon in objects_to_delete:
+ logging.debug('ClipboardBox: deleting surplus object')
+ cb_service = clipboardservice.get_instance()
+ cb_service.delete_object(icon.get_object_id())
+
+ logging.debug('ClipboardBox: ' + object_id + ' was added.')
+
+ def _object_deleted_cb(self, cb_service, object_id):
+ icon = self._icons[object_id]
+ self._tray.remove_item(icon)
+ del self._icons[object_id]
+ logging.debug('ClipboardBox: ' + object_id + ' was deleted.')
+
+ def drag_motion_cb(self, widget, context, x, y, time):
+ logging.debug('ClipboardBox._drag_motion_cb')
+ context.drag_status(gtk.gdk.ACTION_COPY, time)
+ return True;
+
+ def drag_drop_cb(self, widget, context, x, y, time):
+ logging.debug('ClipboardBox._drag_drop_cb')
+ cb_service = clipboardservice.get_instance()
+ object_id = cb_service.add_object(name="")
+
+ self._context_map.add_context(context, object_id, len(context.targets))
+
+ if 'XdndDirectSave0' in context.targets:
+ window = context.source_window
+ prop_type, format, filename = \
+ window.property_get('XdndDirectSave0','text/plain')
+
+ # FIXME query the clipboard service for a filename?
+ base_dir = tempfile.gettempdir()
+ dest_filename = util.unique_id()
+
+ name, dot, extension = filename.rpartition('.')
+ dest_filename += dot + extension
+
+ dest_uri = 'file://' + os.path.join(base_dir, dest_filename)
+
+ window.property_change('XdndDirectSave0', prop_type, format,
+ gtk.gdk.PROP_MODE_REPLACE, dest_uri)
+
+ widget.drag_get_data(context, 'XdndDirectSave0', time)
+ else:
+ for target in context.targets:
+ if str(target) not in ('TIMESTAMP', 'TARGETS', 'MULTIPLE'):
+ widget.drag_get_data(context, target, time)
+
+ cb_service.set_object_percent(object_id, percent=100)
+
+ return True
+
+ def drag_data_received_cb(self, widget, context, x, y, selection, targetType, time):
+ logging.debug('ClipboardBox: got data for target %r' % selection.target)
+
+ object_id = self._context_map.get_object_id(context)
+ try:
+ if selection is None:
+ logging.warn('ClipboardBox: empty selection for target ' + selection.target)
+ elif selection.target == 'XdndDirectSave0':
+ if selection.data == 'S':
+ window = context.source_window
+
+ prop_type, format, dest = \
+ window.property_get('XdndDirectSave0','text/plain')
+
+ clipboard = clipboardservice.get_instance()
+ clipboard.add_object_format(
+ object_id, 'XdndDirectSave0', dest, on_disk=True)
+ else:
+ self._add_selection(object_id, selection)
+
+ finally:
+ # If it's the last target to be processed, finish the dnd transaction
+ if not self._context_map.has_context(context):
+ context.drop_finish(True, gtk.get_current_event_time())
+
diff --git a/src/view/frame/clipboardpanelwindow.py b/src/view/frame/clipboardpanelwindow.py
new file mode 100644
index 0000000..e579b8c
--- /dev/null
+++ b/src/view/frame/clipboardpanelwindow.py
@@ -0,0 +1,99 @@
+# Copyright (C) 2007, 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 urlparse
+
+import gtk
+import hippo
+
+from view.frame.framewindow import FrameWindow
+from view.frame.clipboardbox import ClipboardBox
+from sugar.clipboard import clipboardservice
+from sugar import util
+
+class ClipboardPanelWindow(FrameWindow):
+ def __init__(self, frame, orientation):
+ FrameWindow.__init__(self, orientation)
+
+ self._frame = frame
+
+ # Listening for new clipboard objects
+ # NOTE: we need to keep a reference to gtk.Clipboard in order to keep
+ # listening to it.
+ self._clipboard = gtk.Clipboard()
+ self._clipboard.connect("owner-change", self._owner_change_cb)
+
+ self._clipboard_box = ClipboardBox()
+ self.append(self._clipboard_box, hippo.PACK_EXPAND)
+
+ # Receiving dnd drops
+ self.drag_dest_set(0, [], 0)
+ self.connect("drag_motion", self._clipboard_box.drag_motion_cb)
+ self.connect("drag_drop", self._clipboard_box.drag_drop_cb)
+ self.connect("drag_data_received",
+ self._clipboard_box.drag_data_received_cb)
+
+ def _owner_change_cb(self, clipboard, event):
+ logging.debug("owner_change_cb")
+
+ if self._clipboard_box.owns_clipboard():
+ return
+
+ cb_service = clipboardservice.get_instance()
+ key = cb_service.add_object(name="")
+ cb_service.set_object_percent(key, percent=0)
+
+ targets = clipboard.wait_for_targets()
+ for target in targets:
+ if target not in ('TIMESTAMP', 'TARGETS', 'MULTIPLE', 'SAVE_TARGETS'):
+ logging.debug('Asking for target %s.' % target)
+ selection = clipboard.wait_for_contents(target)
+ if not selection:
+ logging.warning('no data for selection target %s.' % target)
+ continue
+ self._add_selection(key, selection)
+
+ cb_service.set_object_percent(key, percent=100)
+
+ def _add_selection(self, key, selection):
+ if not selection.data:
+ logging.warning('no data for selection target %s.' % selection.type)
+ return
+
+ logging.debug('adding type ' + selection.type + '.')
+
+ cb_service = clipboardservice.get_instance()
+ if selection.type == 'text/uri-list':
+ uris = selection.get_uris()
+
+ if len(uris) > 1:
+ raise NotImplementedError('Multiple uris in text/uri-list still not supported.')
+ uri = uris[0]
+
+ scheme, netloc, path, parameters, query, fragment = urlparse.urlparse(uri)
+ on_disk = (scheme == 'file')
+
+ cb_service.add_object_format(key,
+ selection.type,
+ uri,
+ on_disk)
+ else:
+ cb_service.add_object_format(key,
+ selection.type,
+ selection.data,
+ on_disk=False)
+
diff --git a/src/view/frame/eventarea.py b/src/view/frame/eventarea.py
new file mode 100644
index 0000000..69bb759
--- /dev/null
+++ b/src/view/frame/eventarea.py
@@ -0,0 +1,106 @@
+# Copyright (C) 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 gtk
+import gobject
+import wnck
+
+class EventArea(gobject.GObject):
+ __gsignals__ = {
+ 'enter': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'leave': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._windows = []
+ self._hover = False
+
+ right = gtk.gdk.screen_width() - 1
+ bottom = gtk.gdk.screen_height() -1
+
+ invisible = self._create_invisible(0, 0, 1, 1)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(right, 0, 1, 1)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(0, bottom, 1, 1)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(right, bottom, 1, 1)
+ self._windows.append(invisible)
+
+ screen = wnck.screen_get_default()
+ screen.connect('window-stacking-changed',
+ self._window_stacking_changed_cb)
+
+ def _create_invisible(self, x, y, width, height):
+ invisible = gtk.Invisible()
+ invisible.connect('enter-notify-event', self._enter_notify_cb)
+ invisible.connect('leave-notify-event', self._leave_notify_cb)
+
+ invisible.drag_dest_set(0, [], 0)
+ invisible.connect('drag_motion', self._drag_motion_cb)
+ invisible.connect('drag_leave', self._drag_leave_cb)
+
+ invisible.realize()
+ invisible.window.set_events(gtk.gdk.POINTER_MOTION_MASK |
+ gtk.gdk.ENTER_NOTIFY_MASK |
+ gtk.gdk.LEAVE_NOTIFY_MASK)
+ invisible.window.move_resize(x, y, width, height)
+
+ return invisible
+
+ def _notify_enter(self):
+ if not self._hover:
+ self._hover = True
+ self.emit('enter')
+
+ def _notify_leave(self):
+ if self._hover:
+ self._hover = False
+ self.emit('leave')
+
+ def _enter_notify_cb(self, widget, event):
+ self._notify_enter()
+
+ def _leave_notify_cb(self, widget, event):
+ self._notify_leave()
+
+ def _drag_motion_cb(self, widget, drag_context, x, y, timestamp):
+ drag_context.drag_status(0, timestamp);
+ self._notify_enter()
+ return True
+
+ def _drag_leave_cb(self, widget, drag_context, timestamp):
+ self._notify_leave()
+ return True
+
+ def show(self):
+ for window in self._windows:
+ window.show()
+
+ def hide(self):
+ for window in self._windows:
+ window.hide()
+
+ def _window_stacking_changed_cb(self, screen):
+ for window in self._windows:
+ window.window.raise_()
diff --git a/src/view/frame/frame.py b/src/view/frame/frame.py
new file mode 100644
index 0000000..e8f8fa4
--- /dev/null
+++ b/src/view/frame/frame.py
@@ -0,0 +1,272 @@
+# 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 logging
+
+import gtk
+import gobject
+import hippo
+
+from sugar.graphics import animator
+from sugar.graphics import style
+from sugar.graphics import palettegroup
+from sugar.clipboard import clipboardservice
+
+from view.frame.eventarea import EventArea
+from view.frame.activitiestray import ActivitiesTray
+from view.frame.zoomtoolbar import ZoomToolbar
+from view.frame.friendstray import FriendsTray
+from view.frame.framewindow import FrameWindow
+from view.frame.clipboardpanelwindow import ClipboardPanelWindow
+from model.shellmodel import ShellModel
+
+_FRAME_HIDING_DELAY = 500
+
+class _Animation(animator.Animation):
+ def __init__(self, frame, end):
+ start = frame.current_position
+ animator.Animation.__init__(self, start, end)
+ self._frame = frame
+
+ def next_frame(self, current):
+ self._frame.move(current)
+
+class _MouseListener(object):
+ def __init__(self, frame):
+ self._frame = frame
+ self._hide_sid = 0
+
+ def mouse_enter(self):
+ self._show_frame()
+
+ def mouse_leave(self):
+ if self._frame.mode == Frame.MODE_MOUSE:
+ self._hide_frame()
+
+ def _show_frame(self):
+ if self._hide_sid != 0:
+ gobject.source_remove(self._hide_sid)
+ self._frame.show(Frame.MODE_MOUSE)
+
+ def _hide_frame_timeout_cb(self):
+ self._frame.hide()
+ return False
+
+ def _hide_frame(self):
+ if self._hide_sid != 0:
+ gobject.source_remove(self._hide_sid)
+ self._hide_sid = gobject.timeout_add(
+ _FRAME_HIDING_DELAY, self._hide_frame_timeout_cb)
+
+class _KeyListener(object):
+ def __init__(self, frame):
+ self._frame = frame
+
+ def key_press(self):
+ if self._frame.visible:
+ if self._frame.mode == Frame.MODE_KEYBOARD:
+ self._frame.hide()
+ else:
+ self._frame.show(Frame.MODE_KEYBOARD)
+
+class Frame(object):
+ MODE_MOUSE = 0
+ MODE_KEYBOARD = 1
+ MODE_NON_INTERACTIVE = 2
+
+ def __init__(self, shell):
+ self.mode = None
+
+ self._palette_group = palettegroup.get_group('frame')
+ self._palette_group.connect('popdown', self._palette_group_popdown_cb)
+
+ self._left_panel = None
+ self._right_panel = None
+ self._top_panel = None
+ self._bottom_panel = None
+
+ self._shell = shell
+ self.current_position = 0.0
+ self._animator = None
+
+ self._event_area = EventArea()
+ self._event_area.connect('enter', self._enter_corner_cb)
+ self._event_area.show()
+
+ self._top_panel = self._create_top_panel()
+ self._bottom_panel = self._create_bottom_panel()
+ self._left_panel = self._create_left_panel()
+ self._right_panel = self._create_right_panel()
+
+ screen = gtk.gdk.screen_get_default()
+ screen.connect('size-changed', self._size_changed_cb)
+
+ cb_service = clipboardservice.get_instance()
+ cb_service.connect_after('object-added', self._clipboard_object_added_cb)
+
+ self._key_listener = _KeyListener(self)
+ self._mouse_listener = _MouseListener(self)
+
+ self.move(1.0)
+
+ def is_visible(self):
+ return self.current_position != 0.0
+
+ def hide(self):
+ if self._animator:
+ self._animator.stop()
+
+ self._animator = animator.Animator(0.5)
+ self._animator.add(_Animation(self, 0.0))
+ self._animator.start()
+
+ self._event_area.show()
+
+ self.mode = None
+
+ def show(self, mode):
+ if self.visible:
+ return
+ if self._animator:
+ self._animator.stop()
+
+ self._shell.take_activity_screenshot()
+
+ self.mode = mode
+
+ self._animator = animator.Animator(0.5)
+ self._animator.add(_Animation(self, 1.0))
+ self._animator.start()
+
+ self._event_area.hide()
+
+ def move(self, pos):
+ self.current_position = pos
+ self._update_position()
+
+ def _is_hover(self):
+ return (self._top_panel.hover or \
+ self._bottom_panel.hover or \
+ self._left_panel.hover or \
+ self._right_panel.hover)
+
+ def _create_top_panel(self):
+ panel = self._create_panel(gtk.POS_TOP)
+
+ toolbar = ZoomToolbar(self._shell)
+ panel.append(hippo.CanvasWidget(widget=toolbar))
+ toolbar.show()
+
+ return panel
+
+ def _create_bottom_panel(self):
+ panel = self._create_panel(gtk.POS_BOTTOM)
+
+ box = ActivitiesTray(self._shell)
+ panel.append(box, hippo.PACK_EXPAND)
+
+ return panel
+
+ def _create_right_panel(self):
+ panel = self._create_panel(gtk.POS_RIGHT)
+
+ tray = FriendsTray(self._shell)
+ panel.append(hippo.CanvasWidget(widget=tray), hippo.PACK_EXPAND)
+ tray.show()
+
+ return panel
+
+ def _create_left_panel(self):
+ panel = ClipboardPanelWindow(self, gtk.POS_LEFT)
+
+ self._connect_to_panel(panel)
+ panel.connect('drag-motion', self._drag_motion_cb)
+ panel.connect('drag-leave', self._drag_leave_cb)
+
+ return panel
+
+ def _create_panel(self, orientation):
+ panel = FrameWindow(orientation)
+ self._connect_to_panel(panel)
+
+ return panel
+
+ def _move_panel(self, panel, pos, x1, y1, x2, y2):
+ x = (x2 - x1) * pos + x1
+ y = (y2 - y1) * pos + y1
+
+ panel.move(int(x), int(y))
+
+ # FIXME we should hide and show as necessary to free memory
+ if not panel.props.visible:
+ panel.show()
+
+ def _connect_to_panel(self, panel):
+ panel.connect('enter-notify-event', self._enter_notify_cb)
+ panel.connect('leave-notify-event', self._leave_notify_cb)
+
+ def _update_position(self):
+ screen_h = gtk.gdk.screen_height()
+ screen_w = gtk.gdk.screen_width()
+
+ self._move_panel(self._top_panel, self.current_position,
+ 0, - self._top_panel.size, 0, 0)
+
+ self._move_panel(self._bottom_panel, self.current_position,
+ 0, screen_h, 0, screen_h - self._bottom_panel.size)
+
+ self._move_panel(self._left_panel, self.current_position,
+ - self._left_panel.size, 0, 0, 0)
+
+ self._move_panel(self._right_panel, self.current_position,
+ screen_w, 0, screen_w - self._right_panel.size, 0)
+
+ def _size_changed_cb(self, screen):
+ self._update_position()
+
+ def _clipboard_object_added_cb(self, cb_service, object_id, name):
+ if not self.visible:
+ self.show(self.MODE_NON_INTERACTIVE)
+ gobject.timeout_add(2000, lambda: self.hide())
+
+ def _enter_notify_cb(self, window, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR:
+ self._mouse_listener.mouse_enter()
+
+ def _leave_notify_cb(self, window, event):
+ if event.detail == gtk.gdk.NOTIFY_INFERIOR:
+ return
+
+ if not self._is_hover() and not self._palette_group.is_up():
+ self._mouse_listener.mouse_leave()
+
+ def _palette_group_popdown_cb(self, group):
+ if not self._is_hover():
+ self._mouse_listener.mouse_leave()
+
+ def _drag_motion_cb(self, window, context, x, y, time):
+ self._mouse_listener.mouse_enter()
+
+ def _drag_leave_cb(self, window, drag_context, timestamp):
+ self._mouse_listener.mouse_leave()
+
+ def _enter_corner_cb(self, event_area):
+ self._mouse_listener.mouse_enter()
+
+ def notify_key_press(self):
+ self._key_listener.key_press()
+
+ visible = property(is_visible, None)
diff --git a/src/view/frame/frameinvoker.py b/src/view/frame/frameinvoker.py
new file mode 100644
index 0000000..07dc9d8
--- /dev/null
+++ b/src/view/frame/frameinvoker.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
+#
+# 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
+
+from sugar.graphics import style
+from sugar.graphics.palette import Palette
+from sugar.graphics.palette import CanvasInvoker
+from sugar.graphics.palette import WidgetInvoker
+
+def _get_screen_area():
+ frame_thickness = style.GRID_CELL_SIZE
+
+ x = y = frame_thickness
+ width = gtk.gdk.screen_width() - frame_thickness
+ height = gtk.gdk.screen_height() - frame_thickness
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+class FrameWidgetInvoker(WidgetInvoker):
+ def __init__(self, widget):
+ WidgetInvoker.__init__(self, widget.child)
+
+ self._position_hint = self.ANCHORED
+ self._screen_area = _get_screen_area()
diff --git a/src/view/frame/framewindow.py b/src/view/frame/framewindow.py
new file mode 100644
index 0000000..623d162
--- /dev/null
+++ b/src/view/frame/framewindow.py
@@ -0,0 +1,104 @@
+# 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 gtk
+import hippo
+
+from sugar.graphics import style
+
+class FrameWindow(gtk.Window):
+ __gtype_name__ = 'SugarFrameWindow'
+
+ def __init__(self, position):
+ gtk.Window.__init__(self)
+ self.hover = False
+ self.size = style.GRID_CELL_SIZE + style.LINE_WIDTH
+
+ self._position = position
+
+ self.set_decorated(False)
+ self.connect('realize', self._realize_cb)
+ self.connect('enter-notify-event', self._enter_notify_cb)
+ self.connect('leave-notify-event', self._leave_notify_cb)
+
+ self._canvas = hippo.Canvas()
+ self.add(self._canvas)
+ self._canvas.show()
+
+ box = hippo.CanvasBox()
+ self._canvas.set_root(box)
+
+ padding = style.GRID_CELL_SIZE
+ if self._position == gtk.POS_TOP or self._position == gtk.POS_BOTTOM:
+ box.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ box.props.padding_left = padding
+ box.props.padding_right = padding
+ box.props.padding_top = 0
+ box.props.padding_bottom = 0
+ else:
+ box.props.orientation = hippo.ORIENTATION_VERTICAL
+ box.props.padding_left = 0
+ box.props.padding_right = 0
+ box.props.padding_top = padding
+ box.props.padding_bottom = padding
+
+ self._bg = hippo.CanvasBox(
+ border_color=style.COLOR_BUTTON_GREY.get_int())
+
+ border = style.LINE_WIDTH
+ if position == gtk.POS_TOP:
+ self._bg.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ self._bg.props.border_bottom = border
+ elif position == gtk.POS_BOTTOM:
+ self._bg.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ self._bg.props.border_top = border
+ elif position == gtk.POS_LEFT:
+ self._bg.props.orientation = hippo.ORIENTATION_VERTICAL
+ self._bg.props.border_right = border
+ elif position == gtk.POS_RIGHT:
+ self._bg.props.orientation = hippo.ORIENTATION_VERTICAL
+ self._bg.props.border_left = border
+
+ box.append(self._bg, hippo.PACK_EXPAND)
+
+ self._update_size()
+
+ screen = gtk.gdk.screen_get_default()
+ screen.connect('size-changed', self._size_changed_cb)
+
+ def append(self, child, flags=0):
+ self._bg.append(child, flags)
+
+ def _update_size(self):
+ if self._position == gtk.POS_TOP or self._position == gtk.POS_BOTTOM:
+ self.resize(gtk.gdk.screen_width(), self.size)
+ else:
+ self.resize(self.size, gtk.gdk.screen_height())
+
+ def _realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.window.set_accept_focus(False)
+
+ def _enter_notify_cb(self, window, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR:
+ self.hover = True
+
+ def _leave_notify_cb(self, window, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR:
+ self.hover = False
+
+ def _size_changed_cb(self, screen):
+ self._update_size()
diff --git a/src/view/frame/friendstray.py b/src/view/frame/friendstray.py
new file mode 100644
index 0000000..b34f357
--- /dev/null
+++ b/src/view/frame/friendstray.py
@@ -0,0 +1,142 @@
+# 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 hippo
+
+from sugar.presence import presenceservice
+from sugar.graphics.tray import VTray, TrayIcon
+
+from view.BuddyMenu import BuddyMenu
+from view.frame.frameinvoker import FrameWidgetInvoker
+from model.BuddyModel import BuddyModel
+
+class FriendIcon(TrayIcon):
+ def __init__(self, shell, buddy):
+ TrayIcon.__init__(self, icon_name='computer-xo',
+ xo_color=buddy.get_color())
+
+ palette = BuddyMenu(shell, buddy)
+ self.set_palette(palette)
+ palette.set_group_id('frame')
+ palette.props.invoker = FrameWidgetInvoker(self)
+
+class FriendsTray(VTray):
+ def __init__(self, shell):
+ VTray.__init__(self)
+
+ self._shell = shell
+ self._activity_ps = None
+ self._joined_hid = -1
+ self._left_hid = -1
+ self._buddies = {}
+
+ self._pservice = presenceservice.get_instance()
+ self._pservice.connect('activity-appeared',
+ self.__activity_appeared_cb)
+
+ self._owner = self._pservice.get_owner()
+
+ # Add initial activities the PS knows about
+ self._pservice.get_activities_async(reply_handler=self._get_activities_cb)
+
+ home_model = shell.get_model().get_home()
+ home_model.connect('pending-activity-changed',
+ self._pending_activity_changed_cb)
+
+ def _get_activities_cb(self, list):
+ for activity in list:
+ self.__activity_appeared_cb(self._pservice, activity)
+
+ def add_buddy(self, buddy):
+ if self._buddies.has_key(buddy.props.key):
+ return
+
+ model = BuddyModel(buddy=buddy)
+
+ icon = FriendIcon(self._shell, model)
+ self.add_item(icon)
+ icon.show()
+
+ self._buddies[buddy.props.key] = icon
+
+ def remove_buddy(self, buddy):
+ if not self._buddies.has_key(buddy.props.key):
+ return
+
+ self.remove_item(self._buddies[buddy.props.key])
+ del self._buddies[buddy.props.key]
+
+ def clear(self):
+ for item in self.get_children():
+ self.remove_item(item)
+ self._buddies = {}
+
+ def __activity_appeared_cb(self, pservice, activity_ps):
+ activity = self._shell.get_current_activity()
+ if activity and activity_ps.props.id == activity.get_id():
+ self._set_activity_ps(activity_ps, True)
+
+ def _set_activity_ps(self, activity_ps, shared_activity):
+ if self._activity_ps == activity_ps:
+ return
+
+ if self._joined_hid > 0:
+ self._activity_ps.disconnect(self._joined_hid)
+ self._joined_hid = -1
+ if self._left_hid > 0:
+ self._activity_ps.disconnect(self._left_hid)
+ self._left_hid = -1
+
+ self._activity_ps = activity_ps
+
+ self.clear()
+
+ if shared_activity is True:
+ for buddy in activity_ps.get_joined_buddies():
+ self.add_buddy(buddy)
+
+ self._joined_hid = activity_ps.connect(
+ 'buddy-joined', self.__buddy_joined_cb)
+ self._left_hid = activity_ps.connect(
+ 'buddy-left', self.__buddy_left_cb)
+ else:
+ # only display myself if not shared
+ self.add_buddy(self._owner)
+
+ def _pending_activity_changed_cb(self, home_model, home_activity):
+ if home_activity is None:
+ return
+
+ activity_id = home_activity.get_activity_id()
+ if activity_id is None:
+ return
+
+ # check if activity is shared
+ activity = None
+ for act in self._pservice.get_activities():
+ if activity_id == act.props.id:
+ activity = act
+ break
+ if activity:
+ self._set_activity_ps(activity, True)
+ else:
+ self._set_activity_ps(home_activity, False)
+
+ def __buddy_joined_cb(self, activity, buddy):
+ self.add_buddy(buddy)
+
+ def __buddy_left_cb(self, activity, buddy):
+ self.remove_buddy(buddy)
diff --git a/src/view/frame/overlaybox.py b/src/view/frame/overlaybox.py
new file mode 100644
index 0000000..bb74f18
--- /dev/null
+++ b/src/view/frame/overlaybox.py
@@ -0,0 +1,32 @@
+# 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 hippo
+
+from sugar.graphics.iconbutton import IconButton
+
+class OverlayBox(hippo.CanvasBox):
+ def __init__(self, shell):
+ hippo.CanvasBox.__init__(self, orientation=hippo.ORIENTATION_HORIZONTAL)
+
+ self._shell = shell
+
+ icon = IconButton(icon_name='stock-chat')
+ icon.connect('activated', self._overlay_clicked_cb)
+ self.append(icon)
+
+ def _overlay_clicked_cb(self, item):
+ self._shell.toggle_chat_visibility()
diff --git a/src/view/frame/zoomtoolbar.py b/src/view/frame/zoomtoolbar.py
new file mode 100644
index 0000000..48e63de
--- /dev/null
+++ b/src/view/frame/zoomtoolbar.py
@@ -0,0 +1,84 @@
+# 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
+
+from gettext import gettext as _
+
+import gtk
+
+from sugar.graphics.palette import Palette
+from sugar.graphics.toolbutton import ToolButton
+
+from view.frame.frameinvoker import FrameWidgetInvoker
+from model.shellmodel import ShellModel
+
+class ZoomToolbar(gtk.Toolbar):
+ def __init__(self, shell):
+ gtk.Toolbar.__init__(self)
+
+ self._shell = shell
+
+ self.set_show_arrow(False)
+
+ button = ToolButton(icon_name='zoom-neighborhood')
+ button.connect('clicked',
+ self._level_clicked_cb,
+ ShellModel.ZOOM_MESH)
+ self.insert(button, -1)
+ button.show()
+
+ palette = Palette(_('Neighborhood'))
+ palette.props.invoker = FrameWidgetInvoker(button)
+ palette.set_group_id('frame')
+ button.set_palette(palette)
+
+ button = ToolButton(icon_name='zoom-groups')
+ button.connect('clicked',
+ self._level_clicked_cb,
+ ShellModel.ZOOM_FRIENDS)
+ self.insert(button, -1)
+ button.show()
+
+ palette = Palette(_('Group'))
+ palette.props.invoker = FrameWidgetInvoker(button)
+ palette.set_group_id('frame')
+ button.set_palette(palette)
+
+ button = ToolButton(icon_name='zoom-home')
+ button.connect('clicked',
+ self._level_clicked_cb,
+ ShellModel.ZOOM_HOME)
+ self.insert(button, -1)
+ button.show()
+
+ palette = Palette(_('Home'))
+ palette.props.invoker = FrameWidgetInvoker(button)
+ palette.set_group_id('frame')
+ button.set_palette(palette)
+
+ button = ToolButton(icon_name='zoom-activity')
+ button.connect('clicked',
+ self._level_clicked_cb,
+ ShellModel.ZOOM_ACTIVITY)
+ self.insert(button, -1)
+ button.show()
+
+ palette = Palette(_('Activity'))
+ palette.props.invoker = FrameWidgetInvoker(button)
+ palette.set_group_id('frame')
+ button.set_palette(palette)
+
+ def _level_clicked_cb(self, button, level):
+ self._shell.set_zoom_level(level)