Re: [sugar] [Cross Posted] High res screenshots of Sugar

2008-10-10 Thread Benjamin Berg
On Fri, 2008-10-10 at 18:27 +0200, Marco Pesenti Gritti wrote:
> On Fri, Oct 10, 2008 at 6:19 PM, Benjamin Berg
> <[EMAIL PROTECTED]> wrote:
> > The GTK+ theme would need to be adjusted to give good results, as it
> > uses a lot of pixel sizes.
> 
> When does gtk resolution independence land? :)
> 
> That's going to make our life so much easier...

Maybe for GTK+ 2.16, which is due next year some time after GUADEC.

Though I expect that the current proposal is not yet good enough from a
gtkrc point of view, ie. it does not support basic calculations which we
would probably need.
I have never had a close look though.

Benjamin


signature.asc
Description: This is a digitally signed message part
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


Re: [sugar] [Cross Posted] High res screenshots of Sugar

2008-10-10 Thread Benjamin Berg
On Fri, 2008-10-10 at 18:12 +0200, Marco Pesenti Gritti wrote:
> On Fri, Oct 10, 2008 at 6:09 PM, Bert Freudenberg <[EMAIL PROTECTED]> wrote:
> >>> If you *really* wanted to get fancy, run Sugar inside a VNC server
> >>> of say
> >>> 12000x9000 pixels at 2000 dpi. Sugar is designed to be scalable to
> >>> all
> >>> resolutions so in theory this should work. In practice you'll find
> >>> there are
> >>> some things that are not really scaled.
> >>>
> >>
> >> Fonts will probably be messed up in that case.
> >
> >
> > They shouldn't, Sugar uses scalable fonts.
> 
> Bert is correct here. It's supposed to work... how well it work in
> practice I don't know :)

The GTK+ theme would need to be adjusted to give good results, as it
uses a lot of pixel sizes.

Benjamin


signature.asc
Description: This is a digitally signed message part
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


Re: [sugar] rendering test

2008-09-28 Thread Benjamin Berg
On Sun, 2008-09-28 at 18:35 +0200, Bernie Innocenti wrote:
> Benjamin Berg wrote:
> > A shared cache would be great. One way to do it would be to share a
> > mmap'able file similar to the GTK+ icon cache file. But to create this
> > file, one will need to know the icon colors that should be prerendered.
> >  
> > More complicated would be to create a small service that uploads
> > rendered icons to the X server, and hands back the pixmap ID. That way
> > all applications could use one shared server side cache.
> > (The pixmap based sapwood GTK+ engine, used on the Nokia 770/8x0
> > devices, does this to save memory.)
> 
> We discussed this approach in Brno.  I'm glad to hear it's feasible.
> 
> Oh, why can't we just use the gtk icon cache with the rendered pixmaps?

The thing is that the GTK+ icon cache are just prerendered icons
(created with a standalone application). In Sugar however, we need a
more complicated mechanism as the colour of the icons is changed on the
fly. One would probably need to implement a custom icon cache, that
prerenders the most often needed colour combinations.

Benjamin


signature.asc
Description: This is a digitally signed message part
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


Re: [sugar] rendering test

2008-09-28 Thread Benjamin Berg
Hello,

On Sun, 2008-09-28 at 12:43 +0200, Riccardo Lucchese wrote:
> Besides this, I think the icon caching mechanism should be reworked:
> right now every icon has its own surfaces-cache and its svgloader. So
> that, if I'm not wrong, two icons showing the same svg (with same size,
> colors etc..) cache two distinct but identical surfaces in their
> _iconbuffer and cache two times the raw svg file contents in their
> svgloader.

The cached surface is shared between different icons (the icon buffer is
not shared). _surface_cache is a class variable, so that only one LRU
list for the surfaces is created.

> I guess best would be switching to have only one global cache (or
> not ? ;); perhaps a global cache clashes with using server side
> surfaces ? (see benzea's patches)

A shared cache would be great. One way to do it would be to share a
mmap'able file similar to the GTK+ icon cache file. But to create this
file, one will need to know the icon colors that should be prerendered.
 
More complicated would be to create a small service that uploads
rendered icons to the X server, and hands back the pixmap ID. That way
all applications could use one shared server side cache.
(The pixmap based sapwood GTK+ engine, used on the Nokia 770/8x0
devices, does this to save memory.)

Benjamin


signature.asc
Description: This is a digitally signed message part
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


Re: [sugar] [PATCH 2/6] Implement API to handle tabbing.

2008-06-20 Thread Benjamin Berg
On Fri, 2008-06-20 at 11:19 +0200, Marco Pesenti Gritti wrote:
> On Thu, Jun 19, 2008 at 9:08 PM, Benjamin Berg
> <[EMAIL PROTECTED]> wrote:
> > +def tabbing_activate_current(self):
> > +home_model = self._model.get_home()
> > +activity = home_model.get_active_activity()
> > +if activity and activity.get_window():
> > +activity.get_window().activate(1)
> 
> Do we get a window_changed even in the model even if the window has
> really been activated during the tabbing?

Yes. Because of this the model needs to ignore window changes during
tabbing. If the window changes were not ignored, a race condition
exists:
 - User tabs
 - Timeout to activate the window fires
 - User tabs
 - Window is raised a bit later
 - The homemodel emits active-activity-changed

And in the end the wrong button is selected in the activities tray (for
1/4 of a second).

Benjamin




signature.asc
Description: This is a digitally signed message part
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 6/6] Use Alt+Tab for reverse tabbing direction

2008-06-19 Thread Benjamin Berg

---

 src/view/keyhandler.py |2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/src/view/keyhandler.py b/src/view/keyhandler.py
index b42b93c..ab779ec 100644
--- a/src/view/keyhandler.py
+++ b/src/view/keyhandler.py
@@ -59,7 +59,7 @@ _actions_table = {
 'q' : 'quit_emulator',
 'Tab'   : 'next_window',
 'n' : 'next_window',
-'Tab' : 'previous_window',
+'Tab': 'previous_window',
 'p' : 'previous_window',
 'Escape'   : 'close_window',
 'q': 'close_window',

___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 5/6] Scroll to the button that is selected in the tabbing operation.

