Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/rpms/sugar/0037-Simple-messages-notification-extension.patch
diff options
context:
space:
mode:
Diffstat (limited to 'rpms/sugar/0037-Simple-messages-notification-extension.patch')
-rw-r--r--rpms/sugar/0037-Simple-messages-notification-extension.patch582
1 files changed, 582 insertions, 0 deletions
diff --git a/rpms/sugar/0037-Simple-messages-notification-extension.patch b/rpms/sugar/0037-Simple-messages-notification-extension.patch
new file mode 100644
index 0000000..2d69945
--- /dev/null
+++ b/rpms/sugar/0037-Simple-messages-notification-extension.patch
@@ -0,0 +1,582 @@
+From 733b96d67567be12e611bf711614e78ffddb66dc Mon Sep 17 00:00:00 2001
+From: Martin Abente <martin.abente.lahaye@gmail.com>
+Date: Wed, 1 Dec 2010 10:27:44 -0300
+Subject: [PATCH sugar 37/74] Simple messages notification extension
+
+Extend jarabe.frame.notification with new graphical
+elements in order to display message notifications.
+These graphical elements were inspired from Gary
+Martin mockups.
+
+Messages notification are accessible through dbus
+see http://library.gnome.org/devel/notification-spec/
+or jarabe.frame.frame.add_message method.
+This implementation only supports icons, summary
+and markup body.
+
+When a message is received:
+
+1. A notification icon will appear and remain
+ as long as the time defined by the caller.
+
+2. A new tray button will be added to the respective
+ tray, this button will remain present until the
+ user reads its content to delete it explicitly.
+
+3. The button constains a message palette that will
+ behave as a messages queue.
+
+Icons-only notications will be accesible and will behave
+as before.
+
+VERSION 2: The messages queue was moved from the corners
+ to the respective trays, in order to mantain
+ the corners available for other usage.
+---
+ src/jarabe/frame/frame.py | 128 ++++++++++++++++++++----
+ src/jarabe/frame/notification.py | 205 ++++++++++++++++++++++++++++++++++---
+ src/jarabe/view/pulsingicon.py | 24 +++++
+ 3 files changed, 322 insertions(+), 35 deletions(-)
+
+diff --git a/src/jarabe/frame/frame.py b/src/jarabe/frame/frame.py
+index 079eeeb..96e848b 100644
+--- a/src/jarabe/frame/frame.py
++++ b/src/jarabe/frame/frame.py
+@@ -19,6 +19,7 @@
+ import gtk
+ import gobject
+ import hippo
++import os
+
+ from sugar.graphics import animator
+ from sugar.graphics import style
+@@ -33,6 +34,7 @@
+ from jarabe.frame.framewindow import FrameWindow
+ from jarabe.frame.clipboardpanelwindow import ClipboardPanelWindow
+ from jarabe.frame.notification import NotificationIcon, NotificationWindow
++from jarabe.frame.notification import NotificationButton, HistoryPalette
+ from jarabe.model import notifications
+
+
+@@ -44,6 +46,8 @@
+ _FRAME_HIDING_DELAY = 500
+ _NOTIFICATION_DURATION = 5000
+
++_DEFAULT_ICON = 'emblem-notification'
++
+
+ class _Animation(animator.Animation):
+ def __init__(self, frame, end):
+@@ -119,6 +123,10 @@ def __init__(self):
+ self._event_area.connect('enter', self._enter_corner_cb)
+ self._event_area.show()
+
++ self._activities_tray = None
++ self._devices_tray = None
++ self._friends_tray = None
++
+ self._top_panel = self._create_top_panel()
+ self._bottom_panel = self._create_bottom_panel()
+ self._left_panel = self._create_left_panel()
+@@ -131,6 +139,7 @@ def __init__(self):
+ self._mouse_listener = _MouseListener(self)
+
+ self._notif_by_icon = {}
++ self._notif_by_message = {}
+
+ notification_service = notifications.get_service()
+ notification_service.notification_received.connect(
+@@ -190,6 +199,7 @@ def _create_top_panel(self):
+ panel.append(hippo.CanvasWidget(widget=activities_tray),
+ hippo.PACK_EXPAND)
+ activities_tray.show()
++ self._activities_tray = activities_tray
+
+ return panel
+
+@@ -201,6 +211,7 @@ def _create_bottom_panel(self):
+ panel.append(hippo.CanvasWidget(widget=devices_tray),
+ hippo.PACK_EXPAND)
+ devices_tray.show()
++ self._devices_tray = devices_tray
+
+ return panel
+
+@@ -210,6 +221,7 @@ def _create_right_panel(self):
+ tray = FriendsTray()
+ panel.append(hippo.CanvasWidget(widget=tray), hippo.PACK_EXPAND)
+ tray.show()
++ self._friends_tray = tray
+
+ return panel
+
+@@ -285,15 +297,7 @@ def _drag_leave_cb(self, window, drag_context, timestamp):
+ def _enter_corner_cb(self, event_area):
+ self._mouse_listener.mouse_enter()
+
+- def notify_key_press(self):
+- self._key_listener.key_press()
+-
+- def add_notification(self, icon, corner=gtk.CORNER_TOP_LEFT,
+- duration=_NOTIFICATION_DURATION):
+-
+- if not isinstance(icon, NotificationIcon):
+- raise TypeError('icon must be a NotificationIcon.')
+-
++ def _create_notification_window(self, corner):
+ window = NotificationWindow()
+
+ screen = gtk.gdk.screen_get_default()
+@@ -309,6 +313,46 @@ def add_notification(self, icon, corner=gtk.CORNER_TOP_LEFT,
+ else:
+ raise ValueError('Inalid corner: %r' % corner)
+
++ return window
++
++ def _add_message_button(self, button, corner):
++ if corner == gtk.CORNER_BOTTOM_RIGHT:
++ self._devices_tray.add_item(button)
++ elif corner == gtk.CORNER_TOP_RIGHT:
++ self._friends_tray.add_item(button)
++ else:
++ self._activities_tray.add_item(button)
++
++ def _remove_message_button(self, button, corner):
++ if corner == gtk.CORNER_BOTTOM_RIGHT:
++ self._devices_tray.remove_item(button)
++ elif corner == gtk.CORNER_TOP_RIGHT:
++ self._friends_tray.remove_item(button)
++ else:
++ self._activities_tray.remove_item(button)
++
++ def _launch_notification_icon(self, icon_name, xo_color,
++ position, duration):
++ icon = NotificationIcon()
++ icon.props.xo_color = xo_color
++
++ if icon_name.startswith(os.sep):
++ icon.props.icon_filename = icon_name
++ else:
++ icon.props.icon_name = icon_name
++
++ self.add_notification(icon, position, duration)
++
++ def notify_key_press(self):
++ self._key_listener.key_press()
++
++ def add_notification(self, icon, corner=gtk.CORNER_TOP_LEFT,
++ duration=_NOTIFICATION_DURATION):
++
++ if not isinstance(icon, NotificationIcon):
++ raise TypeError('icon must be a NotificationIcon.')
++
++ window = self._create_notification_window(corner)
+ window.add(icon)
+ icon.show()
+ window.show()
+@@ -327,28 +371,76 @@ def remove_notification(self, icon):
+ window.destroy()
+ del self._notif_by_icon[icon]
+
++ def add_message(self, body, summary='', icon_name=_DEFAULT_ICON,
++ xo_color=None, corner=gtk.CORNER_TOP_LEFT,
++ duration=_NOTIFICATION_DURATION):
++
++ if xo_color is None:
++ xo_color = profile.get_color()
++
++ button = self._notif_by_message.get(corner, None)
++ if button is None:
++ button = NotificationButton(xo_color)
++ button.show()
++ self._add_message_button(button, corner)
++ self._notif_by_message[corner] = button
++
++ button.start_pulsing()
++
++ palette = button.get_palette()
++ if palette is None:
++ palette = HistoryPalette()
++ palette.set_group_id('frame')
++ palette.connect('clear-messages', self.remove_message, corner)
++ button.set_palette(palette)
++
++ palette.push_message(body, summary, icon_name, xo_color)
++ self._launch_notification_icon(icon_name, xo_color, corner, duration)
++
++
++ def remove_message(self, palette, corner):
++ if corner not in self._notif_by_message:
++ logging.debug('Button %s is not active', str(corner))
++ return
++
++ button = self._notif_by_message[corner]
++ self._remove_message_button(button, corner)
++ del self._notif_by_message[corner]
++
+ def __notification_received_cb(self, **kwargs):
+- logging.debug('__notification_received_cb')
+- icon = NotificationIcon()
++ logging.debug('__notification_received_cb %r', kwargs)
+
+ hints = kwargs['hints']
+
+- icon_file_name = hints.get('x-sugar-icon-file-name', '')
+- if icon_file_name:
+- icon.props.icon_filename = icon_file_name
+- else:
+- icon.props.icon_name = 'application-octet-stream'
++ icon_name = hints.get('x-sugar-icon-file-name', '')
++ if not icon_name:
++ icon_name = _DEFAULT_ICON
+
+ icon_colors = hints.get('x-sugar-icon-colors', '')
+ if not icon_colors:
+ icon_colors = profile.get_color()
+- icon.props.xo_color = icon_colors
+
+ duration = kwargs.get('expire_timeout', -1)
+ if duration == -1:
+ duration = _NOTIFICATION_DURATION
+
+- self.add_notification(icon, gtk.CORNER_TOP_RIGHT, duration)
++ category = hints.get('category', '')
++ if category == 'device':
++ position = gtk.CORNER_BOTTOM_RIGHT
++ elif category == 'presence':
++ position = gtk.CORNER_TOP_RIGHT
++ else:
++ position = gtk.CORNER_TOP_LEFT
++
++ summary = kwargs.get('summary', '')
++ body = kwargs.get('body', '')
++
++ if summary or body:
++ self.add_message(body, summary, icon_name,
++ icon_colors, position, duration)
++ else:
++ self._launch_notification_icon(icon_name, icon_colors,
++ position, duration)
+
+ def __notification_cancelled_cb(self, **kwargs):
+ # Do nothing for now. Our notification UI is so simple, there's no
+diff --git a/src/jarabe/frame/notification.py b/src/jarabe/frame/notification.py
+index 3471e2c..b5724d5 100644
+--- a/src/jarabe/frame/notification.py
++++ b/src/jarabe/frame/notification.py
+@@ -1,4 +1,6 @@
+ # Copyright (C) 2008 One Laptop Per Child
++# Copyright (C) 2010 Martin Abente
++# Copyright (C) 2010 Aleksey Lim
+ #
+ # 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
+@@ -16,13 +18,185 @@
+
+ import gobject
+ import gtk
++import re
++import os
++
++from gettext import gettext as _
+
+ from sugar.graphics import style
+ from sugar.graphics.xocolor import XoColor
++from sugar.graphics.palette import Palette
++from sugar.graphics.menuitem import MenuItem
++from sugar.graphics.toolbutton import ToolButton
++from sugar import profile
+
++from jarabe.frame.frameinvoker import FrameWidgetInvoker
+ from jarabe.view.pulsingicon import PulsingIcon
+
+
++_PULSE_TIMEOUT = 3
++_PULSE_COLOR = XoColor('%s,%s' % \
++ (style.COLOR_BUTTON_GREY.get_svg(), style.COLOR_TRANSPARENT.get_svg()))
++_BODY_FILTERS = "<img.*?/>"
++_NOTIFICATION_ICON = 'emblem-notification'
++
++
++def _create_pulsing_icon(icon_name, xo_color):
++ icon = PulsingIcon(
++ pixel_size=style.STANDARD_ICON_SIZE,
++ pulse_color=_PULSE_COLOR,
++ base_color=xo_color,
++ timeout=_PULSE_TIMEOUT,
++ )
++
++ if icon_name.startswith(os.sep):
++ icon.props.file = icon_name
++ else:
++ icon.props.icon_name = icon_name
++
++ return icon
++
++
++class _HistoryIconWidget(gtk.Alignment):
++ __gtype_name__ = 'SugarHistoryIconWidget'
++
++ def __init__(self, icon_name, xo_color):
++ icon = _create_pulsing_icon(icon_name, xo_color)
++ icon.props.pulsing = True
++
++ gtk.Alignment.__init__(self, xalign=0.5, yalign=0.0)
++ self.props.top_padding = style.DEFAULT_PADDING
++ self.set_size_request(
++ style.GRID_CELL_SIZE - style.FOCUS_LINE_WIDTH * 2,
++ style.GRID_CELL_SIZE - style.DEFAULT_PADDING)
++ self.add(icon)
++
++
++class _HistorySummaryWidget(gtk.Alignment):
++ __gtype_name__ = 'SugarHistorySummaryWidget'
++
++ def __init__(self, summary):
++ summary_label = gtk.Label()
++ summary_label.props.wrap = True
++ summary_label.set_markup(
++ '<b>%s</b>' % gobject.markup_escape_text(summary))
++
++ gtk.Alignment.__init__(self, xalign=0.0, yalign=1.0)
++ self.props.right_padding = style.DEFAULT_SPACING
++ self.add(summary_label)
++
++
++class _HistoryBodyWidget(gtk.Alignment):
++ __gtype_name__ = 'SugarHistoryBodyWidget'
++
++ def __init__(self, body):
++ body_label = gtk.Label()
++ body_label.props.wrap = True
++ body_label.set_markup(body)
++
++ gtk.Alignment.__init__(self, xalign=0, yalign=0.0)
++ self.props.right_padding = style.DEFAULT_SPACING
++ self.add(body_label)
++
++
++class _MessagesHistoryBox(gtk.VBox):
++ __gtype_name__ = 'SugarMessagesHistoryBox'
++
++ def __init__(self):
++ gtk.VBox.__init__(self)
++ self._setup_links_style()
++
++ def _setup_links_style(self):
++ # XXX: find a better way to change style for upstream
++ link_color = profile.get_color().get_fill_color()
++ visited_link_color = profile.get_color().get_stroke_color()
++
++ links_style='''
++ style "label" {
++ GtkLabel::link-color="%s"
++ GtkLabel::visited-link-color="%s"
++ }
++ widget_class "*GtkLabel" style "label"
++ ''' % (link_color, visited_link_color)
++ gtk.rc_parse_string(links_style)
++
++ def push_message(self, body, summary, icon_name, xo_color):
++ entry = gtk.HBox()
++
++ icon_widget = _HistoryIconWidget(icon_name, xo_color)
++ entry.pack_start(icon_widget, False)
++
++ message = gtk.VBox()
++ message.props.border_width = style.DEFAULT_PADDING
++ entry.pack_start(message)
++
++ if summary:
++ summary_widget = _HistorySummaryWidget(summary)
++ message.pack_start(summary_widget, False)
++
++ body = re.sub(_BODY_FILTERS, '', body)
++
++ if body:
++ body_widget = _HistoryBodyWidget(body)
++ message.pack_start(body_widget)
++
++ entry.show_all()
++ self.pack_start(entry)
++ self.reorder_child(entry, 0)
++
++ self_width_, self_height = self.size_request()
++ if (self_height > gtk.gdk.screen_height() / 4 * 3) and \
++ (len(self.get_children()) > 1):
++ self.remove(self.get_children()[-1])
++
++
++class HistoryPalette(Palette):
++ __gtype_name__ = 'SugarHistoryPalette'
++
++ __gsignals__ = {
++ 'clear-messages': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
++ }
++
++ def __init__(self):
++ Palette.__init__(self)
++
++ self.set_accept_focus(False)
++
++ self._messages_box = _MessagesHistoryBox()
++ self._messages_box.show()
++
++ palette_box = self.get_children()[0]
++ primary_box = palette_box.get_children()[0]
++ primary_box.hide()
++ palette_box.add(self._messages_box)
++ palette_box.reorder_child(self._messages_box, 0)
++
++ clear_option = MenuItem(_('Clear history'), 'dialog-cancel')
++ clear_option.connect('activate', self.__clear_messages_cb)
++ clear_option.show()
++
++ self.menu.append(clear_option)
++
++ def __clear_messages_cb(self, clear_option):
++ self.emit('clear-messages')
++
++ def push_message(self, body, summary, icon_name, xo_color):
++ self._messages_box.push_message(body, summary, icon_name, xo_color)
++
++
++class NotificationButton(ToolButton):
++
++ def __init__(self, xo_color):
++ ToolButton.__init__(self)
++ self._icon = _create_pulsing_icon(_NOTIFICATION_ICON, xo_color)
++ self.set_icon_widget(self._icon)
++ self._icon.show()
++ self.set_palette_invoker(FrameWidgetInvoker(self))
++
++ def start_pulsing(self):
++ self._icon.props.pulsing = True
++
++
+ class NotificationIcon(gtk.EventBox):
+ __gtype_name__ = 'SugarNotificationIcon'
+
+@@ -32,28 +206,29 @@ class NotificationIcon(gtk.EventBox):
+ 'icon-filename': (str, None, None, None, gobject.PARAM_READWRITE),
+ }
+
+- _PULSE_TIMEOUT = 3
+-
+ def __init__(self, **kwargs):
+ self._icon = PulsingIcon(pixel_size=style.STANDARD_ICON_SIZE)
+ gobject.GObject.__init__(self, **kwargs)
+ self.props.visible_window = False
++ self.set_app_paintable(True)
+
+- self._icon.props.pulse_color = \
+- XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+- style.COLOR_TRANSPARENT.get_svg()))
+- self._icon.props.pulsing = True
++ color = gtk.gdk.color_parse(style.COLOR_BLACK.get_html())
++ self.modify_bg(gtk.STATE_PRELIGHT, color)
++
++ color = gtk.gdk.color_parse(style.COLOR_BUTTON_GREY.get_html())
++ self.modify_bg(gtk.STATE_ACTIVE, color)
++
++ self._icon.props.pulse_color = _PULSE_COLOR
++ self._icon.props.timeout = _PULSE_TIMEOUT
+ self.add(self._icon)
+ self._icon.show()
+
+- gobject.timeout_add_seconds(self._PULSE_TIMEOUT,
+- self.__stop_pulsing_cb)
++ self.start_pulsing()
+
+ 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 start_pulsing(self):
++ self._icon.props.pulsing = True
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'xo-color':
+@@ -86,17 +261,13 @@ def _get_palette(self):
+ class NotificationWindow(gtk.Window):
+ __gtype_name__ = 'SugarNotificationWindow'
+
+- def __init__(self, **kwargs):
+-
+- gtk.Window.__init__(self, **kwargs)
++ def __init__(self):
++ gtk.Window.__init__(self, gtk.WINDOW_POPUP)
+
+ 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/jarabe/view/pulsingicon.py b/src/jarabe/view/pulsingicon.py
+index 9a98a80..0a97377 100644
+--- a/src/jarabe/view/pulsingicon.py
++++ b/src/jarabe/view/pulsingicon.py
+@@ -88,12 +88,23 @@ def __init__(self, **kwargs):
+ self._pulse_color = None
+ self._paused = False
+ self._pulsing = False
++ self._timeout = 0
++ self._pulsing_sid = None
+
+ Icon.__init__(self, **kwargs)
+
+ self._palette = None
+ self.connect('destroy', self.__destroy_cb)
+
++ def set_timeout(self, timeout):
++ self._timeout = timeout
++
++ def get_timeout(self):
++ return self._timeout
++
++ timeout = gobject.property(
++ type=int, getter=get_timeout, setter=set_timeout)
++
+ def set_pulse_color(self, pulse_color):
+ self._pulse_color = pulse_color
+ self._pulser.update()
+@@ -140,10 +151,20 @@ def get_paused(self):
+ type=bool, default=False, getter=get_paused, setter=set_paused)
+
+ def set_pulsing(self, pulsing):
++ if self._pulsing == pulsing:
++ return
++
++ if self._pulsing_sid is not None:
++ gobject.source_remove(self._pulsing_sid)
++ self._pulsing_sid = None
++
+ self._pulsing = pulsing
+
+ if self._pulsing:
+ self._pulser.start(restart=True)
++ if self.props.timeout > 0:
++ self._pulsing_sid = gobject.timeout_add_seconds(
++ self.props.timeout, self.__timeout_cb)
+ else:
+ self._pulser.stop()
+
+@@ -163,6 +184,9 @@ def _set_palette(self, palette):
+
+ palette = property(_get_palette, _set_palette)
+
++ def __timeout_cb(self):
++ self.props.pulsing = False
++
+ def __destroy_cb(self, icon):
+ self._pulser.stop()
+ if self._palette is not None:
+--
+1.7.6
+