Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomeu Vizoso <tomeu@tomeuvizoso.net>2008-04-02 13:56:53 (GMT)
committer Tomeu Vizoso <tomeu@tomeuvizoso.net>2008-04-02 13:56:53 (GMT)
commite3a6ece96596d7bf0b61048902676c2e83bc4e55 (patch)
tree5eac695b18f4378b4c76325c3a44c301b7919639
parent39eabae4e2784a1236f2366b6acf923830c97e1c (diff)
Add notifications for activity launch and invitations.
-rw-r--r--src/view/devices/battery.py1
-rw-r--r--src/view/devices/network/mesh.py1
-rw-r--r--src/view/devices/network/wireless.py1
-rw-r--r--src/view/frame/Makefile.am1
-rw-r--r--src/view/frame/activitiestray.py154
-rw-r--r--src/view/frame/clipboardtray.py2
-rw-r--r--src/view/frame/frame.py31
-rw-r--r--src/view/frame/frameinvoker.py1
-rw-r--r--src/view/frame/notification.py107
-rw-r--r--src/view/home/activitiesring.py4
-rw-r--r--src/view/palettes.py10
-rw-r--r--src/view/pulsingicon.py17
12 files changed, 317 insertions, 13 deletions
diff --git a/src/view/devices/battery.py b/src/view/devices/battery.py
index 01f8100..8a4caf0 100644
--- a/src/view/devices/battery.py
+++ b/src/view/devices/battery.py
@@ -38,6 +38,7 @@ class DeviceView(TrayIcon):
self._model = model
self.palette = BatteryPalette(_('My Battery life'))
+ self.set_palette(self.palette)
self.palette.props.invoker = FrameWidgetInvoker(self)
self.palette.set_group_id('frame')
diff --git a/src/view/devices/network/mesh.py b/src/view/devices/network/mesh.py
index 121e56a..bcdc0b2 100644
--- a/src/view/devices/network/mesh.py
+++ b/src/view/devices/network/mesh.py
@@ -36,6 +36,7 @@ class DeviceView(TrayIcon):
self._model = model
self.palette = MeshPalette(_("Mesh Network"), model)
+ self.set_palette(self.palette)
self.palette.props.invoker = FrameWidgetInvoker(self)
self.palette.set_group_id('frame')
diff --git a/src/view/devices/network/wireless.py b/src/view/devices/network/wireless.py
index ff1ba53..6b50769 100644
--- a/src/view/devices/network/wireless.py
+++ b/src/view/devices/network/wireless.py
@@ -46,6 +46,7 @@ class DeviceView(TrayIcon):
self._counter = 0
self.palette = WirelessPalette(self._get_palette_primary_text(), meshdev)
+ self.set_palette(self.palette)
self.palette.props.invoker = FrameWidgetInvoker(self)
self.palette.set_group_id('frame')
self.palette.set_frequency(self._model.props.frequency)
diff --git a/src/view/frame/Makefile.am b/src/view/frame/Makefile.am
index b02085a..3c5dc79 100644
--- a/src/view/frame/Makefile.am
+++ b/src/view/frame/Makefile.am
@@ -9,6 +9,7 @@ sugar_PYTHON = \
friendstray.py \
eventarea.py \
frame.py \
+ notification.py \
overlaybox.py \
framewindow.py \
zoomtoolbar.py
diff --git a/src/view/frame/activitiestray.py b/src/view/frame/activitiestray.py
index e2a448e..4fbd5f6 100644
--- a/src/view/frame/activitiestray.py
+++ b/src/view/frame/activitiestray.py
@@ -1,3 +1,4 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
# Copyright (C) 2008 One Laptop Per Child
#
# This program is free software; you can redistribute it and/or modify
@@ -23,11 +24,18 @@ from sugar.graphics import style
from sugar.graphics.tray import HTray
from sugar.graphics.xocolor import XoColor
from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.icon import Icon
+from sugar.graphics.palette import Palette, WidgetInvoker
+from sugar.graphics.menuitem import MenuItem
+from sugar import activity
from model import shellmodel
from view.palettes import JournalPalette, CurrentActivityPalette
from view.pulsingicon import PulsingIcon
from view.frame.frameinvoker import FrameWidgetInvoker
+from view.frame.notification import NotificationIcon
+import view.frame.frame
class ActivityButton(RadioToolButton):
def __init__(self, home_activity, group):
@@ -48,9 +56,9 @@ class ActivityButton(RadioToolButton):
self._icon.show()
if self._home_activity.get_type() == "org.laptop.JournalActivity":
- palette = JournalPalette(self, self._home_activity)
+ palette = JournalPalette(self._home_activity)
else:
- palette = CurrentActivityPalette(self, self._home_activity)
+ palette = CurrentActivityPalette(self._home_activity)
palette.props.invoker = FrameWidgetInvoker(self)
palette.set_group_id('frame')
self.set_palette(palette)
@@ -59,23 +67,136 @@ class ActivityButton(RadioToolButton):
self._icon.props.pulsing = True
self._notify_launching_hid = home_activity.connect('notify::launching',
self.__notify_launching_cb)
+
+ self._notif_icon = NotificationIcon()
+ self._notif_icon.props.xo_color = home_activity.get_icon_color()
+ if home_activity.get_icon_path():
+ self._notif_icon.props.icon_filename = home_activity.get_icon_path()
+ else:
+ self._notif_icon.props.icon_name = 'image-missing'
+ view.frame.frame.get_instance().add_notification(self._notif_icon)
else:
self._notify_launching_hid = None
+ self._notif_icon = None
def __notify_launching_cb(self, home_activity, pspec):
- self._icon.props.pulsing = False
- home_activity.disconnect(self._notify_launching_hid)
+ if not home_activity.props.launching:
+ if self._notif_icon is not None:
+ frame = view.frame.frame.get_instance()
+ frame.remove_notification(self._notif_icon)
+ self._notif_icon = None
+ self._icon.props.pulsing = False
+ home_activity.disconnect(self._notify_launching_hid)
+
+class InviteButton(ToolButton):
+ def __init__(self, activity_model):
+ ToolButton.__init__(self)
+
+ self._activity_model = activity_model
+
+ self._icon = Icon()
+ self._icon.props.xo_color = activity_model.get_color()
+ if activity_model.get_icon_name():
+ self._icon.props.file = activity_model.get_icon_name()
+ else:
+ self._icon.props.icon_name = 'image-missing'
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ palette = InvitePalette(activity_model)
+ palette.props.invoker = FrameWidgetInvoker(self)
+ palette.set_group_id('frame')
+ self.set_palette(palette)
+
+ self.connect('clicked', self.__clicked_cb)
+ self.connect('destroy', self.__destroy_cb)
+
+ self._notif_icon = NotificationIcon()
+ self._notif_icon.props.xo_color = activity_model.get_color()
+ if activity_model.get_icon_name():
+ self._notif_icon.props.icon_filename = activity_model.get_icon_name()
+ else:
+ self._notif_icon.props.icon_name = 'image-missing'
+ self._notif_icon.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ palette = InvitePalette(activity_model)
+ palette.props.invoker = WidgetInvoker(self._notif_icon)
+ palette.set_group_id('frame')
+ self._notif_icon.palette = palette
+
+ view.frame.frame.get_instance().add_notification(self._notif_icon)
+
+ def __button_release_event_cb(self, icon, event):
+ self.emit('clicked')
+
+ def __clicked_cb(self, button):
+ if self._notif_icon is not None:
+ frame = view.frame.frame.get_instance()
+ frame.remove_notification(self._notif_icon)
+ self._notif_icon = None
+
+ shell = view.Shell.get_instance()
+ shell.join_activity(self._activity_model.get_bundle_id(),
+ self._activity_model.get_id())
+
+ def __destroy_cb(self, button):
+ frame = view.frame.frame.get_instance()
+ frame.remove_notification(self._notif_icon)
+
+class InvitePalette(Palette):
+ def __init__(self, activity_model):
+ self._activity_model = activity_model
+
+ Palette.__init__(self, '')
+
+ registry = activity.get_registry()
+ activity_info = registry.get_activity(activity_model.get_bundle_id())
+ if activity_info:
+ self.set_primary_text(activity_info.name)
+ else:
+ self.set_primary_text(activity_model.get_bundle_id())
+
+ menu_item = MenuItem(_('Join'), icon_name='dialog-ok')
+ menu_item.connect('activate', self.__join_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ menu_item = MenuItem(_('Decline'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__decline_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ def __join_activate_cb(self, menu_item):
+ shell = view.Shell.get_instance()
+ shell.join_activity(self._activity_model.get_bundle_id(),
+ self._activity_model.get_id())
+
+ def __decline_activate_cb(self, menu_item):
+ invites = shellmodel.get_instance().get_invites()
+ for invite in invites:
+ if invite.get_activity_id() == self._activity_model.get_id():
+ invites.remove_invite(invite)
+ return
class ActivitiesTray(HTray):
def __init__(self):
HTray.__init__(self)
self._buttons = {}
+ self._invite_to_item = {}
+
self._home_model = shellmodel.get_instance().get_home()
self._home_model.connect('activity-added', self.__activity_added_cb)
self._home_model.connect('activity-removed', self.__activity_removed_cb)
self._home_model.connect('pending-activity-changed', self.__activity_changed_cb)
+ self._invites = shellmodel.get_instance().get_invites()
+ 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 __activity_added_cb(self, home_model, home_activity):
logging.debug('__activity_added_cb: %r' % home_activity)
if self.get_children():
@@ -103,3 +224,28 @@ class ActivitiesTray(HTray):
def __activity_toggled_cb(self, button, home_activity):
home_activity.get_window().activate(1)
+ def __invite_clicked_cb(self, icon, invite):
+ self._invites.remove_invite(invite)
+
+ def __invite_added_cb(self, invites, invite):
+ self._add_invite(invite)
+
+ def __invite_removed_cb(self, invites, invite):
+ self._remove_invite(invite)
+
+ def _add_invite(self, invite):
+ mesh = shellmodel.get_instance().get_mesh()
+ activity_model = mesh.get_activity(invite.get_activity_id())
+ if activity_model:
+ item = InviteButton(activity_model)
+ item.connect('clicked', self.__invite_clicked_cb, invite)
+ self.add_item(item)
+ item.show()
+
+ self._invite_to_item[invite] = item
+
+ def _remove_invite(self, invite):
+ self.remove_item(self._invite_to_item[invite])
+ self._invite_to_item[invite].destroy()
+ del self._invite_to_item[invite]
+
diff --git a/src/view/frame/clipboardtray.py b/src/view/frame/clipboardtray.py
index e4e7b5f..044aafd 100644
--- a/src/view/frame/clipboardtray.py
+++ b/src/view/frame/clipboardtray.py
@@ -108,7 +108,7 @@ class ClipboardTray(VTray):
icon.show()
self._icons[object_id] = icon
- objects_to_delete = self.get_children()[self.MAX_ITEMS:]
+ objects_to_delete = self.get_children()[:-self.MAX_ITEMS]
for icon in objects_to_delete:
logging.debug('ClipboardTray: deleting surplus object')
cb_service = clipboardservice.get_instance()
diff --git a/src/view/frame/frame.py b/src/view/frame/frame.py
index 0cafeff..4a407b6 100644
--- a/src/view/frame/frame.py
+++ b/src/view/frame/frame.py
@@ -33,9 +33,11 @@ from view.frame.friendstray import FriendsTray
from view.frame.devicestray import DevicesTray
from view.frame.framewindow import FrameWindow
from view.frame.clipboardpanelwindow import ClipboardPanelWindow
+from view.frame.notification import NotificationIcon, NotificationWindow
from model.shellmodel import ShellModel
_FRAME_HIDING_DELAY = 500
+_NOTIFICATION_DURATION = 5000
class _Animation(animator.Animation):
def __init__(self, frame, end):
@@ -121,6 +123,8 @@ class Frame(object):
self._key_listener = _KeyListener(self)
self._mouse_listener = _MouseListener(self)
+ self._notif_by_icon = {}
+
def is_visible(self):
return self.current_position != 0.0
@@ -279,6 +283,33 @@ class Frame(object):
def notify_key_press(self):
self._key_listener.key_press()
+ def add_notification(self, icon):
+ if not isinstance(icon, NotificationIcon):
+ raise TypeError('icon must be a NotificationIcon.')
+
+ window = NotificationWindow()
+ window.move(0, 0)
+ window.add(icon)
+ icon.show()
+ window.show()
+
+ self._notif_by_icon[icon] = window
+
+ gobject.timeout_add(_NOTIFICATION_DURATION,
+ lambda: self.remove_notification(icon))
+
+ def remove_notification(self, icon):
+ if not isinstance(icon, NotificationIcon):
+ raise TypeError('icon must be a NotificationIcon.')
+
+ if icon not in self._notif_by_icon:
+ logging.debug('icon %r not in list of notifications.' % icon)
+ return
+
+ window = self._notif_by_icon[icon]
+ window.destroy()
+ del self._notif_by_icon[icon]
+
visible = property(is_visible, None)
_instance = None
diff --git a/src/view/frame/frameinvoker.py b/src/view/frame/frameinvoker.py
index 607290e..31f5933 100644
--- a/src/view/frame/frameinvoker.py
+++ b/src/view/frame/frameinvoker.py
@@ -18,7 +18,6 @@ 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():
diff --git a/src/view/frame/notification.py b/src/view/frame/notification.py
new file mode 100644
index 0000000..e4f3246
--- /dev/null
+++ b/src/view/frame/notification.py
@@ -0,0 +1,107 @@
+# Copyright (C) 2008 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 math
+
+import gobject
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.style import Color
+from sugar.graphics.xocolor import XoColor
+
+from view.pulsingicon import PulsingIcon
+
+class NotificationIcon(gtk.EventBox):
+ __gtype_name__ = 'SugarNotificationIcon'
+
+ __gproperties__ = {
+ 'xo-color' : (object, None, None, gobject.PARAM_READWRITE),
+ 'icon-name' : (str, None, None, None, gobject.PARAM_READWRITE),
+ 'icon-filename' : (str, None, None, None, gobject.PARAM_READWRITE)
+ }
+
+ _PULSE_TIMEOUT = 3000
+
+ def __init__(self, **kwargs):
+ self._icon = PulsingIcon(pixel_size=style.STANDARD_ICON_SIZE)
+ gobject.GObject.__init__(self, **kwargs)
+ self.props.visible_window = False
+
+ self._icon.props.pulse_color = \
+ XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulsing = True
+ self.add(self._icon)
+ self._icon.show()
+
+ gobject.timeout_add(self._PULSE_TIMEOUT, self.__stop_pulsing_cb)
+
+ self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
+
+ def __stop_pulsing_cb(self):
+ self._icon.props.pulsing = False
+ return False
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'xo-color':
+ if self._icon.props.base_color != value:
+ self._icon.props.base_color = value
+ elif pspec.name == 'icon-name':
+ if self._icon.props.icon_name != value:
+ self._icon.props.icon_name = value
+ elif pspec.name == 'icon-filename':
+ if self._icon.props.file != value:
+ self._icon.props.file = value
+ else:
+ gtk.HBox.do_set_property(self, pspec, value)
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'xo-color':
+ return self._icon.props.base_color
+ elif pspec.name == 'icon-name':
+ return self._icon.props.icon_name
+ elif pspec.name == 'icon-filename':
+ return self._icon.props.file
+ else:
+ return gtk.HBox.do_get_property(self, pspec)
+
+ def _set_palette(self, palette):
+ self._icon.palette = palette
+
+ def _get_palette(self):
+ return self._icon.palette
+
+ palette = property(_get_palette, _set_palette)
+
+class NotificationWindow(gtk.Window):
+ __gtype_name__ = 'SugarNotificationWindow'
+
+ def __init__(self, **kwargs):
+
+ gtk.Window.__init__(self, **kwargs)
+
+ self.set_decorated(False)
+ self.set_resizable(False)
+ self.connect('realize', self._realize_cb)
+
+ def _realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.window.set_accept_focus(False)
+
+ color = gtk.gdk.color_parse(style.COLOR_TOOLBAR_GREY.get_html())
+ self.modify_bg(gtk.STATE_NORMAL, color)
+
diff --git a/src/view/home/activitiesring.py b/src/view/home/activitiesring.py
index 9d84e30..52d3427 100644
--- a/src/view/home/activitiesring.py
+++ b/src/view/home/activitiesring.py
@@ -202,9 +202,9 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
self.props.size = style.STANDARD_ICON_SIZE
if home_activity.get_type() == "org.laptop.JournalActivity":
- palette = JournalPalette(self, home_activity)
+ palette = JournalPalette(home_activity)
else:
- palette = CurrentActivityPalette(self, home_activity)
+ palette = CurrentActivityPalette(home_activity)
self.set_palette(palette)
def __pending_activity_changed_cb(self, home_model, home_activity):
diff --git a/src/view/palettes.py b/src/view/palettes.py
index ff8fad6..1180e95 100644
--- a/src/view/palettes.py
+++ b/src/view/palettes.py
@@ -28,7 +28,7 @@ from sugar.graphics.menuitem import MenuItem
from sugar.graphics.icon import Icon
class BasePalette(Palette):
- def __init__(self, widget, home_activity):
+ def __init__(self, home_activity):
Palette.__init__(self, '', menu_after_content=True)
if home_activity.props.launching:
@@ -45,9 +45,9 @@ class BasePalette(Palette):
raise NotImplementedError
class CurrentActivityPalette(BasePalette):
- def __init__(self, widget, home_activity):
+ def __init__(self, home_activity):
self._home_activity = home_activity
- BasePalette.__init__(self, widget, home_activity)
+ BasePalette.__init__(self, home_activity)
def setup_palette(self):
self.set_primary_text(self._home_activity.get_title())
@@ -89,12 +89,12 @@ class CurrentActivityPalette(BasePalette):
self._home_activity.get_window().close(1)
class JournalPalette(BasePalette):
- def __init__(self, widget, home_activity):
+ def __init__(self, home_activity):
self._home_activity = home_activity
self._progress_bar = None
self._free_space_label = None
- BasePalette.__init__(self, widget, home_activity)
+ BasePalette.__init__(self, home_activity)
def setup_palette(self):
self.set_primary_text(self._home_activity.get_title())
diff --git a/src/view/pulsingicon.py b/src/view/pulsingicon.py
index 6d8e53f..0a4a825 100644
--- a/src/view/pulsingicon.py
+++ b/src/view/pulsingicon.py
@@ -75,6 +75,13 @@ class PulsingIcon(Icon):
Icon.__init__(self, **kwargs)
+ self._palette = None
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette is not None:
+ self._palette.destroy()
+
# Hack for sharing code between CanvasPulsingIcon and PulsingIcon
_get_as_rgba = _get_as_rgba
_update_colors = _update_colors
@@ -136,6 +143,16 @@ class PulsingIcon(Icon):
else:
return Icon.do_get_property(self, pspec)
+ def _get_palette(self):
+ return self._palette
+
+ def _set_palette(self, palette):
+ if self._palette is not None:
+ self._palette.props.invoker = None
+ self._palette = palette
+
+ palette = property(_get_palette, _set_palette)
+
class CanvasPulsingIcon(CanvasIcon):
__gtype_name__ = 'SugarCanvasPulsingIcon'