2008-06-19 Thread Benjamin Berg

---

 src/view/frame/activitiestray.py |2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/src/view/frame/activitiestray.py b/src/view/frame/activitiestray.py
index 03a523e..c34c119 100644
--- a/src/view/frame/activitiestray.py
+++ b/src/view/frame/activitiestray.py
@@ -342,6 +342,8 @@ class ActivitiesTray(HTray):
 self._freeze_button_clicks = True
 button.props.active = True
 self._freeze_button_clicks = True
+
+self.scroll_to_item(button)
 
 def __activity_clicked_cb(self, button, home_activity):
 if not self._freeze_button_clicks and button.props.active:

___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 4/6] Ignore "click" events during the activity_changed_cb.

2008-06-19 Thread Benjamin Berg
It is neccessary to ignore any "click" events that happen as a result of the
activity_changed_cb. If these were not ignored they would cause an instant
activity switch to the new activity.
---

 src/view/frame/activitiestray.py |6 +-
 1 files changed, 5 insertions(+), 1 deletions(-)

diff --git a/src/view/frame/activitiestray.py b/src/view/frame/activitiestray.py
index 0c5b3f8..03a523e 100644
--- a/src/view/frame/activitiestray.py
+++ b/src/view/frame/activitiestray.py
@@ -302,6 +302,7 @@ class ActivitiesTray(HTray):
 
 self._buttons = {}
 self._invite_to_item = {}
+self._freeze_button_clicks = False
 
 self._home_model = shellmodel.get_instance().get_home()
 self._home_model.connect('activity-added', self.__activity_added_cb)
@@ -336,11 +337,14 @@ class ActivitiesTray(HTray):
 
 def __activity_changed_cb(self, home_model, home_activity):
 logging.debug('__activity_changed_cb: %r' % home_activity)
+
 button = self._buttons[home_activity.get_activity_id()]
+self._freeze_button_clicks = True
 button.props.active = True
+self._freeze_button_clicks = True
 
 def __activity_clicked_cb(self, button, home_activity):
-if button.props.active:
+if not self._freeze_button_clicks and button.props.active:
 logging.debug('ActivitiesTray.__activity_clicked_cb')
 window = home_activity.get_window()
 if window:

___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 3/6] Handle the keyboard event handling for tabbing.

2008-06-19 Thread Benjamin Berg

---

 src/view/keyhandler.py |   91 +++-
 1 files changed, 89 insertions(+), 2 deletions(-)

diff --git a/src/view/keyhandler.py b/src/view/keyhandler.py
index 16f5a43..b42b93c 100644
--- a/src/view/keyhandler.py
+++ b/src/view/keyhandler.py
@@ -33,6 +33,10 @@ _BRIGHTNESS_STEP = 2
 _VOLUME_STEP = 10
 _BRIGHTNESS_MAX = 15
 _VOLUME_MAX = 100
