Ticket #1447: 0001-Added-Close-button-to-activity-launcher-rewrote-lay.patch

File 0001-Added-Close-button-to-activity-launcher-rewrote-lay.patch, 14.2 KB (added by wadeb, 15 years ago)

New version of the Sugar patch

  • src/jarabe/model/shell.py

    From 7cc4ffe504bb93c4864814673fa48ce8986d90b9 Mon Sep 17 00:00:00 2001
    From: Wade Brainerd <wadetb@gmail.com>
    Date: Fri, 30 Oct 2009 22:00:40 -0400
    Subject: [PATCH] Added Close button to activity launcher; rewrote layout to accomodate complex centering.
    
    Delay between icon pulses, so the user can count pulses and for a possible performance gain.
    ---
     src/jarabe/model/shell.py   |   18 +++-
     src/jarabe/view/launcher.py |  257 ++++++++++++++++++++++++++++++++-----------
     src/jarabe/view/service.py  |    5 +
     3 files changed, 216 insertions(+), 64 deletions(-)
    
    diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py
    index de5a66f..153e64d 100644
    a b class ShellModel(gobject.GObject): 
    572572                home_activity.get_type())
    573573            home_activity.props.launching = False
    574574            self._remove_activity(home_activity)
     575            self.emit('launch-failed', home_activity)
    575576        else:
    576577            logging.error('Model for activity id %s does not exist.',
    577578                activity_id)
    578579
    579         self.emit('launch-failed', home_activity)
    580 
     580    def notify_activity_ended(self, activity_id):
     581        home_activity = self.get_activity_by_id(activity_id)
     582        if home_activity:
     583            if home_activity.props.launching:
     584                logging.debug('Activity %s (%s) ended while still launching, '\
     585                              'assuming it failed.', activity_id,
     586                              home_activity.get_type())
     587                self.notify_launch_failed(activity_id)
     588            else:
     589                logging.debug("Activity %s ended", activity_id)
     590                self._remove_activity(home_activity)
     591        else:
     592            # The activity may have already been removed when its window closed.
     593            pass
     594   
    581595    def _check_activity_launched(self, activity_id):
    582596        home_activity = self.get_activity_by_id(activity_id)
    583597
  • src/jarabe/view/launcher.py

    diff --git a/src/jarabe/view/launcher.py b/src/jarabe/view/launcher.py
    index d4b9967..1db87fb 100644
    a b  
    1515# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
    1616
    1717import logging
     18from gettext import gettext as _
     19
     20import math
    1821
    1922import gtk
    2023import hippo
    2124import gobject
    2225
    2326from sugar import wm
     27from sugar.presence import presenceservice
    2428from sugar.graphics import style
    2529from sugar.graphics import animator
    2630from sugar.graphics.xocolor import XoColor
     31from sugar.graphics.icon import Icon
     32from sugar.graphics.icon import CanvasIcon
    2733
    2834from jarabe.model import shell
    29 from jarabe.view.pulsingicon import CanvasPulsingIcon
     35
     36XOCOLOR_WHITE = XoColor("#ffffff,#ffffff")
     37XOCOLOR_GRAY = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
     38                                  style.COLOR_WHITE.get_svg()))
     39
     40
     41def _parse_svgcolor(str):
     42    r = int(str[1:3], 16)
     43    g = int(str[3:5], 16)
     44    b = int(str[5:7], 16)
     45    return r, g, b
     46   
     47def _parse_xocolor(xocolor):
     48    stroke = _parse_svgcolor(xocolor.get_stroke_color())
     49    fill = _parse_svgcolor(xocolor.get_fill_color())
     50    return stroke, fill
     51
     52def _format_xocolor(stroke_rgb, fill_rgb):
     53    return XoColor("#%02x%02x%02x,#%02x%02x%02x" % (stroke_rgb + fill_rgb))
     54
     55def _blend_rgb(a, b, current):
     56    return (a[0] + (b[0] - a[0]) * current,
     57            a[1] + (b[1] - a[1]) * current,
     58            a[2] + (b[2] - a[2]) * current)
     59   
     60def _blend_xocolor(a, b, current):
     61    stroke_a, fill_a = _parse_xocolor(a)
     62    stroke_b, fill_b = _parse_xocolor(b)
     63    stroke_c = _blend_rgb(stroke_a, stroke_b, current)
     64    fill_c = _blend_rgb(fill_a, fill_b, current)
     65    return _format_xocolor(stroke_c, fill_c)
     66
     67
     68class _Layout(gobject.GObject, hippo.CanvasLayout):
     69    __gtype_name__ = 'SugarLauncherLayout'
     70    def __init__(self):
     71        gobject.GObject.__init__(self)
     72        self._box = None
     73       
     74        self._child_mode = []
     75
     76    # Note to reviewer:
     77    # The child mode is kept in an array that parallels the children of the CanvasBox.
     78    # This disallows objects from being deleted or hidden as the arrays would get out of sync.
     79    # Is there a better way to attach a layout mode to each child?
     80    def append_mode(self, child, mode):
     81        self._child_mode.append(mode)
     82
     83    def do_set_box(self, box):
     84        self._box = box
     85
     86    def do_get_height_request(self, for_width):
     87        return 0, 0
     88
     89    def do_get_width_request(self):
     90        return 0, 0
     91
     92    def do_allocate(self, x, y, width, height,
     93                    req_width, req_height, origin_changed):
     94       
     95        circle_angle = -math.pi/2
     96        circle_radius = min(width, height) * 0.4
     97       
     98        center_bottom_y = height * 3 / 4
     99
     100        i = 0
     101        for child in self._box.get_layout_children():
     102            mode = self._child_mode[i]
     103            i += 1
     104           
     105            if mode == 'center-bottom':                   
     106                min_width, child_width = child.get_width_request()
     107                min_height, child_height = child.get_height_request(child_width)
     108               
     109                center_bottom_y -= (child_height + style.DEFAULT_SPACING) / 2
     110               
     111        i = 0
     112        for child in self._box.get_layout_children():
     113            min_width, child_width = child.get_width_request()
     114            min_height, child_height = child.get_height_request(child_width)
     115
     116            mode = self._child_mode[i]
     117            i += 1
     118
     119            if mode == 'center':                 
     120                child.allocate(x + (width - child_width) / 2,
     121                               y + (height - child_height) / 2,
     122                               child_width, child_height, origin_changed)
     123           
     124            if mode == 'center-bottom':                   
     125                child.allocate(x + (width - child_width) / 2,
     126                               y + center_bottom_y,
     127                               child_width, child_height, origin_changed)
     128                center_bottom_y += child_height + style.DEFAULT_SPACING
     129
     130class _ZoomAnimation(animator.Animation):
     131    def __init__(self, icon, start_size, end_size):
     132        animator.Animation.__init__(self, 0.0, 1.0)
     133
     134        self._icon = icon
     135        self._start_size = start_size
     136        self._end_size = end_size
     137       
     138    def next_frame(self, current):
     139        size = self._start_size + (self._end_size - self._start_size) * current
     140        self._icon.props.size = int(size)
     141   
     142class _PulseAnimation(animator.Animation):
     143    def __init__(self, icon, start_color, middle_color):
     144        animator.Animation.__init__(self, 0.0, 1.0)
     145
     146        self._icon = icon
     147        self._start_color = start_color
     148        self._middle_color = middle_color
     149       
     150    def do_frame(self, t, duration, easing):
     151        f = math.sin((t / duration) * math.pi)
     152        color = _blend_xocolor(self._start_color, self._middle_color, f)
     153        self._icon.xo_color = color
    30154
    31155class LaunchWindow(gtk.Window):
    32156    def __init__(self, activity_id, icon_path, icon_color):
    33157        gobject.GObject.__init__(self)
    34158
     159        self._activity_id = activity_id
     160        self._home_activity = None
     161
     162        self._icon_color = icon_color
     163       
    35164        self.props.type_hint = gtk.gdk.WINDOW_TYPE_HINT_NORMAL
    36165        self.props.decorated = False
    37166
     167        self._layout = _Layout()
     168
     169        self._box = hippo.CanvasBox()
     170        self._box.set_layout(self._layout)
     171
     172        self._activity_icon = CanvasIcon(file_name=icon_path,
     173                                         xo_color=self._icon_color)
     174        self._box.append(self._activity_icon)
     175        self._layout.append_mode(self._activity_icon, 'center')
     176
     177        self._error_text = hippo.CanvasText(text='',
     178                                xalign=hippo.ALIGNMENT_CENTER,
     179                                font_desc=style.FONT_BOLD.get_pango_desc(),
     180                                color=style.COLOR_BUTTON_GREY.get_int())
     181        self._box.append(self._error_text)
     182        self._layout.append_mode(self._error_text, 'center-bottom')
     183
     184        button = gtk.Button(label=_('Stop'))
     185        button.connect('clicked', self.__close_button_clicked_cb)
     186        button.props.image = Icon(icon_name='activity-stop',
     187                                  icon_size=gtk.ICON_SIZE_BUTTON)
     188        self._close_button = hippo.CanvasWidget(widget=button,
     189                                                xalign=hippo.ALIGNMENT_CENTER)
     190        self._box.append(self._close_button)
     191        self._layout.append_mode(self._close_button, 'center-bottom')
     192
     193        self._box.set_child_visible(self._error_text, False)       
     194        self._box.set_child_visible(self._close_button, False)
     195
    38196        canvas = hippo.Canvas()
    39197        canvas.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
    40         self.add(canvas)
    41         canvas.show()
    42 
    43         self._activity_id = activity_id
    44         self._box = LaunchBox(activity_id, icon_path, icon_color)
    45198        canvas.set_root(self._box)
     199        canvas.show()
     200        self.add(canvas)
    46201
    47202        self.connect('realize', self.__realize_cb)
    48203
    49204        screen = gtk.gdk.screen_get_default()
    50205        screen.connect('size-changed', self.__size_changed_cb)
    51 
    52206        self._update_size()
     207       
     208        anim = animator.Animator(1.0)
     209        anim.add(_PulseAnimation(self._activity_icon,
     210                                 self._icon_color, XOCOLOR_GRAY))         
     211        anim.add(_ZoomAnimation(self._activity_icon,
     212                                style.STANDARD_ICON_SIZE, style.XLARGE_ICON_SIZE))         
     213        anim.start()
     214
     215        self._timeout_id = gobject.timeout_add(3000, self._pulse_icon)
     216
     217    def _pulse_icon(self):
     218        anim = animator.Animator(1.0)
     219        anim.add(_PulseAnimation(self._activity_icon,
     220                                 self._icon_color, XOCOLOR_GRAY))         
     221        anim.start()
     222        return True
    53223
    54224    def show(self):
    55225        self.present()
    56         self._box.zoom_in()
    57226
    58227    def _update_size(self):
    59228        self.resize(gtk.gdk.screen_width(), gtk.gdk.screen_height())
    class LaunchWindow(gtk.Window): 
    66235    def __size_changed_cb(self, screen):
    67236        self._update_size()
    68237
    69 class LaunchBox(hippo.CanvasBox):
    70     def __init__(self, activity_id, icon_path, icon_color):
    71         gobject.GObject.__init__(self, orientation=hippo.ORIENTATION_VERTICAL)
     238    def show_failed(self, home_activity):
     239        logging.debug("Displaying failure message on launcher")
     240       
     241        gobject.source_remove(self._timeout_id)
     242       
     243        self._home_activity = home_activity
    72244
    73         self._activity_id = activity_id
    74         self._activity_icon = CanvasPulsingIcon(
    75             file_name=icon_path,
    76             pulse_color=icon_color,
    77             background_color=style.COLOR_WHITE.get_gdk_color())
    78         self.append(self._activity_icon, hippo.PACK_EXPAND)
    79 
    80         # FIXME support non-xo colors in CanvasPulsingIcon
    81         self._activity_icon.props.base_color = \
    82             XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
    83                                style.COLOR_TRANSPARENT.get_svg()))
    84 
    85         self._animator = animator.Animator(1.0)
    86 
    87         self._home = shell.get_model()
    88         self._home.connect('active-activity-changed',
    89                            self.__active_activity_changed_cb)
    90 
    91         self.connect('destroy', self.__destroy_cb)
    92 
    93     def __destroy_cb(self, box):
    94         self._activity_icon.props.pulsing = False
    95         self._home.disconnect_by_func(self.__active_activity_changed_cb)
    96 
    97     def zoom_in(self):
    98         self._activity_icon.props.size = style.STANDARD_ICON_SIZE
    99 
    100         self._animator.remove_all()
    101         self._animator.add(_Animation(self._activity_icon,
    102                                       style.STANDARD_ICON_SIZE,
    103                                       style.XLARGE_ICON_SIZE))
    104         self._animator.start()
    105         self._activity_icon.props.pulsing = True
    106 
    107     def __active_activity_changed_cb(self, model, activity):
    108         if activity.get_activity_id() == self._activity_id:
    109             self._activity_icon.props.paused = False
    110         else:
    111             self._activity_icon.props.paused = True
    112 
    113 class _Animation(animator.Animation):
    114     def __init__(self, icon, start_size, end_size):
    115         animator.Animation.__init__(self, 0.0, 1.0)
     245        self._box.set_child_visible(self._error_text, True)       
     246        self._box.set_child_visible(self._close_button, True)
    116247
    117         self._icon = icon
    118         self.start_size = start_size
    119         self.end_size = end_size
     248        self._error_text.props.text = _('%s failed to start.') % \
     249                                        home_activity.get_activity_name()
    120250
    121     def next_frame(self, current):
    122         d = (self.end_size - self.start_size) * current
    123         self._icon.props.size = int(self.start_size + d)
     251    def __close_button_clicked_cb(self, button):
     252        _destroy_launcher(self._home_activity)
    124253
    125254_launchers = {}
    126255
    def setup(): 
    131260    model.connect('launch-completed', __launch_completed_cb)
    132261
    133262def add_launcher(activity_id, icon_path, icon_color):
    134 
    135263    if activity_id in _launchers:
    136264        return
    137265
    def __launch_started_cb(home_model, home_activity): 
    145273                 home_activity.get_icon_color())
    146274
    147275def __launch_failed_cb(home_model, home_activity):
    148     _destroy_launcher(home_activity)
     276    activity_id = home_activity.get_activity_id()
     277
     278    if activity_id in _launchers:
     279        _launchers[activity_id].show_failed(home_activity)
     280    else:
     281        logging.error('Launcher for %s is missing', activity_id)
    149282
    150283def __launch_completed_cb(home_model, home_activity):
    151284    _destroy_launcher(home_activity)
  • src/jarabe/view/service.py

    diff --git a/src/jarabe/view/service.py b/src/jarabe/view/service.py
    index 2b91437..c1da23c 100644
    a b class UIService(dbus.service.Object): 
    9898    def NotifyLaunchFailure(self, activity_id):
    9999        shell.get_model().notify_launch_failed(activity_id)
    100100
     101    @dbus.service.method(_DBUS_SHELL_IFACE,
     102                         in_signature="s", out_signature="")
     103    def NotifyActivityEnded(self, activity_id):
     104        shell.get_model().notify_activity_ended(activity_id)
     105
    101106    @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s")
    102107    def ColorChanged(self, color):
    103108        pass