diff options
Diffstat (limited to 'rpms/sugar/0037-Simple-messages-notification-extension.patch')
-rw-r--r-- | rpms/sugar/0037-Simple-messages-notification-extension.patch | 582 |
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 + |