+# The modifier used for tabbing. Should the shortcuts ever be made user
+# configurable, then some code to figure out the apropriate modifier is
+# needed instead of hardcoding it.
+_TABBING_MODIFIER = gtk.gdk.MOD1_MASK
 
 _actions_table = {
 'F1' : 'zoom_mesh',
@@ -78,10 +82,13 @@ class KeyHandler(object):
 self._keycode_pressed = 0
 self._keystate_pressed = 0
 self._speech_proxy = None
+self._tabbing_windows = False
 
 self._key_grabber = KeyGrabber()
 self._key_grabber.connect('key-pressed',
   self._key_pressed_cb)
+self._key_grabber.connect('key-released',
+  self._key_released_cb)
 
 for key in _actions_table.keys():
 self._key_grabber.grab(key)
@@ -132,15 +139,70 @@ class KeyHandler(object):
 self._get_speech_proxy().SayText(text, reply_handler=lambda: None, 
\
 error_handler=self._on_speech_err)
 
+def _window_tabbing(self, direction):
+shell = view.Shell.get_instance()
+if not self._tabbing_windows:
+logging.debug('Grabing the input.')
+
+screen = gtk.gdk.screen_get_default()
+window = screen.get_root_window() 
+keyboard_grab_result = gtk.gdk.keyboard_grab(window)
+pointer_grab_result = gtk.gdk.pointer_grab(window)
+
+self._tabbing_windows = (keyboard_grab_result == 
gtk.gdk.GRAB_SUCCESS and
+ pointer_grab_result == 
gtk.gdk.GRAB_SUCCESS)
+
+# Now test that the modifier is still active to prevent race
+# conditions. We also test if one of the grabs failed.
+mask = window.get_pointer()[2]
+if not self._tabbing_windows or not (mask & _TABBING_MODIFIER):
+logging.debug('Releasing grabs again.')
+
+if keyboard_grab_result != gtk.gdk.GRAB_SUCCESS:
+gtk.gdk.keyboard_ungrab()
+if pointer_grab_result != gtk.gdk.GRAB_SUCCESS:
+gtk.gdk.pointer_ungrab()
+self._tabbing_windows = False
+else:
+shell.tabbing_start()
+
+first_switch = True
+else:
+first_switch = False
+
+if self._tabbing_windows:
+if direction == 1:
+shell.tabbing_next_activity(first_switch)
+else:
+shell.tabbing_previous_activity(first_switch)
+
+return self._tabbing_windows
+
+def _stop_window_tabbing(self):
+# Some useless key was pressed, or  released.
+if not self._tabbing_windows:
+return
+
+logging.debug('Releasing grabs again.')
+gtk.gdk.keyboard_ungrab()
+gtk.gdk.pointer_ungrab()
+
+shell = view.Shell.get_instance()
+shell.tabbing_stop()
+
+self._tabbing_windows = False
+
 def handle_say_text(self):
 clipboard = gtk.clipboard_get(selection="PRIMARY")
 clipboard.request_text(self._primary_selection_cb)
 
 def handle_previous_window(self):
-view.Shell.get_instance().activate_previous_activity()
+if not self._window_tabbing(-1):
+view.Shell.get_instance().activate_previous_activity()
 
 def handle_next_window(self):
-view.Shell.get_instance().activate_next_activity()
+if not self._window_tabbing(1):
+view.Shell.get_instance().activate_next_activity()
 
 def handle_close_window(self):
 view.Shell.get_instance().close_current_activity()
@@ -252,9 +314,34 @@ class KeyHandler(object):
 self._keystate_pressed = state
 
 action = _actions_table[key]
+if self._tabbing_windows:
+# Only accept window tabbing events, everything else
+# cancels the tabbing operation.
+if not action in ["next_window", "previous_window"]:
+self._stop_window_tabbing()
+return True
+
 method = getattr(self, 'handle_' + action)
 method()
 
 return True
+else:
+# If this is not a registered key, then cancel any active
+# tabbing.
+if self._tabbing_windows:
+if not grabber.is_modifier(keycode):
+self._stop_window_tabbing()
+return True
 
 return False
+
+def _key_released_cb(self, grabber, keycode, state):
+if self._tabbing_windows:
+

[sugar] [PATCH 2/6] Implement API to handle tabbing.

2008-06-19 Thread Benjamin Berg
This adds some functions to to start/stop the tabbing and to switch to the
next and previous activity. It also takes care of raising the activities
window after a short delay.
---

 src/view/Shell.py |   63 +
 1 files changed, 63 insertions(+), 0 deletions(-)

diff --git a/src/view/Shell.py b/src/view/Shell.py
index b77465d..35f4073 100644
--- a/src/view/Shell.py
+++ b/src/view/Shell.py
@@ -57,6 +57,7 @@ class Shell(gobject.GObject):
 self._screen = wnck.screen_get_default()
 self._current_host = None
 self._screen_rotation = 0
+self._tabbing_timeout = None
 
 self._key_handler = KeyHandler()
 
@@ -222,6 +223,68 @@ class Shell(gobject.GObject):
 self.take_activity_screenshot()
 next_activity.get_window().activate(gtk.get_current_event_time())
 
+def tabbing_activate_current(self):
+home_model = self._model.get_home()
+activity = home_model.get_active_activity()
+if activity and activity.get_window():
+activity.get_window().activate(1)
+
+def __tabbing_timeout_cb(self):
+self._tabbing_timeout = None
+self.tabbing_activate_current()
+return False
+
+def tabbing_previous_activity(self, first_switch):
+home_model = self._model.get_home()
+zoom_level = self._model.get_zoom_level()
+
+if first_switch and zoom_level != shellmodel.ShellModel.ZOOM_ACTIVITY:
+activity = home_model.get_active_activity()
+else:
+activity = home_model.get_previous_activity()
+
+home_model.tabbing_set_activity(activity)
+
+if self._tabbing_timeout:
+gobject.source_remove(self._tabbing_timeout)
+self._tabbing_timeout = \
+gobject.timeout_add(250, self.__tabbing_timeout_cb)
+
+def tabbing_next_activity(self, first_switch):
+home_model = self._model.get_home()
+zoom_level = self._model.get_zoom_level()
+
+if first_switch and zoom_level != shellmodel.ShellModel.ZOOM_ACTIVITY:
+activity = home_model.get_active_activity()
+else:
+activity = home_model.get_next_activity()
+
+home_model.tabbing_set_activity(activity)
+
+if self._tabbing_timeout:
+gobject.source_remove(self._tabbing_timeout)
+self._tabbing_timeout = \
+gobject.timeout_add(250, self.__tabbing_timeout_cb)
+
+def tabbing_start(self):
+self.take_activity_screenshot()
+self._frame.show(self._frame.MODE_NON_INTERACTIVE)
+
+home_model = self._model.get_home()
+home_model.tabbing_start()
+
+def tabbing_stop(self):
+self._frame.hide()
+
+if self._tabbing_timeout:
+gobject.source_remove(self._tabbing_timeout)
+
+self.tabbing_activate_current()
+self._model.get_home().tabbing_stop()
+
+home_model = self._model.get_home()
+home_model.tabbing_stop()
+
 def close_current_activity(self):
 if self._model.get_zoom_level() != shellmodel.ShellModel.ZOOM_ACTIVITY:
 return

___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 1/6] Add functionallity for tabbing trough activities.

2008-06-19 Thread Benjamin Berg
To be able to raise activity windows on a delay, the homemodel needs to
be set directly. It also needs to ignore window raises while the user is
tabbing.
---

 src/model/homemodel.py |   15 +++
 1 files changed, 15 insertions(+), 0 deletions(-)

diff --git a/src/model/homemodel.py b/src/model/homemodel.py
index a75adcf..be46ffb 100644
--- a/src/model/homemodel.py
+++ b/src/model/homemodel.py
@@ -64,6 +64,7 @@ class HomeModel(gobject.GObject):
 
 self._activities = []
 self._active_activity = None
+self._tabbing = False
 
 screen = wnck.screen_get_default()
 screen.connect('window-opened', self._window_opened_cb)
@@ -102,6 +103,15 @@ class HomeModel(gobject.GObject):
 """Returns the activity that the user is currently working in"""
 return self._active_activity
 
+def tabbing_set_activity(self, activity):
+self._set_active_activity(activity)
+
+def tabbing_start(self):
+self._tabbing = True
+
+def tabbing_stop(self):
+self._tabbing = False
+
 def _set_active_activity(self, home_activity):
 if self._active_activity == home_activity:
 return
@@ -185,6 +195,11 @@ class HomeModel(gobject.GObject):
 logging.error("set_active() failed: %s" % err)
 
 def _active_window_changed_cb(self, screen, previous_window=None):
+if self._tabbing:
+# Ignore any window changes when tabbing, as these are comming
+# in delayed.
+return
+
 window = screen.get_active_window()
 if window is None:
 return

___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 2/2] Add scroll_to_item functions to the trays to show a button that may be hidden.

2008-06-19 Thread Benjamin Berg

---

 src/sugar/graphics/tray.py |   25 +
 1 files changed, 25 insertions(+), 0 deletions(-)

diff --git a/src/sugar/graphics/tray.py b/src/sugar/graphics/tray.py
index d7d5918..2f5db9a 100644
--- a/src/sugar/graphics/tray.py
+++ b/src/sugar/graphics/tray.py
@@ -67,6 +67,25 @@ class _TrayViewport(gtk.Viewport):
 elif direction == _NEXT_PAGE:
 self._scroll_next()
 
+def scroll_to_item(self, item):
+"""This function scrolls the viewport so that item will be visible."""
+assert item in self.traybar.get_children()
+
+# Get the allocation, and make sure that it is visible
+if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+adj = self.get_hadjustment()
+start = item.allocation.x
+stop = item.allocation.x + item.allocation.width
+else:
+adj = self.get_vadjustment()
+start = item.allocation.y
+stop = item.allocation.y + item.allocation.height
+
+if start < adj.value:
+adj.value = start
+elif stop > adj.value + adj.page_size:
+adj.value = stop - adj.page_size
+
 def _scroll_next(self):
 allocation = self.get_allocation()
 if self.orientation == gtk.ORIENTATION_HORIZONTAL:
@@ -218,6 +237,9 @@ class HTray(gtk.HBox):
 def get_item_index(self, item):
 return self._viewport.traybar.get_item_index(item)
 
+def scroll_to_item(self, item):
+self._viewport.scroll_to_item(item)
+
 class VTray(gtk.VBox):
 def __init__(self, **kwargs):
 gobject.GObject.__init__(self, **kwargs)
@@ -249,6 +271,9 @@ class VTray(gtk.VBox):
 def get_item_index(self, item):
 return self._viewport.traybar.get_item_index(item)
 
+def scroll_to_item(self, item):
+self._viewport.scroll_to_item(item)
+
 class TrayButton(ToolButton):
 def __init__(self, **kwargs):
 ToolButton.__init__(self, **kwargs)

___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 1/2] Add is_modifier and is_special_modifier functions to SugarKeyGrabber.

2008-06-19 Thread Benjamin Berg

---

 src/sugar/_sugarext.defs  |   19 +
 src/sugar/sugar-key-grabber.c |   59 +
 src/sugar/sugar-key-grabber.h |6 
 3 files changed, 84 insertions(+), 0 deletions(-)

diff --git a/src/sugar/_sugarext.defs b/src/sugar/_sugarext.defs
index 02b673c..6bc068f 100644
--- a/src/sugar/_sugarext.defs
+++ b/src/sugar/_sugarext.defs
@@ -121,6 +121,25 @@
   )
 )
 
+(define-method is_modifier
+  (of-object "SugarKeyGrabber")
+  (c-name "sugar_key_grabber_is_modifier")
+  (return-type "gboolean")
+  (parameters
+'("guint" "keycode")
+  )
+)
+
+(define-method is_specific_modifier
+  (of-object "SugarKeyGrabber")
+  (c-name "sugar_key_grabber_is_specific_modifier")
+  (return-type "gboolean")
+  (parameters
+'("guint" "keycode")
+'("guint" "mask")
+  )
+)
+
 ;; From sexy-icon-entry.h
 
 (define-function sexy_icon_entry_get_type
diff --git a/src/sugar/sugar-key-grabber.c b/src/sugar/sugar-key-grabber.c
index baddab5..ed0cf9f 100644
--- a/src/sugar/sugar-key-grabber.c
+++ b/src/sugar/sugar-key-grabber.c
@@ -217,3 +217,62 @@ sugar_key_grabber_grab(SugarKeyGrabber *grabber, const 
char *key)
 
grabber->keys = g_list_append(grabber->keys, keyinfo);  
 }
+
+gboolean
+sugar_key_grabber_is_modifier(SugarKeyGrabber *grabber, guint keycode)
+{
+   Display *xdisplay;
+   XModifierKeymap *modmap;
+   gint size, i;
+   gboolean is_modifier = FALSE;
+
+   xdisplay = gdk_x11_drawable_get_xdisplay(GDK_DRAWABLE (grabber->root));
+
+   modmap = XGetModifierMapping (xdisplay);
+   
+   size = 8 * modmap->max_keypermod;
+   for (i = 0; i < size; i++) {
+   if (keycode == modmap->modifiermap[i]) {
+   is_modifier = TRUE;
+   break;
+   }
+   }
+   
+   XFreeModifiermap (modmap);
+   
+   return is_modifier;
+}
+
+
+gboolean
+sugar_key_grabber_is_specific_modifier(SugarKeyGrabber *grabber, guint 
keycode, guint mask)
+{
+   Display *xdisplay;
+   XModifierKeymap *modmap;
+   gint start, end, i, mod_index;
+   gboolean is_modifier = FALSE;
+
+   xdisplay = gdk_x11_drawable_get_xdisplay(GDK_DRAWABLE (grabber->root));
+
+   modmap = XGetModifierMapping (xdisplay);
+   
+   mod_index = 0;
+   mask = mask >> 1;
+   while (mask != 0) {
+   mask = mask >> 1;
+   mod_index += 1;
+   }
+   
+   start = mod_index * modmap->max_keypermod;
+   end = (mod_index + 1) * modmap->max_keypermod;
+   for (i = start; i < end; i++) {
+   if (keycode == modmap->modifiermap[i]) {
+   is_modifier = TRUE;
+   break;
+   }
+   }
+   
+   XFreeModifiermap (modmap);
+   
+   return is_modifier;
+}
diff --git a/src/sugar/sugar-key-grabber.h b/src/sugar/sugar-key-grabber.h
index 5b734e7..cf6efe2 100644
--- a/src/sugar/sugar-key-grabber.h
+++ b/src/sugar/sugar-key-grabber.h
@@ -60,6 +60,12 @@ void sugar_key_grabber_grab  
(SugarKeyGrabber *grabber,
 char*sugar_key_grabber_get_key  (SugarKeyGrabber *grabber,
 guint  
  keycode,
 guint  
  state);
+gboolean sugar_key_grabber_is_modifier  (SugarKeyGrabber *grabber,
+ guintkeycode);
+gboolean sugar_key_grabber_is_specific_modifier (SugarKeyGrabber *grabber,
+ guintkeycode,
+ guintmask);
+
 
 G_END_DECLS
 

___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH] Tabbing again

2008-06-19 Thread Benjamin Berg
Hello,

here is another go at the tabbing patches, after Eben gave his feedback.
There are no palettes anymore, instead it tries to get close to
switching the activities instantly. However, the actual activity
switching is delayed by 1/4 of a second, to help against long expose
times.

There are again two patch series. One for sugar-toolkit (2 Patches) and
another one for sugar (6 patches).

Benjamin


signature.asc
Description: This is a digitally signed message part
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


Re: [sugar] [PATCH] Change tabbing to show a preview by opening the frame

2008-06-10 Thread Benjamin Berg
On Tue, 2008-06-10 at 09:58 +0200, Tomeu Vizoso wrote:
> On Thu, Jun 5, 2008 at 12:20 AM, Benjamin Berg
> > Unfortunately there are a couple of issues with palettes.
> >
> > One issue is that that at first only the primary palette is shown, but
> > after a while the secondary palette will pop up (as the timeout is
> > started automatically).
> 
> Can you look at how the secondary palette is displayed by the right
> click? I understand that's what you want, right?

Yup, something like that. Though it will need to be handled differently
than the right click code. I guess just adding another parameter to
popdown will work.

> > Then I am accessing the _update_position function of the Palette
> > directly, to handle disappearing activities properly.
> 
> You mean to move left the palette when another activity icon
> disappears? Perhaps the Invoker should be listening for position
> changes on the observed widget and move the palette accordingly?

Yeah, something like that. Though I am not sure what is needed to get it
working right now.

> Before looking in detail at the code, have you considered relying on
> the window manager to do the tabbing and have the shell to just
> observe the changes on the tabbing stack? Perhaps wnck can help on
> that?

To say the truth I did not even consider it, as tabbing is already
handled by Sugar in the existing code. That said, I just had a quick
look, but could not find anything in metacity or libwnck that suggests
that leaving tabbing to the window manager is possible.

Benjamin


signature.asc
Description: This is a digitally signed message part
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 5/5] Add support to the activitiestray to pop up the palette during tabbing

2008-06-04 Thread Benjamin Berg
---
 src/view/frame/activitiestray.py |   58 +-
 src/view/frame/frame.py  |2 +-
 2 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/src/view/frame/activitiestray.py b/src/view/frame/activitiestray.py
index 020cb68..8955ac8 100644
--- a/src/view/frame/activitiestray.py
+++ b/src/view/frame/activitiestray.py
@@ -16,6 +16,7 @@
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
 import logging
+import gobject
 from gettext import gettext as _
 import gtk
 
@@ -182,17 +183,22 @@ class InvitePalette(Palette):
 return
 
 class ActivitiesTray(HTray):
-def __init__(self):
+def __init__(self, frame):
 HTray.__init__(self)
 
+self._frame = frame
 self._buttons = {}
 self._invite_to_item = {}
+self._tabbing_activity = None
 
 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._home_model.connect('tabbing-activity-changed',
+ self.__tabbing_activity_changed_cb)
+self._frame.connect('notify::unobscured', self.__frame_notify_unobscured_cb)
 
 self._invites = shellmodel.get_instance().get_invites()
 for invite in self._invites:
@@ -200,6 +206,18 @@ class ActivitiesTray(HTray):
 self._invites.connect('invite-added', self.__invite_added_cb)
 self._invites.connect('invite-removed', self.__invite_removed_cb)
 
+def __update_tabbing_palette_position(self, *args):
+if self._tabbing_activity and self._frame.props.unobscured:
+button = self._buttons[self._tabbing_activity.get_activity_id()]
+palette = button.get_palette()
+if palette:
+# FIXME: This should probably go into the invoker code.
+# This needs to be done because the button may have moved.
+palette._update_position()
+palette.queue_draw()
+button.queue_draw()
+return False
+
 def __activity_added_cb(self, home_model, home_activity):
 logging.debug('__activity_added_cb: %r' % home_activity)
 if self.get_children():
@@ -212,6 +230,8 @@ class ActivitiesTray(HTray):
 self._buttons[home_activity.get_activity_id()] = button
 button.connect('clicked', self.__activity_clicked_cb, home_activity)
 button.show()
+
+gobject.idle_add(self.__update_tabbing_palette_position)
 
 def __activity_removed_cb(self, home_model, home_activity):
 logging.debug('__activity_removed_cb: %r' % home_activity)
@@ -219,10 +239,29 @@ class ActivitiesTray(HTray):
 self.remove_item(button)
 del self._buttons[home_activity.get_activity_id()]
 
+gobject.idle_add(self.__update_tabbing_palette_position)
+
 def __activity_changed_cb(self, home_model, home_activity):
 logging.debug('__activity_changed_cb: %r' % home_activity)
 button = self._buttons[home_activity.get_activity_id()]
 button.props.active = True
+
+def __tabbing_activity_changed_cb(self, home_model, tabbing_activity):
+logging.debug('__tabbing_activity_changed_cb: %r' % tabbing_activity)
+
+if self._tabbing_activity:
+button = self._buttons[self._tabbing_activity.get_activity_id()]
+palette = button.get_palette()
+if palette:
+palette.popdown(True)
+
+self._tabbing_activity = tabbing_activity
+# popup may only happen here if the frame is shown completely
+if self._frame.props.unobscured and self._tabbing_activity:
+button = self._buttons[self._tabbing_activity.get_activity_id()]
+palette = button.get_palette()
+if palette:
+palette.popup(True)
 
 def __activity_clicked_cb(self, button, home_activity):
 if button.props.active:
@@ -238,6 +277,23 @@ class ActivitiesTray(HTray):
 def __invite_removed_cb(self, invites, invite):
 self._remove_invite(invite)
 
+def __frame_notify_unobscured_cb(self, *args):
+logging.debug('ActivitiesTray.__frame_notify_unobscured_cb')
+
+if not self._tabbing_activity:
+return
+
+button = self._buttons[self._tabbing_activity.get_activity_id()]
+palette = button.get_palette()
+if not palette:
+return
+
+if self._frame.props.unobscured:
+palette.popup(True)
+self._popup_palette = None
+else:
+palette.popdown(True)
+
 def _add_invite(self, invite):
 mesh = shellmodel.get_instance().get_mesh()
 activity_model = mesh.get_activity(

[sugar] [PATCH 4/5] Handle the key events for tabbing.

2008-06-04 Thread Benjamin Berg
This adds the neccessary keyboard handling, grabbing the input, switching to
the new activity, or canceling the operation. It is also responsible for
showing the frame during the tabbing operation.
---
 src/view/keyhandler.py |   96 +++-
 1 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/src/view/keyhandler.py b/src/view/keyhandler.py
index 306bb21..8dee50c 100644
--- a/src/view/keyhandler.py
+++ b/src/view/keyhandler.py
@@ -32,6 +32,10 @@ _BRIGHTNESS_STEP = 2
 _VOLUME_STEP = 10
 _BRIGHTNESS_MAX = 15
 _VOLUME_MAX = 100
+# The modifier used for tabbing. Should the shortcuts ever be made user
+# configurable, then some code to figure out the apropriate modifier is
+# needed instead of hardcoding it.
+_TABBING_MODIFIER = gtk.gdk.MOD1_MASK
 
 _actions_table = {
 'F1' : 'zoom_mesh',
@@ -77,10 +81,13 @@ class KeyHandler(object):
 self._keycode_pressed = 0
 self._keystate_pressed = 0
 self._speech_proxy = None
+self._tabbing_windows = False
 
 self._key_grabber = KeyGrabber()
 self._key_grabber.connect('key-pressed',
   self._key_pressed_cb)
+self._key_grabber.connect('key-released',
+  self._key_released_cb)
 
 for key in _actions_table.keys():
 self._key_grabber.grab(key)
@@ -131,15 +138,75 @@ class KeyHandler(object):
 self._get_speech_proxy().SayText(text, reply_handler=lambda: None, \
 error_handler=self._on_speech_err)
 
+def _window_tabbing(self, direction):
+if not self._tabbing_windows:
+logging.debug('Grabing the input.')
+
+screen = gtk.gdk.screen_get_default()
+window = screen.get_root_window() 
+keyboard_grab_result = gtk.gdk.keyboard_grab(window)
+pointer_grab_result = gtk.gdk.pointer_grab(window)
+
+self._tabbing_windows = (keyboard_grab_result == gtk.gdk.GRAB_SUCCESS and
+ pointer_grab_result == gtk.gdk.GRAB_SUCCESS)
+
+# Now test that the modifier is still active to prevent race
+# conditions. We also test if one of the grabs failed.
+mask = window.get_pointer()[2]
+if not self._tabbing_windows or not (mask & _TABBING_MODIFIER):
+logging.debug('Releasing grabs again.')
+
+if keyboard_grab_result != gtk.gdk.GRAB_SUCCESS:
+gtk.gdk.keyboard_ungrab()
+if pointer_grab_result != gtk.gdk.GRAB_SUCCESS:
+gtk.gdk.pointer_ungrab()
+self._tabbing_windows = False
+
+if self._tabbing_windows:
+# We have the grab, so show the frame.
+shell = view.Shell.get_instance()
+frame = shell.get_frame()
+frame.show(frame.MODE_NON_INTERACTIVE)
+
+if direction == 1:
+shell.tabbing_next_activity()
+else:
+shell.tabbing_previous_activity()
+
+return self._tabbing_windows
+
+def _stop_window_tabbing(self, cancel=True):
+# Some useless key was pressed, or  released.
+if not self._tabbing_windows:
+return
+
+logging.debug('Releasing grabs again.')
+gtk.gdk.keyboard_ungrab()
+gtk.gdk.pointer_ungrab()
+
+shell = view.Shell.get_instance()
+frame = shell.get_frame()
+
+if not cancel:
+shell.tabbing_activate_current()
+else:
+shell.tabbing_cancel()
+
+frame.hide()
+
+self._tabbing_windows = False
+
 def handle_say_text(self):
 clipboard = gtk.clipboard_get(selection="PRIMARY")
 clipboard.request_text(self._primary_selection_cb)
 
 def handle_previous_window(self):
-view.Shell.get_instance().activate_previous_activity()
+if not self._window_tabbing(-1):
+view.Shell.get_instance().activate_previous_activity()
 
 def handle_next_window(self):
-view.Shell.get_instance().activate_next_activity()
+if not self._window_tabbing(1):
+view.Shell.get_instance().activate_next_activity()
 
 def handle_close_window(self):
 view.Shell.get_instance().close_current_activity()
@@ -218,9 +285,34 @@ class KeyHandler(object):
 self._keystate_pressed = state
 
 action = _actions_table[key]
+if self._tabbing_windows:
+# Only accept window tabbing events, everything else
+# cancels the tabbing operation.
+if not action in ["next_window", "previous_window"]:
+self._stop_window_tabbing(True)
+return True
+
 method = getattr(self, 'handle_' + action)
 method()
 
 return True
+else:
+# If this is not a registered

[sugar] [PATCH 3/5] Add some wrappers around the functions that are exported by the HomeModel

2008-06-04 Thread Benjamin Berg
---
 src/view/Shell.py |   19 +++
 1 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/src/view/Shell.py b/src/view/Shell.py
index 875e9a5..b2b7b84 100644
--- a/src/view/Shell.py
+++ b/src/view/Shell.py
@@ -219,6 +219,25 @@ class Shell(gobject.GObject):
 self.take_activity_screenshot()
 next_activity.get_window().activate(gtk.get_current_event_time())
 
+def tabbing_previous_activity(self):
+home_model = self._model.get_home()
+home_model.tabbing_previous_activity()
+
+def tabbing_next_activity(self):
+home_model = self._model.get_home()
+home_model.tabbing_next_activity()
+
+def tabbing_cancel(self):
+home_model = self._model.get_home()
+home_model.tabbing_cancel()
+
+def tabbing_activate_current(self):
+home_model = self._model.get_home()
+activity = home_model.get_tabbing_activity()
+if activity:
+activity.get_window().activate(1)
+home_model.tabbing_cancel()
+
 def close_current_activity(self):
 if self._model.get_zoom_level() != shellmodel.ShellModel.ZOOM_ACTIVITY:
 return
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 2/5] Add functionallity for tabbing trough activities.

2008-06-04 Thread Benjamin Berg
This commits adds some functions and properties to keep track of the
activities when the user is tabbing.
---
 src/model/homemodel.py |   61 
 1 files changed, 61 insertions(+), 0 deletions(-)
diff --git a/src/model/homemodel.py b/src/model/homemodel.py
index 5538f84..032f019 100644
--- a/src/model/homemodel.py
+++ b/src/model/homemodel.py
@@ -53,6 +53,9 @@ class HomeModel(gobject.GObject):
([gobject.TYPE_PYOBJECT])),
 'pending-activity-changed': (gobject.SIGNAL_RUN_FIRST,
  gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+'tabbing-activity-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
  ([gobject.TYPE_PYOBJECT]))
 }
 
@@ -62,6 +65,7 @@ class HomeModel(gobject.GObject):
 self._activities = []
 self._active_activity = None
 self._pending_activity = None
+self._tabbing_activity = None
 
 screen = wnck.screen_get_default()
 screen.connect('window-opened', self._window_opened_cb)
@@ -126,6 +130,42 @@ class HomeModel(gobject.GObject):
 """
 return self._active_activity
 
+def tabbing_previous_activity(self):
+activities = self._get_activities_with_window()
+if len(activities) == 0:
+return
+
+activity = self._tabbing_activity
+if activity is None:
+activity = self._pending_activity
+
+i = activities.index(activity)
+if i - 1 >= 0:
+self._set_tabbing_activity(activities[i - 1])
+else:
+self._set_tabbing_activity(activities[len(activities) - 1])
+
+def tabbing_next_activity(self):
+activities = self._get_activities_with_window()
+if len(activities) == 0:
+return
+
+activity = self._tabbing_activity
+if activity is None:
+activity = self._pending_activity
+
+i = activities.index(activity)
+if i + 1 < len(activities):
+self._set_tabbing_activity(activities[i + 1])
+else:
+self._set_tabbing_activity(activities[0])
+
+def tabbing_cancel(self):
+self._set_tabbing_activity(None)
+
+def get_tabbing_activity(self):
+return self._tabbing_activity
+
 def _set_active_activity(self, home_activity):
 if self._active_activity == home_activity:
 return
@@ -146,6 +186,13 @@ class HomeModel(gobject.GObject):
 self._active_activity = home_activity
 self.emit('active-activity-changed', self._active_activity)
 
+def _set_tabbing_activity(self, tabbing_activity):
+if self._tabbing_activity == tabbing_activity:
+return
+
+self._tabbing_activity = tabbing_activity
+self.emit('tabbing-activity-changed', self._tabbing_activity)
+
 def __iter__(self): 
 return iter(self._activities)
 
@@ -243,6 +290,20 @@ class HomeModel(gobject.GObject):
 logging.error('No activities are running')
 self._set_pending_activity(None)
 
+if home_activity == self._tabbing_activity:
+# Find a new tabbing activity
+activities = self._get_activities_with_window()
+if len(activities) <= 1:
+# There is no other activity to tab to ...
+self._set_tabbing_activity(None)
+
+i = activities.index(self._tabbing_activity)
+if i + 1 < len(activities):
+self._set_tabbing_activity(activities[i + 1])
+else:
+# Do not wrap, but instead select the last activity again
+self._set_tabbing_activity(activities[i - 1])
+
 self.emit('activity-removed', home_activity)
 self._activities.remove(home_activity)
 
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH 1/5] Add an "unobscured" property that is set when the frame is fully visible.

2008-06-04 Thread Benjamin Berg
This commit switches the Frame to be a GObject. By doing this the gobject
property system can be used to notify interested parties when the frame
becomes fully visible. This is needed, so that the ActivitiesTray can
show the palette of an activity as soon as the frame is fully visible.
---
 src/view/frame/frame.py |   21 +++--
 1 files changed, 19 insertions(+), 2 deletions(-)
diff --git a/src/view/frame/frame.py b/src/view/frame/frame.py
index 6009e7f..9e5af71 100644
--- a/src/view/frame/frame.py
+++ b/src/view/frame/frame.py
@@ -89,12 +89,19 @@ class _KeyListener(object):
 else:
 self._frame.show(Frame.MODE_KEYBOARD)
 
-class Frame(object):
+class Frame(gobject.GObject):
 MODE_MOUSE= 0
 MODE_KEYBOARD = 1
 MODE_NON_INTERACTIVE = 2
+__gtype_name__ = "SugarFrame"
+__gproperties__ = {
+'unobscured' : (bool, None, None, False,
+gobject.PARAM_READABLE),
+}
 
 def __init__(self):
+gobject.GObject.__init__(self)
+
 self.mode = None
 
 self._palette_group = palettegroup.get_group('frame')
@@ -106,6 +113,7 @@ class Frame(object):
 self._bottom_panel = None
 
 self.current_position = 0.0
+self._unobscured = False
 self._animator = None
 
 self._event_area = EventArea()
@@ -248,6 +256,11 @@ class Frame(object):
 self._move_panel(self._right_panel, self.current_position,
  screen_w, 0, screen_w - self._right_panel.size, 0)
 
+unobscured = (self.current_position == 1.0)
+if unobscured != self._unobscured:
+self._unobscured = unobscured
+self.notify('unobscured')
+
 def _size_changed_cb(self, screen):
 self._update_position()
 
@@ -274,7 +287,11 @@ class Frame(object):
 
 def _enter_corner_cb(self, event_area):
 self._mouse_listener.mouse_enter()
-
+
+def do_get_property(self, pspec):
+if pspec.name == 'unobscured':
+return self._unobscured
+
 def notify_key_press(self):
 self._key_listener.key_press()
 
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH] Add is_modifier and is_special_modifier functions to SugarKeyGrabber.

2008-06-04 Thread Benjamin Berg
This commit adds two new functions to SugarKeyGrabber that allow to check
whether a modifier key has been pressed or released. This is needed to stop
tabbing when  is released again.
---
 src/sugar/_sugarext.defs  |   19 +
 src/sugar/sugar-key-grabber.c |   59 +
 src/sugar/sugar-key-grabber.h |6 
 3 files changed, 84 insertions(+), 0 deletions(-)
diff --git a/src/sugar/_sugarext.defs b/src/sugar/_sugarext.defs
index 1c9812e..dc1d3ca 100644
--- a/src/sugar/_sugarext.defs
+++ b/src/sugar/_sugarext.defs
@@ -100,6 +100,25 @@
   )
 )
 
+(define-method is_modifier
+  (of-object "SugarKeyGrabber")
+  (c-name "sugar_key_grabber_is_modifier")
+  (return-type "gboolean")
+  (parameters
+'("guint" "keycode")
+  )
+)
+
+(define-method is_specific_modifier
+  (of-object "SugarKeyGrabber")
+  (c-name "sugar_key_grabber_is_specific_modifier")
+  (return-type "gboolean")
+  (parameters
+'("guint" "keycode")
+'("guint" "mask")
+  )
+)
+
 ;; From sexy-icon-entry.h
 
 (define-function sexy_icon_entry_get_type
diff --git a/src/sugar/sugar-key-grabber.c b/src/sugar/sugar-key-grabber.c
index baddab5..ed0cf9f 100644
--- a/src/sugar/sugar-key-grabber.c
+++ b/src/sugar/sugar-key-grabber.c
@@ -217,3 +217,62 @@ sugar_key_grabber_grab(SugarKeyGrabber *grabber, const char *key)
 
 	grabber->keys = g_list_append(grabber->keys, keyinfo);	
 }
+
+gboolean
+sugar_key_grabber_is_modifier(SugarKeyGrabber *grabber, guint keycode)
+{
+	Display *xdisplay;
+	XModifierKeymap *modmap;
+	gint size, i;
+	gboolean is_modifier = FALSE;
+
+	xdisplay = gdk_x11_drawable_get_xdisplay(GDK_DRAWABLE (grabber->root));
+
+	modmap = XGetModifierMapping (xdisplay);
+	
+	size = 8 * modmap->max_keypermod;
+	for (i = 0; i < size; i++) {
+		if (keycode == modmap->modifiermap[i]) {
+			is_modifier = TRUE;
+			break;
+		}
+	}
+	
+	XFreeModifiermap (modmap);
+	
+	return is_modifier;
+}
+
+
+gboolean
+sugar_key_grabber_is_specific_modifier(SugarKeyGrabber *grabber, guint keycode, guint mask)
+{
+	Display *xdisplay;
+	XModifierKeymap *modmap;
+	gint start, end, i, mod_index;
+	gboolean is_modifier = FALSE;
+
+	xdisplay = gdk_x11_drawable_get_xdisplay(GDK_DRAWABLE (grabber->root));
+
+	modmap = XGetModifierMapping (xdisplay);
+	
+	mod_index = 0;
+	mask = mask >> 1;
+	while (mask != 0) {
+		mask = mask >> 1;
+		mod_index += 1;
+	}
+	
+	start = mod_index * modmap->max_keypermod;
+	end = (mod_index + 1) * modmap->max_keypermod;
+	for (i = start; i < end; i++) {
+		if (keycode == modmap->modifiermap[i]) {
+			is_modifier = TRUE;
+			break;
+		}
+	}
+	
+	XFreeModifiermap (modmap);
+	
+	return is_modifier;
+}
diff --git a/src/sugar/sugar-key-grabber.h b/src/sugar/sugar-key-grabber.h
index 5b734e7..cf6efe2 100644
--- a/src/sugar/sugar-key-grabber.h
+++ b/src/sugar/sugar-key-grabber.h
@@ -60,6 +60,12 @@ void sugar_key_grabber_grab		(SugarKeyGrabber *grabber,
 char*sugar_key_grabber_get_key  (SugarKeyGrabber *grabber,
 	 guintkeycode,
 	 guintstate);
+gboolean sugar_key_grabber_is_modifier  (SugarKeyGrabber *grabber,
+ guintkeycode);
+gboolean sugar_key_grabber_is_specific_modifier (SugarKeyGrabber *grabber,
+ guintkeycode,
+ guintmask);
+
 
 G_END_DECLS
 
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar


[sugar] [PATCH] Change tabbing to show a preview by opening the frame

2008-06-04 Thread Benjamin Berg
Hello,

Some patches that implement nicer tabbing will follow. With the patches
applied, pressing +Tab will show the frame. The activity that will
be selected as soon as  is released again is shown by popping up
its palette.


Unfortunately there are a couple of issues with palettes.

One issue is that that at first only the primary palette is shown, but
after a while the secondary palette will pop up (as the timeout is
started automatically).

Then I am accessing the _update_position function of the Palette
directly, to handle disappearing activities properly.

The third thing I noticed is that there is a race condition in the
Palette (which I think is my fault ...). I'll need to have a look at how
to fix this.
(The reason for the race is the code that delays the "popup" signal
until the window has been mapped. IIRC that was needed so the gab
drawing would work properly.)


Benjamin


signature.asc
Description: This is a digitally signed message part
___
Sugar mailing list
Sugar@lists.laptop.org
http://lists.laptop.org/listinfo/sugar