From a6ef49b731cfc570c4f7fd6d4f9d500fb95995de Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Tue, 15 Aug 2006 12:20:09 +0200 Subject: [PATCH 01/18] Make HomeWindow a canvas. Modify screen resolution to match what Walter said. --- shell/HomeWindow.py | 164 ++++---------------------------------- shell/session/Emulator.py | 2 +- 2 files changed, 16 insertions(+), 150 deletions(-) diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index 886068c2..7e939568 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -1,165 +1,31 @@ -from gettext import gettext as _ - import gtk -import wnck +import goocanvas -from sugar.activity import ActivityFactory -from ActivitiesModel import ActivitiesModel -from sugar.presence.PresenceService import PresenceService +class Model(goocanvas.CanvasModelSimple): + def __init__(self): + goocanvas.CanvasModelSimple.__init__(self) -class NewActivityButton(gtk.MenuToolButton): - def __init__(self, home): - gtk.MenuToolButton.__init__(self, None, _('New Activity')) + root = self.get_root_item() - self._home = home - - self.set_menu(gtk.Menu()) - self.connect("show-menu", self.__show_menu_cb) - - def __show_menu_cb(self, button): - menu = gtk.Menu() - - for module in self._home.list_activities(): - if module.get_show_launcher(): - item = gtk.MenuItem(module.get_name(), False) - activity_id = module.get_id() - item.connect('activate', - self.__menu_item_activate_cb, activity_id) - menu.append(item) - item.show() - - self.set_menu(menu) - - def __menu_item_activate_cb(self, item, activity_id): - self._home.create(activity_id) + item = goocanvas.Rect(x=0, y=0, width=693, height=520, + fill_color="red") -class Toolbar(gtk.Toolbar): - def __init__(self, shell): - gtk.Toolbar.__init__(self) - - new_activity_button = NewActivityButton(shell) - self.insert(new_activity_button, -1) - new_activity_button.show() - -class ActivitiesGrid(gtk.VBox): - def __init__(self, shell, model): - gtk.VBox.__init__(self, shell) - - self._shell = shell - self._buttons = {} - - for activity in model: - self._add(activity) - model.connect('activity-added', self.__activity_added_cb) - model.connect('activity-removed', self.__activity_removed_cb) - - def __activity_added_cb(self, model, activity): - self._add(activity) - - def __activity_removed_cb(self, model, activity): - self._remove(window) - - def _remove(self, activity): - button = self._buttons[activity.get_id()] - self.remove(button) - - def _add(self, activity): - button = gtk.Button(activity.get_title()) - button.connect('clicked', self.__button_clicked_cb, activity) - self.pack_start(button, False) - button.show() - - self._buttons[activity.get_id()] = button - - def __button_clicked_cb(self, button, info): - self._shell.join_activity(info.get_service()) - -class TasksGrid(gtk.VBox): - def __init__(self, home): - gtk.VBox.__init__(self) - - self._home = home - self._buttons = {} - - screen = wnck.screen_get_default() - for window in screen.get_windows(): - if not window.is_skip_tasklist(): - self._add(window) - screen.connect('window_opened', self.__window_opened_cb) - screen.connect('window_closed', self.__window_closed_cb) - - def __window_opened_cb(self, screen, window): - if not window.is_skip_tasklist(): - self._add(window) - - def __window_closed_cb(self, screen, window): - if not window.is_skip_tasklist(): - self._remove(window) - - def _remove(self, window): - button = self._buttons[window.get_xid()] - self.remove(button) - - def __window_name_changed_cb(self, window, button): - button.set_label(window.get_name()) - - def _add(self, window): - button = gtk.Button(window.get_name()) - window.connect('name-changed', self.__window_name_changed_cb, button) - button.connect('clicked', self.__button_clicked_cb, window) - self.pack_start(button, False) - button.show() - - self._buttons[window.get_xid()] = button - - def __button_clicked_cb(self, button, window): - self._home.activate(window) + root.add_child(item) class HomeWindow(gtk.Window): def __init__(self, shell): gtk.Window.__init__(self) - - self._shell = shell self.connect('realize', self.__realize_cb) - - vbox = gtk.VBox(False, 6) - vbox.set_border_width(24) - toolbar = Toolbar(self) - vbox.pack_start(toolbar, False) - toolbar.show() + canvas = goocanvas.CanvasView() + canvas_model = Model() + canvas.set_bounds(0, 0, 693, 520) + self.add(canvas) + canvas.show() - label = gtk.Label('Open activities:') - label.set_alignment(0.0, 0.5) - vbox.pack_start(label, False) - label.show() - - grid = TasksGrid(self) - vbox.pack_start(grid) - grid.show() - - label = gtk.Label('Shared activities:') - label.set_alignment(0.0, 0.5) - vbox.pack_start(label, False) - label.show() - - model = ActivitiesModel(shell.get_registry()) - grid = ActivitiesGrid(shell, model) - vbox.pack_start(grid) - grid.show() - - self.add(vbox) - vbox.show() + canvas.set_model(canvas_model) + canvas.set_size_request(693, 520) def __realize_cb(self, window): self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) - - def list_activities(self): - return self._shell.get_registry().list_activities() - - def create(self, activity_name): - self._shell.start_activity(activity_name) - - def activate(self, activity_window): - activity_window.activate(gtk.get_current_event_time()) diff --git a/shell/session/Emulator.py b/shell/session/Emulator.py index 2bb2b3ac..1e7bced3 100644 --- a/shell/session/Emulator.py +++ b/shell/session/Emulator.py @@ -43,7 +43,7 @@ class XephyrProcess(Process): class XnestProcess(Process): def __init__(self): self._display = get_display_number() - cmd = 'Xnest :%d -ac -geometry 640x480' % (self._display) + cmd = 'Xnest :%d -ac -geometry 693x520' % (self._display) Process.__init__(self, cmd) def get_name(self): From 115eefb4c2bb8154f6834caa62be2bcba80f70dc Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 16 Aug 2006 19:55:13 +0200 Subject: [PATCH 02/18] Implement a canvas element that can draw svg icons with different colors. --- shell/HomeWindow.py | 5 +++++ sugar/canvas/IconItem.py | 40 ++++++++++++++++++++++++++++++++++++++++ sugar/canvas/Makefile.am | 4 ++++ sugar/canvas/__init__.py | 0 4 files changed, 49 insertions(+) create mode 100644 sugar/canvas/IconItem.py create mode 100644 sugar/canvas/Makefile.am create mode 100644 sugar/canvas/__init__.py diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index ee567818..1609fe78 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -1,6 +1,8 @@ import gtk import goocanvas +from sugar.canvas.IconItem import IconItem + class Model(goocanvas.CanvasModelSimple): def __init__(self): goocanvas.CanvasModelSimple.__init__(self) @@ -9,7 +11,10 @@ class Model(goocanvas.CanvasModelSimple): item = goocanvas.Rect(x=0, y=0, width=693, height=520, fill_color="red") + root.add_child(item) + item = IconItem('buddy') + #item.set_color('blue') root.add_child(item) class HomeWindow(gtk.Window): diff --git a/sugar/canvas/IconItem.py b/sugar/canvas/IconItem.py new file mode 100644 index 00000000..54697b67 --- /dev/null +++ b/sugar/canvas/IconItem.py @@ -0,0 +1,40 @@ +import re + +import gtk +import goocanvas + +class IconItem(goocanvas.Image): + def __init__(self, icon_name): + goocanvas.Image.__init__(self) + + self._icon_name = icon_name + self._color = None + + # FIXME changing the icon color will cause + # the svg to be read in memory and rendered + # two times. + self._update_pixbuf() + + def set_parent(self, parent): + goocanvas.Image.set_parent(self, parent) + + def set_color(self, color): + self._color = color + self._update_pixbuf() + + def _update_pixbuf(self): + theme = gtk.icon_theme_get_default() + info = theme.lookup_icon(self._icon_name, 48, 0) + icon_file = open(info.get_filename(), 'r') + data = icon_file.read() + icon_file.close() + + if self._color != None: + style = '.icon-color {fill: %s;}' % self._color + data = re.sub('\.icon-color \{.*\}', style, data) + + loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/svg+xml') + loader.write(data) + loader.close() + + self.set_property('pixbuf', loader.get_pixbuf()) diff --git a/sugar/canvas/Makefile.am b/sugar/canvas/Makefile.am new file mode 100644 index 00000000..9120ba25 --- /dev/null +++ b/sugar/canvas/Makefile.am @@ -0,0 +1,4 @@ +sugardir = $(pythondir)/sugar/canvas +sugar_PYTHON = \ + __init__.py \ + IconItem.py diff --git a/sugar/canvas/__init__.py b/sugar/canvas/__init__.py new file mode 100644 index 00000000..e69de29b From a963c330780da5053c5030bde17138ed2a28d0cb Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 16 Aug 2006 20:34:33 +0200 Subject: [PATCH 03/18] Change emulator resolution to 800x600. Change canvas resolution to 1200x900, scale it down in the emulator. --- shell/HomeWindow.py | 9 +++++---- shell/session/Emulator.py | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index 1609fe78..014ea1d6 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -9,12 +9,12 @@ class Model(goocanvas.CanvasModelSimple): root = self.get_root_item() - item = goocanvas.Rect(x=0, y=0, width=693, height=520, + item = goocanvas.Rect(x=0, y=0, width=1200, height=900, fill_color="red") root.add_child(item) item = IconItem('buddy') - #item.set_color('blue') + item.set_color('blue') root.add_child(item) class HomeWindow(gtk.Window): @@ -27,12 +27,13 @@ class HomeWindow(gtk.Window): canvas = goocanvas.CanvasView() canvas_model = Model() - canvas.set_bounds(0, 0, 693, 520) + canvas.set_bounds(0, 0, 1200, 900) + canvas.set_scale(float(800) / float(1200)) + canvas.set_size_request(800, 600) self.add(canvas) canvas.show() canvas.set_model(canvas_model) - canvas.set_size_request(693, 520) def __realize_cb(self, window): self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) diff --git a/shell/session/Emulator.py b/shell/session/Emulator.py index 1e7bced3..c18d92e4 100644 --- a/shell/session/Emulator.py +++ b/shell/session/Emulator.py @@ -30,7 +30,7 @@ def get_display_number(): class XephyrProcess(Process): def __init__(self): self._display = get_display_number() - cmd = 'Xephyr :%d -ac -screen 640x480' % (self._display) + cmd = 'Xephyr :%d -ac -screen 800x600' % (self._display) Process.__init__(self, cmd) def get_name(self): @@ -43,7 +43,7 @@ class XephyrProcess(Process): class XnestProcess(Process): def __init__(self): self._display = get_display_number() - cmd = 'Xnest :%d -ac -geometry 693x520' % (self._display) + cmd = 'Xnest :%d -ac -geometry 800x600' % (self._display) Process.__init__(self, cmd) def get_name(self): From 47f25f234ee9775b0c6723312b05ca69d3709449 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 16 Aug 2006 22:01:43 +0200 Subject: [PATCH 04/18] Home page background --- shell/HomeWindow.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index 014ea1d6..47cd7f71 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -3,19 +3,32 @@ import goocanvas from sugar.canvas.IconItem import IconItem +class Background(goocanvas.Group): + def __init__(self): + goocanvas.Group.__init__(self) + + item = goocanvas.Rect(width=1200, height=900, + fill_color="#4f4f4f") + self.add_child(item) + + item = goocanvas.Rect(x=50, y=50, width=1100, height=800, + line_width=0, fill_color="#d8d8d8", + radius_x=30, radius_y=30) + self.add_child(item) + + item = goocanvas.Text(text="My Activities", + x=60, y=10, fill_color="white", + font="Sans 21") + self.add_child(item) + class Model(goocanvas.CanvasModelSimple): def __init__(self): goocanvas.CanvasModelSimple.__init__(self) root = self.get_root_item() - item = goocanvas.Rect(x=0, y=0, width=1200, height=900, - fill_color="red") - root.add_child(item) - - item = IconItem('buddy') - item.set_color('blue') - root.add_child(item) + background = Background() + root.add_child(background) class HomeWindow(gtk.Window): def __init__(self, shell): From dae3d2be2b99cbbb59a3aff1ab00fb0b8705a3ff Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 16 Aug 2006 23:20:22 +0200 Subject: [PATCH 05/18] Some work to implement the activity bar --- activities/browser/browser.activity | 1 + shell/ActivityRegistry.py | 12 +++++++ shell/HomeWindow.py | 20 +++++++++-- sugar/canvas/IconItem.py | 52 ++++++++++++++++++----------- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/activities/browser/browser.activity b/activities/browser/browser.activity index dcf4e438..6a1d3cdf 100644 --- a/activities/browser/browser.activity +++ b/activities/browser/browser.activity @@ -1,6 +1,7 @@ [Activity] name = Web id = com.redhat.Sugar.BrowserActivity +icon = browser-activity python_module = browser.BrowserActivity.BrowserActivity default_type = _web_olpc._udp show_launcher = yes diff --git a/shell/ActivityRegistry.py b/shell/ActivityRegistry.py index acf379c9..5ff0059f 100644 --- a/shell/ActivityRegistry.py +++ b/shell/ActivityRegistry.py @@ -8,6 +8,7 @@ class ActivityModule: def __init__(self, name, activity_id, directory): self._name = name + self._icon = None self._id = activity_id self._directory = directory self._show_launcher = False @@ -20,6 +21,14 @@ class ActivityModule: """Get the activity identifier""" return self._id + def get_icon(self): + """Get the activity icon name""" + return self._icon + + def set_icon(self, icon): + """Set the activity icon name""" + self._icon = icon + def get_directory(self): """Get the path to activity directory.""" return self._directory @@ -97,6 +106,9 @@ class ActivityRegistry: if cp.has_option('Activity', 'show_launcher'): module.set_show_launcher(True) + if cp.has_option('Activity', 'icon'): + module.set_icon(cp.get('Activity', 'icon')) + module.set_default_type(default_type) return True diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index 47cd7f71..d44add60 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -3,6 +3,19 @@ import goocanvas from sugar.canvas.IconItem import IconItem +class ActivityBar(goocanvas.Group): + def __init__(self, registry): + goocanvas.Group.__init__(self) + + for activity in registry.list_activities(): + if activity.get_show_launcher(): + self.add_activity(activity) + + def add_activity(self, activity): + item = IconItem(icon_name=activity.get_icon(), + color='white', width=42, height=42) + self.add_child(item) + class Background(goocanvas.Group): def __init__(self): goocanvas.Group.__init__(self) @@ -22,7 +35,7 @@ class Background(goocanvas.Group): self.add_child(item) class Model(goocanvas.CanvasModelSimple): - def __init__(self): + def __init__(self, shell): goocanvas.CanvasModelSimple.__init__(self) root = self.get_root_item() @@ -30,6 +43,9 @@ class Model(goocanvas.CanvasModelSimple): background = Background() root.add_child(background) + activity_bar = ActivityBar(shell.get_registry()) + root.add_child(activity_bar) + class HomeWindow(gtk.Window): def __init__(self, shell): gtk.Window.__init__(self) @@ -39,7 +55,7 @@ class HomeWindow(gtk.Window): self.connect('realize', self.__realize_cb) canvas = goocanvas.CanvasView() - canvas_model = Model() + canvas_model = Model(shell) canvas.set_bounds(0, 0, 1200, 900) canvas.set_scale(float(800) / float(1200)) canvas.set_size_request(800, 600) diff --git a/sugar/canvas/IconItem.py b/sugar/canvas/IconItem.py index 54697b67..66ec1934 100644 --- a/sugar/canvas/IconItem.py +++ b/sugar/canvas/IconItem.py @@ -1,28 +1,44 @@ import re +import gobject import gtk import goocanvas +import rsvg + +class IconView(goocanvas.ItemViewSimple): + def __init__(self, handle, canvas_view, parent_view): + goocanvas.SimpleItemView.__init__(self, canvas_view, parent_view) + self._handle = handle + + def do_paint(self, cr, bounds, scale): + self._handle.render_cairo(cr) + return self.bounds class IconItem(goocanvas.Image): - def __init__(self, icon_name): - goocanvas.Image.__init__(self) + __gproperties__ = { + 'icon-name': (str, None, None, None, gobject.PARAM_READWRITE), + 'color': (str, None, None, None, gobject.PARAM_READWRITE) + } - self._icon_name = icon_name - self._color = None + def __init__(self, **kwargs): + goocanvas.Image.__init__(self, **kwargs) - # FIXME changing the icon color will cause - # the svg to be read in memory and rendered - # two times. - self._update_pixbuf() + def do_set_property(self, pspec, value): + if pspec.name == 'icon-name': + self._icon_name = value + elif pspec.name == 'color': + self._color = value + else: + raise AttributeError, 'unknown property %s' % pspec.name - def set_parent(self, parent): - goocanvas.Image.set_parent(self, parent) + def do_get_property(self, pspec): + if pspec.name == 'icon-name': + return self._icon_name + if pspec.name == 'color': + return self._color - def set_color(self, color): - self._color = color - self._update_pixbuf() - - def _update_pixbuf(self): + def create_view(self, canvas_view, parent_view): + print 'Create view' theme = gtk.icon_theme_get_default() info = theme.lookup_icon(self._icon_name, 48, 0) icon_file = open(info.get_filename(), 'r') @@ -33,8 +49,6 @@ class IconItem(goocanvas.Image): style = '.icon-color {fill: %s;}' % self._color data = re.sub('\.icon-color \{.*\}', style, data) - loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/svg+xml') - loader.write(data) - loader.close() + handle = rsvg.Handle(data=data) - self.set_property('pixbuf', loader.get_pixbuf()) + return IconView(handle, canvas_view, parent_view) From 7990bc0d3126408e2728e3f41745e1332182de5b Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Thu, 17 Aug 2006 10:32:59 +0200 Subject: [PATCH 06/18] Get activity bar and activity creation to work --- shell/HomeWindow.py | 32 ++++++++++++++++--- sugar/canvas/IconItem.py | 69 +++++++++++++++++++--------------------- sugar/util.py | 14 ++++++++ 3 files changed, 74 insertions(+), 41 deletions(-) diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index d44add60..1449d0df 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -3,17 +3,27 @@ import goocanvas from sugar.canvas.IconItem import IconItem +class ActivityItem(IconItem): + def __init__(self, activity): + IconItem.__init__(self, activity.get_icon(), 'white', 30) + self._activity = activity + + def get_activity_id(self): + return self._activity.get_id() + class ActivityBar(goocanvas.Group): - def __init__(self, registry): + def __init__(self, shell): goocanvas.Group.__init__(self) + self._shell = shell + + registry = shell.get_registry() for activity in registry.list_activities(): if activity.get_show_launcher(): self.add_activity(activity) def add_activity(self, activity): - item = IconItem(icon_name=activity.get_icon(), - color='white', width=42, height=42) + item = ActivityItem(activity) self.add_child(item) class Background(goocanvas.Group): @@ -43,7 +53,8 @@ class Model(goocanvas.CanvasModelSimple): background = Background() root.add_child(background) - activity_bar = ActivityBar(shell.get_registry()) + activity_bar = ActivityBar(shell) + activity_bar.translate(50, 860) root.add_child(activity_bar) class HomeWindow(gtk.Window): @@ -55,14 +66,27 @@ class HomeWindow(gtk.Window): self.connect('realize', self.__realize_cb) canvas = goocanvas.CanvasView() + canvas_model = Model(shell) canvas.set_bounds(0, 0, 1200, 900) canvas.set_scale(float(800) / float(1200)) canvas.set_size_request(800, 600) + + canvas.connect("item_view_created", self.__item_view_created_cb) + self.add(canvas) canvas.show() canvas.set_model(canvas_model) + def __item_view_created_cb(self, view, item_view, item): + if isinstance(item, ActivityItem): + item_view.connect("button_press_event", + self.__activity_button_press_cb, + item.get_activity_id()) + + def __activity_button_press_cb(self, view, target, event, activity_id): + self._shell.start_activity(activity_id) + def __realize_cb(self, window): self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) diff --git a/sugar/canvas/IconItem.py b/sugar/canvas/IconItem.py index 66ec1934..7be096d1 100644 --- a/sugar/canvas/IconItem.py +++ b/sugar/canvas/IconItem.py @@ -3,52 +3,47 @@ import re import gobject import gtk import goocanvas -import rsvg -class IconView(goocanvas.ItemViewSimple): - def __init__(self, handle, canvas_view, parent_view): - goocanvas.SimpleItemView.__init__(self, canvas_view, parent_view) - self._handle = handle +from sugar.util import GObjectSingletonMeta - def do_paint(self, cr, bounds, scale): - self._handle.render_cairo(cr) - return self.bounds +class IconCache(gobject.GObject): + __metaclass__ = GObjectSingletonMeta -class IconItem(goocanvas.Image): - __gproperties__ = { - 'icon-name': (str, None, None, None, gobject.PARAM_READWRITE), - 'color': (str, None, None, None, gobject.PARAM_READWRITE) - } + def __init__(self): + gobject.GObject.__init__(self) + self._icons = {} - def __init__(self, **kwargs): - goocanvas.Image.__init__(self, **kwargs) - - def do_set_property(self, pspec, value): - if pspec.name == 'icon-name': - self._icon_name = value - elif pspec.name == 'color': - self._color = value - else: - raise AttributeError, 'unknown property %s' % pspec.name - - def do_get_property(self, pspec): - if pspec.name == 'icon-name': - return self._icon_name - if pspec.name == 'color': - return self._color - - def create_view(self, canvas_view, parent_view): - print 'Create view' + def _create_icon(self, name, color, size): theme = gtk.icon_theme_get_default() - info = theme.lookup_icon(self._icon_name, 48, 0) + info = theme.lookup_icon(name, size, 0) icon_file = open(info.get_filename(), 'r') data = icon_file.read() icon_file.close() - if self._color != None: - style = '.icon-color {fill: %s;}' % self._color + if color != None: + style = '.icon-color {fill: %s;}' % color data = re.sub('\.icon-color \{.*\}', style, data) - handle = rsvg.Handle(data=data) + loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/svg-xml') + loader.set_size(size, size) + loader.write(data) + loader.close() + + return loader.get_pixbuf() - return IconView(handle, canvas_view, parent_view) + def get_icon(self, name, color, size): + key = (name, color, size) + if self._icons.has_key(key): + return self._icons[key] + else: + icon = self._create_icon(name, color, size) + self._icons[key] = icon + return icon + +class IconItem(goocanvas.Image): + def __init__(self, icon_name, color, size, **kwargs): + goocanvas.Image.__init__(self, **kwargs) + + icon_cache = IconCache() + pixbuf = icon_cache.get_icon(icon_name, color, size) + self.set_property('pixbuf', pixbuf) diff --git a/sugar/util.py b/sugar/util.py index bfddf32b..9cb7d55a 100644 --- a/sugar/util.py +++ b/sugar/util.py @@ -4,6 +4,20 @@ import random import binascii import string +import gobject + +class GObjectSingletonMeta(gobject.GObjectMeta): + """GObject Singleton Metaclass""" + + def __init__(klass, name, bases, dict): + gobject.GObjectMeta.__init__(klass, name, bases, dict) + klass.__instance = None + + def __call__(klass, *args, **kwargs): + if klass.__instance is None: + klass.__instance = gobject.GObjectMeta.__call__(klass, *args, **kwargs) + return klass.__instance + def _stringify_sha(sha_hash): """Convert binary sha1 hash data into printable characters.""" print_sha = "" From e5ed8275a1f375da460b3a757994f39ba80c4f08 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Thu, 17 Aug 2006 11:47:41 +0200 Subject: [PATCH 07/18] Some work on the donut... --- shell/HomeWindow.py | 13 +++++++++++ sugar/canvas/DonutItem.py | 49 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 sugar/canvas/DonutItem.py diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index 1449d0df..9534a81c 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -2,6 +2,15 @@ import gtk import goocanvas from sugar.canvas.IconItem import IconItem +from sugar.canvas.DonutItem import DonutItem + +class TasksItem(DonutItem): + def __init__(self): + DonutItem.__init__(self, 200) + self.add_piece(30) + self.add_piece(30) + self.add_piece(30) + self.add_piece(10) class ActivityItem(IconItem): def __init__(self, activity): @@ -57,6 +66,10 @@ class Model(goocanvas.CanvasModelSimple): activity_bar.translate(50, 860) root.add_child(activity_bar) + tasks = TasksItem() + tasks.translate(600, 450) + root.add_child(tasks) + class HomeWindow(gtk.Window): def __init__(self, shell): gtk.Window.__init__(self) diff --git a/sugar/canvas/DonutItem.py b/sugar/canvas/DonutItem.py new file mode 100644 index 00000000..19c56f80 --- /dev/null +++ b/sugar/canvas/DonutItem.py @@ -0,0 +1,49 @@ +import math + +import goocanvas + +class PieceItem(goocanvas.Path): + def __init__(self, angle_start, angle_end, **kwargs): + goocanvas.Path.__init__(self, **kwargs) + self._angle_start = angle_start + self._angle_end = angle_end + + def construct(self): + r = self.get_parent().get_radius() + + data = 'M0,0 ' + + dx = r * math.cos(self._angle_start) + dy = - r * math.sin(self._angle_start) + + data += 'l%f,%f ' % (dx, dy) + + dx = r * math.cos(self._angle_end) + dy = - r * math.sin(self._angle_end) + + data += 'A%f,%f 0 0,0 %f,%f ' % (r, r, dx, dy) + + data += 'z' + + print data + + self.set_property('data', data) + +class DonutItem(goocanvas.Group): + def __init__(self, radius, **kwargs): + goocanvas.Group.__init__(self, **kwargs) + self._radius = radius + self._angle_start = 0 + + def add_piece(self, perc): + angle_end = self._angle_start + perc * 2 * math.pi / 100 + piece_item = PieceItem(self._angle_start, angle_end) + self._angle_start = angle_end + + # FIXME can't override set_parent on the + # PieceItem and there is no signal. + self.add_child(piece_item) + piece_item.construct() + + def get_radius(self): + return self._radius From fb829989c7c04d1d5063df3a2c64d28f1f541704 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Thu, 17 Aug 2006 12:09:45 +0200 Subject: [PATCH 08/18] Get tasks adding to work --- shell/HomeWindow.py | 31 +++++++++++++++++++++++++++---- sugar/canvas/DonutItem.py | 8 ++++++-- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index 9534a81c..f19f4e0b 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -1,5 +1,6 @@ import gtk import goocanvas +import wnck from sugar.canvas.IconItem import IconItem from sugar.canvas.DonutItem import DonutItem @@ -7,10 +8,32 @@ from sugar.canvas.DonutItem import DonutItem class TasksItem(DonutItem): def __init__(self): DonutItem.__init__(self, 200) - self.add_piece(30) - self.add_piece(30) - self.add_piece(30) - self.add_piece(10) + + self._items = {} + + screen = wnck.screen_get_default() + for window in screen.get_windows(): + if not window.is_skip_tasklist(): + self._add(window) + screen.connect('window_opened', self.__window_opened_cb) + screen.connect('window_closed', self.__window_closed_cb) + + def __window_opened_cb(self, screen, window): + if not window.is_skip_tasklist(): + self._add(window) + + def __window_closed_cb(self, screen, window): + if not window.is_skip_tasklist(): + self._remove(window) + + def _remove(self, window): + item = self._items[window.get_xid()] + self.remove_child(item) + del self._items[window.get_xid()] + + def _add(self, window): + item = self.add_piece(100 / 8) + self._items[window.get_xid()] = item class ActivityItem(IconItem): def __init__(self, activity): diff --git a/sugar/canvas/DonutItem.py b/sugar/canvas/DonutItem.py index 19c56f80..df64e851 100644 --- a/sugar/canvas/DonutItem.py +++ b/sugar/canvas/DonutItem.py @@ -25,8 +25,6 @@ class PieceItem(goocanvas.Path): data += 'z' - print data - self.set_property('data', data) class DonutItem(goocanvas.Group): @@ -45,5 +43,11 @@ class DonutItem(goocanvas.Group): self.add_child(piece_item) piece_item.construct() + return piece_item + + def remove_piece(self, piece_item): + index = self.find(piece_item) + self.remove_child(index) + def get_radius(self): return self._radius From 10f356cb22ffcaddfbbcd0b8332ef65409e5bf5f Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Thu, 17 Aug 2006 13:16:48 +0200 Subject: [PATCH 09/18] Add fg and bg circles, fill the paths --- shell/HomeWindow.py | 11 ++++++++++- sugar/canvas/DonutItem.py | 15 ++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index f19f4e0b..ed86e6f5 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -4,10 +4,11 @@ import wnck from sugar.canvas.IconItem import IconItem from sugar.canvas.DonutItem import DonutItem +from sugar.canvas.DonutItem import PieceItem class TasksItem(DonutItem): def __init__(self): - DonutItem.__init__(self, 200) + DonutItem.__init__(self, 250) self._items = {} @@ -33,6 +34,7 @@ class TasksItem(DonutItem): def _add(self, window): item = self.add_piece(100 / 8) + item.set_data('window', window) self._items[window.get_xid()] = item class ActivityItem(IconItem): @@ -120,9 +122,16 @@ class HomeWindow(gtk.Window): item_view.connect("button_press_event", self.__activity_button_press_cb, item.get_activity_id()) + elif isinstance(item, PieceItem): + item_view.connect("button_press_event", + self.__task_button_press_cb) def __activity_button_press_cb(self, view, target, event, activity_id): self._shell.start_activity(activity_id) + def __task_button_press_cb(self, view, target, event): + window = view.get_item().get_data('window') + window.activate(gtk.get_current_event_time()) + def __realize_cb(self, window): self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) diff --git a/sugar/canvas/DonutItem.py b/sugar/canvas/DonutItem.py index df64e851..e7f56057 100644 --- a/sugar/canvas/DonutItem.py +++ b/sugar/canvas/DonutItem.py @@ -8,6 +8,10 @@ class PieceItem(goocanvas.Path): self._angle_start = angle_start self._angle_end = angle_end + self.set_property('fill-color', '#e8e8e8') + self.set_property('stroke-color', '#d8d8d8') + self.set_property('line-width', 4) + def construct(self): r = self.get_parent().get_radius() @@ -33,6 +37,15 @@ class DonutItem(goocanvas.Group): self._radius = radius self._angle_start = 0 + bg = goocanvas.Ellipse(radius_x=radius, radius_y=radius, + fill_color='#c2c3c5', line_width=0) + self.add_child(bg) + + fg_radius = radius / 2 + fg = goocanvas.Ellipse(radius_x=fg_radius, radius_y=fg_radius, + fill_color='#d8d8d8', line_width=0) + self.add_child(fg) + def add_piece(self, perc): angle_end = self._angle_start + perc * 2 * math.pi / 100 piece_item = PieceItem(self._angle_start, angle_end) @@ -40,7 +53,7 @@ class DonutItem(goocanvas.Group): # FIXME can't override set_parent on the # PieceItem and there is no signal. - self.add_child(piece_item) + self.add_child(piece_item, 1) piece_item.construct() return piece_item From f65d23c44002fe94477cb3354667e9297e386092 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Thu, 17 Aug 2006 14:23:52 +0200 Subject: [PATCH 10/18] Add icon for activity to the donut. Add signals in the shell for window open/close and use them in the task view. --- shell/ActivityHost.py | 6 +++++ shell/ChatController.py | 3 ++- shell/HomeWindow.py | 41 ++++++++++++++------------------ shell/Shell.py | 13 +++++++---- sugar/canvas/DonutItem.py | 49 +++++++++++++++++++++++++++++++++++---- 5 files changed, 79 insertions(+), 33 deletions(-) diff --git a/shell/ActivityHost.py b/shell/ActivityHost.py index 639c589d..84577c1f 100644 --- a/shell/ActivityHost.py +++ b/shell/ActivityHost.py @@ -20,9 +20,15 @@ class ActivityHost: self._gdk_window = gtk.gdk.window_foreign_new(self._xid) self._people_window = PeopleWindow(shell, self) + info = self._shell.get_registry().get_activity(self._default_type) + self._icon_name = info.get_icon() + def get_id(self): return self._id + def get_icon_name(self): + return self._icon_name + def share(self): self._people_window.share() self._activity.share() diff --git a/shell/ChatController.py b/shell/ChatController.py index 96d2fd00..00bab802 100644 --- a/shell/ChatController.py +++ b/shell/ChatController.py @@ -13,7 +13,8 @@ class ChatController: self._shell.connect('activity-closed', self.__activity_closed_cb) - def __activity_closed_cb(self, shell, activity_id): + def __activity_closed_cb(self, shell, activity): + activity_id = activity.get_id() if self._id_to_name.has_key(activity_id): name = self._id_to_name[activity_id] del self._name_to_chat[name] diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index ed86e6f5..26d29cce 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -7,35 +7,30 @@ from sugar.canvas.DonutItem import DonutItem from sugar.canvas.DonutItem import PieceItem class TasksItem(DonutItem): - def __init__(self): + def __init__(self, shell): DonutItem.__init__(self, 250) self._items = {} - screen = wnck.screen_get_default() - for window in screen.get_windows(): - if not window.is_skip_tasklist(): - self._add(window) - screen.connect('window_opened', self.__window_opened_cb) - screen.connect('window_closed', self.__window_closed_cb) + shell.connect('activity_opened', self.__activity_opened_cb) + shell.connect('activity_closed', self.__activity_closed_cb) - def __window_opened_cb(self, screen, window): - if not window.is_skip_tasklist(): - self._add(window) + def __activity_opened_cb(self, shell, activity): + self._add(activity) - def __window_closed_cb(self, screen, window): - if not window.is_skip_tasklist(): - self._remove(window) + def __activity_closed_cb(self, shell, activity): + self._remove(activity) - def _remove(self, window): - item = self._items[window.get_xid()] + def _remove(self, activity): + item = self._items[activity.get_id()] self.remove_child(item) - del self._items[window.get_xid()] + del self._items[activity.get_id()] - def _add(self, window): - item = self.add_piece(100 / 8) - item.set_data('window', window) - self._items[window.get_xid()] = item + def _add(self, activity): + icon_name = activity.get_icon_name() + item = self.add_piece(100 / 8, icon_name, 'blue') + item.set_data('activity', activity) + self._items[activity.get_id()] = item class ActivityItem(IconItem): def __init__(self, activity): @@ -91,7 +86,7 @@ class Model(goocanvas.CanvasModelSimple): activity_bar.translate(50, 860) root.add_child(activity_bar) - tasks = TasksItem() + tasks = TasksItem(shell) tasks.translate(600, 450) root.add_child(tasks) @@ -130,8 +125,8 @@ class HomeWindow(gtk.Window): self._shell.start_activity(activity_id) def __task_button_press_cb(self, view, target, event): - window = view.get_item().get_data('window') - window.activate(gtk.get_current_event_time()) + activity = view.get_item().get_data('activity') + activity.present() def __realize_cb(self, window): self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) diff --git a/shell/Shell.py b/shell/Shell.py index 614e1d15..50072fb7 100755 --- a/shell/Shell.py +++ b/shell/Shell.py @@ -39,7 +39,10 @@ class ShellDbusService(dbus.service.Object): class Shell(gobject.GObject): __gsignals__ = { - 'activity-closed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])) + 'activity-opened': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'activity-closed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])) } def __init__(self, registry): @@ -71,14 +74,16 @@ class Shell(gobject.GObject): def __window_opened_cb(self, screen, window): if window.get_window_type() == wnck.WINDOW_NORMAL: - self._hosts[window.get_xid()] = ActivityHost(self, window) + host = ActivityHost(self, window) + self._hosts[window.get_xid()] = host + self.emit('activity-opened', host) def __window_closed_cb(self, screen, window): if window.get_window_type() == wnck.WINDOW_NORMAL: xid = window.get_xid() - activity = self._hosts[xid] - self.emit('activity-closed', activity.get_id()) + host = self._hosts[xid] + self.emit('activity-closed', host) del self._hosts[xid] diff --git a/sugar/canvas/DonutItem.py b/sugar/canvas/DonutItem.py index e7f56057..54c6dcce 100644 --- a/sugar/canvas/DonutItem.py +++ b/sugar/canvas/DonutItem.py @@ -2,6 +2,29 @@ import math import goocanvas +from sugar.canvas.IconItem import IconItem + +class PieceIcon(IconItem): + def __init__(self, piece_item, icon_name, color, **kwargs): + IconItem.__init__(self, icon_name, color, 48, **kwargs) + self._piece_item = piece_item + + def construct(self): + angle_start = self._piece_item.get_angle_start() + angle_end = self._piece_item.get_angle_end() + radius = self.get_parent().get_radius() + inner_radius = self.get_parent().get_inner_radius() + + icon_radius = (radius + inner_radius) / 2 + icon_angle = (angle_start + angle_end) / 2 + x = icon_radius * math.cos(icon_angle) + y = - icon_radius * math.sin(icon_angle) + + icon_width = self.get_property('width') + icon_height = self.get_property('height') + self.set_property('x', x - icon_width / 2) + self.set_property('y', y - icon_height / 2) + class PieceItem(goocanvas.Path): def __init__(self, angle_start, angle_end, **kwargs): goocanvas.Path.__init__(self, **kwargs) @@ -12,6 +35,12 @@ class PieceItem(goocanvas.Path): self.set_property('stroke-color', '#d8d8d8') self.set_property('line-width', 4) + def get_angle_start(self): + return self._angle_start + + def get_angle_end(self): + return self._angle_end + def construct(self): r = self.get_parent().get_radius() @@ -41,21 +70,28 @@ class DonutItem(goocanvas.Group): fill_color='#c2c3c5', line_width=0) self.add_child(bg) - fg_radius = radius / 2 - fg = goocanvas.Ellipse(radius_x=fg_radius, radius_y=fg_radius, + self._inner_radius = radius / 2 + fg = goocanvas.Ellipse(radius_x=self._inner_radius, + radius_y=self._inner_radius, fill_color='#d8d8d8', line_width=0) self.add_child(fg) - def add_piece(self, perc): + def add_piece(self, perc, icon_name, color): + # FIXME can't override set_parent on the + # PieceItem and there is no signal. So we + # call a construct item on the childs for now. + angle_end = self._angle_start + perc * 2 * math.pi / 100 piece_item = PieceItem(self._angle_start, angle_end) self._angle_start = angle_end - # FIXME can't override set_parent on the - # PieceItem and there is no signal. self.add_child(piece_item, 1) piece_item.construct() + icon = PieceIcon(piece_item, icon_name, color) + self.add_child(icon) + icon.construct() + return piece_item def remove_piece(self, piece_item): @@ -64,3 +100,6 @@ class DonutItem(goocanvas.Group): def get_radius(self): return self._radius + + def get_inner_radius(self): + return self._inner_radius From 640fff56191efe08013f1194f18db824d9aa228d Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Thu, 17 Aug 2006 14:42:29 +0200 Subject: [PATCH 11/18] Handle activity closed by removing his piece from the donut. --- shell/HomeWindow.py | 10 ++++++++-- sugar/canvas/DonutItem.py | 19 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/shell/HomeWindow.py b/shell/HomeWindow.py index 26d29cce..3790bf01 100644 --- a/shell/HomeWindow.py +++ b/shell/HomeWindow.py @@ -5,6 +5,7 @@ import wnck from sugar.canvas.IconItem import IconItem from sugar.canvas.DonutItem import DonutItem from sugar.canvas.DonutItem import PieceItem +from sugar.canvas.DonutItem import PieceIcon class TasksItem(DonutItem): def __init__(self, shell): @@ -23,13 +24,17 @@ class TasksItem(DonutItem): def _remove(self, activity): item = self._items[activity.get_id()] - self.remove_child(item) + self.remove_piece(item) del self._items[activity.get_id()] def _add(self, activity): icon_name = activity.get_icon_name() item = self.add_piece(100 / 8, icon_name, 'blue') + + # FIXME This really sucks. Fix goocanvas event handling. item.set_data('activity', activity) + item.get_icon().set_data('activity', activity) + self._items[activity.get_id()] = item class ActivityItem(IconItem): @@ -117,7 +122,8 @@ class HomeWindow(gtk.Window): item_view.connect("button_press_event", self.__activity_button_press_cb, item.get_activity_id()) - elif isinstance(item, PieceItem): + elif isinstance(item, PieceItem) or \ + isinstance(item, PieceIcon): item_view.connect("button_press_event", self.__task_button_press_cb) diff --git a/sugar/canvas/DonutItem.py b/sugar/canvas/DonutItem.py index 54c6dcce..9f712206 100644 --- a/sugar/canvas/DonutItem.py +++ b/sugar/canvas/DonutItem.py @@ -35,6 +35,14 @@ class PieceItem(goocanvas.Path): self.set_property('stroke-color', '#d8d8d8') self.set_property('line-width', 4) + def get_icon(self): + return self._icon + + def set_icon(self, icon_name, color): + self._icon = PieceIcon(self, icon_name, color) + self.get_parent().add_child(self._icon) + self._icon.construct() + def get_angle_start(self): return self._angle_start @@ -87,15 +95,16 @@ class DonutItem(goocanvas.Group): self.add_child(piece_item, 1) piece_item.construct() - - icon = PieceIcon(piece_item, icon_name, color) - self.add_child(icon) - icon.construct() + piece_item.set_icon(icon_name, color) return piece_item def remove_piece(self, piece_item): - index = self.find(piece_item) + index = self.find_child(piece_item) + self.remove_child(index) + + icon = piece_item.get_icon() + index = self.find_child(icon) self.remove_child(index) def get_radius(self): From 14c9c55731e15012aafd806f00707b80ca33aefb Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Thu, 17 Aug 2006 14:43:17 +0200 Subject: [PATCH 12/18] Typo --- sugar/canvas/DonutItem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugar/canvas/DonutItem.py b/sugar/canvas/DonutItem.py index 9f712206..a6b49ea9 100644 --- a/sugar/canvas/DonutItem.py +++ b/sugar/canvas/DonutItem.py @@ -87,7 +87,7 @@ class DonutItem(goocanvas.Group): def add_piece(self, perc, icon_name, color): # FIXME can't override set_parent on the # PieceItem and there is no signal. So we - # call a construct item on the childs for now. + # call a construct method on the childs for now. angle_end = self._angle_start + perc * 2 * math.pi / 100 piece_item = PieceItem(self._angle_start, angle_end) From 825758018df41500182c8834dc19e7893d353b3a Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Fri, 18 Aug 2006 19:36:36 +0200 Subject: [PATCH 13/18] Update the color property name --- sugar/canvas/IconItem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sugar/canvas/IconItem.py b/sugar/canvas/IconItem.py index 7be096d1..153699b8 100644 --- a/sugar/canvas/IconItem.py +++ b/sugar/canvas/IconItem.py @@ -21,8 +21,8 @@ class IconCache(gobject.GObject): icon_file.close() if color != None: - style = '.icon-color {fill: %s;}' % color - data = re.sub('\.icon-color \{.*\}', style, data) + style = '.fill-color {fill: %s;}' % color + data = re.sub('\.fill-color \{.*\}', style, data) loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/svg-xml') loader.set_size(size, size) From 24dae31a9d1bb2141db3bfd0364f2d95219fc7fa Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 19 Aug 2006 01:29:42 +0200 Subject: [PATCH 14/18] Starting to create the mesh view... --- configure.ac | 1 + shell/Makefile.am | 4 +-- shell/Shell.py | 2 +- shell/{ => home}/ActivitiesModel.py | 0 shell/{HomeWindow.py => home/HomeView.py} | 23 +++------------ shell/home/HomeWindow.py | 35 +++++++++++++++++++++++ shell/home/Makefile.am | 6 ++++ shell/home/MeshView.py | 20 +++++++++++++ shell/home/__init__.py | 0 9 files changed, 68 insertions(+), 23 deletions(-) rename shell/{ => home}/ActivitiesModel.py (100%) rename shell/{HomeWindow.py => home/HomeView.py} (86%) create mode 100644 shell/home/HomeWindow.py create mode 100644 shell/home/Makefile.am create mode 100644 shell/home/MeshView.py create mode 100644 shell/home/__init__.py diff --git a/configure.ac b/configure.ac index f53cb507..735c2cc9 100644 --- a/configure.ac +++ b/configure.ac @@ -30,6 +30,7 @@ activities/chat/Makefile activities/terminal/Makefile shell/Makefile shell/data/Makefile +shell/home/Makefile shell/session/Makefile shell/PresenceService/Makefile sugar/Makefile diff --git a/shell/Makefile.am b/shell/Makefile.am index 5642a4ab..61caa3a3 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = data session PresenceService +SUBDIRS = data session home PresenceService bin_SCRIPTS = \ sugar \ @@ -11,13 +11,11 @@ bin_SCRIPTS = \ sugardir = $(pkgdatadir)/shell sugar_PYTHON = \ __init__.py \ - ActivitiesModel.py \ ActivityHost.py \ ActivityRegistry.py \ ChatController.py \ ConsoleWindow.py \ Owner.py \ - HomeWindow.py \ PeopleWindow.py \ PresenceView.py \ Shell.py diff --git a/shell/Shell.py b/shell/Shell.py index 50072fb7..ad4db5b0 100755 --- a/shell/Shell.py +++ b/shell/Shell.py @@ -8,7 +8,7 @@ import gobject import wnck from ActivityRegistry import ActivityRegistry -from HomeWindow import HomeWindow +from home.HomeWindow import HomeWindow from sugar import env from Owner import ShellOwner from sugar.presence.PresenceService import PresenceService diff --git a/shell/ActivitiesModel.py b/shell/home/ActivitiesModel.py similarity index 100% rename from shell/ActivitiesModel.py rename to shell/home/ActivitiesModel.py diff --git a/shell/HomeWindow.py b/shell/home/HomeView.py similarity index 86% rename from shell/HomeWindow.py rename to shell/home/HomeView.py index 3790bf01..29f2e233 100644 --- a/shell/HomeWindow.py +++ b/shell/home/HomeView.py @@ -95,27 +95,15 @@ class Model(goocanvas.CanvasModelSimple): tasks.translate(600, 450) root.add_child(tasks) -class HomeWindow(gtk.Window): +class HomeView(goocanvas.CanvasView): def __init__(self, shell): - gtk.Window.__init__(self) - + goocanvas.CanvasView.__init__(self) self._shell = shell - self.connect('realize', self.__realize_cb) - - canvas = goocanvas.CanvasView() + self.connect("item_view_created", self.__item_view_created_cb) canvas_model = Model(shell) - canvas.set_bounds(0, 0, 1200, 900) - canvas.set_scale(float(800) / float(1200)) - canvas.set_size_request(800, 600) - - canvas.connect("item_view_created", self.__item_view_created_cb) - - self.add(canvas) - canvas.show() - - canvas.set_model(canvas_model) + self.set_model(canvas_model) def __item_view_created_cb(self, view, item_view, item): if isinstance(item, ActivityItem): @@ -133,6 +121,3 @@ class HomeWindow(gtk.Window): def __task_button_press_cb(self, view, target, event): activity = view.get_item().get_data('activity') activity.present() - - def __realize_cb(self, window): - self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) diff --git a/shell/home/HomeWindow.py b/shell/home/HomeWindow.py new file mode 100644 index 00000000..d66020ea --- /dev/null +++ b/shell/home/HomeWindow.py @@ -0,0 +1,35 @@ +import gtk + +from home.MeshView import MeshView +from home.HomeView import HomeView + +class HomeWindow(gtk.Window): + def __init__(self, shell): + gtk.Window.__init__(self) + + self.connect('realize', self.__realize_cb) + + self._nb = gtk.Notebook() + self._nb.set_show_tabs(False) + self._nb.set_show_border(False) + + home_view = HomeView(shell) + self._nb.append_page(home_view) + self._setup_canvas(home_view) + home_view.show() + + mesh_view = MeshView(shell) + self._setup_canvas(mesh_view) + self._nb.append_page(mesh_view) + mesh_view.show() + + self.add(self._nb) + self._nb.show() + + def _setup_canvas(self, canvas): + canvas.set_bounds(0, 0, 1200, 900) + canvas.set_scale(float(800) / float(1200)) + canvas.set_size_request(800, 600) + + def __realize_cb(self, window): + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) diff --git a/shell/home/Makefile.am b/shell/home/Makefile.am new file mode 100644 index 00000000..184570e5 --- /dev/null +++ b/shell/home/Makefile.am @@ -0,0 +1,6 @@ +sugardir = $(pkgdatadir)/shell +sugar_PYTHON = \ + __init__.py \ + MeshView.py \ + HomeView.py \ + Window.py diff --git a/shell/home/MeshView.py b/shell/home/MeshView.py new file mode 100644 index 00000000..7b2e5f67 --- /dev/null +++ b/shell/home/MeshView.py @@ -0,0 +1,20 @@ +import goocanvas + +class Model(goocanvas.CanvasModelSimple): + def __init__(self, shell): + goocanvas.CanvasModelSimple.__init__(self) + + root = self.get_root_item() + +class MeshView(goocanvas.CanvasView): + def __init__(self, shell): + goocanvas.CanvasView.__init__(self) + self._shell = shell + + self.connect("item_view_created", self.__item_view_created_cb) + + canvas_model = Model(shell) + self.set_model(canvas_model) + + def __item_view_created_cb(self, view, item_view, item): + pass diff --git a/shell/home/__init__.py b/shell/home/__init__.py new file mode 100644 index 00000000..e69de29b From de65daf5480b673df26c5144925c8041a766cc87 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 19 Aug 2006 02:00:04 +0200 Subject: [PATCH 15/18] Implement zooming levels, actual view still empty --- shell/Makefile.am | 1 + shell/Shell.py | 39 ++++++++++++++++++++++++++++ shell/data/Makefile.am | 6 +---- shell/data/activity-placeholder.png | Bin 3163 -> 0 bytes shell/data/home-background.png | Bin 70665 -> 0 bytes shell/data/kbdconfig | 6 ++--- shell/home/FriendsView.py | 20 ++++++++++++++ shell/home/HomeWindow.py | 13 ++++++++++ shell/home/Makefile.am | 3 ++- shell/sugar-zoom | 14 ++++++++++ 10 files changed, 93 insertions(+), 9 deletions(-) delete mode 100644 shell/data/activity-placeholder.png delete mode 100644 shell/data/home-background.png create mode 100644 shell/home/FriendsView.py create mode 100644 shell/sugar-zoom diff --git a/shell/Makefile.am b/shell/Makefile.am index 61caa3a3..61483ae8 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -6,6 +6,7 @@ bin_SCRIPTS = \ sugar-activity-factory \ sugar-console \ sugar-people \ + sugar-zoom \ sugar-presence-service sugardir = $(pkgdatadir)/shell diff --git a/shell/Shell.py b/shell/Shell.py index ad4db5b0..c8ebe06a 100755 --- a/shell/Shell.py +++ b/shell/Shell.py @@ -37,7 +37,20 @@ class ShellDbusService(dbus.service.Object): def show_console(self): gobject.idle_add(self.__show_console_idle) + @dbus.service.method('com.redhat.Sugar.Shell') + def zoom_in(self): + self._shell.zoom_in() + + @dbus.service.method('com.redhat.Sugar.Shell') + def zoom_out(self): + self._shell.zoom_out() + class Shell(gobject.GObject): + ZOOM_MESH = 0 + ZOOM_FRIENDS = 1 + ZOOM_HOME = 2 + ZOOM_ACTIVITY = 3 + __gsignals__ = { 'activity-opened': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), @@ -51,6 +64,7 @@ class Shell(gobject.GObject): self._screen = wnck.screen_get_default() self._registry = registry self._hosts = {} + self._zoom_level = Shell.ZOOM_HOME def start(self): session_bus = dbus.SessionBus() @@ -159,3 +173,28 @@ class Shell(gobject.GObject): def get_chat_controller(self): return self._chat_controller + + def _set_zoom_level(self, level): + self._zoom_level = level + + if level == Shell.ZOOM_ACTIVITY: + self._screen.toggle_showing_desktop(False) + else: + self._screen.toggle_showing_desktop(True) + + if level == Shell.ZOOM_HOME: + self._home_window.set_view(HomeWindow.HOME_VIEW) + elif level == Shell.ZOOM_FRIENDS: + self._home_window.set_view(HomeWindow.FRIENDS_VIEW) + elif level == Shell.ZOOM_MESH: + self._home_window.set_view(HomeWindow.MESH_VIEW) + + def zoom_in(self): + level = self._zoom_level + 1 + if level <= Shell.ZOOM_ACTIVITY: + self._set_zoom_level(level) + + def zoom_out(self): + level = self._zoom_level - 1 + if level >= Shell.ZOOM_MESH: + self._set_zoom_level(level) diff --git a/shell/data/Makefile.am b/shell/data/Makefile.am index fa14d6bb..1f0f9c05 100644 --- a/shell/data/Makefile.am +++ b/shell/data/Makefile.am @@ -1,8 +1,4 @@ confdir = $(pkgdatadir) conf_DATA = kbdconfig -imagesdir = $(pkgdatadir) -images_DATA = \ - home-background.png - -EXTRA_DIST = $(conf_DATA) $(images_DATA) +EXTRA_DIST = $(conf_DATA) diff --git a/shell/data/activity-placeholder.png b/shell/data/activity-placeholder.png deleted file mode 100644 index 9fea97b8000bda494372213f85026748aa2c26c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3163 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 B98CZK diff --git a/shell/data/home-background.png b/shell/data/home-background.png deleted file mode 100644 index 346b5dcef82315c455a9b3c30a2712202bbb3d3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70665 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[t7|^gC}yzzRX#zPD>Ss?-g|_rR+^feMsWV#iK= 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 p=prev c=close -f1=desktop -f2=!sugar-people -f3=!sugar-console +f1=!sugar-zoom out +f2=!sugar-zoom in +f3=!sugar-people f4=!sugar-activity org.sugar.Terminal diff --git a/shell/home/FriendsView.py b/shell/home/FriendsView.py new file mode 100644 index 00000000..baa834bd --- /dev/null +++ b/shell/home/FriendsView.py @@ -0,0 +1,20 @@ +import goocanvas + +class Model(goocanvas.CanvasModelSimple): + def __init__(self, shell): + goocanvas.CanvasModelSimple.__init__(self) + + root = self.get_root_item() + +class FriendsView(goocanvas.CanvasView): + def __init__(self, shell): + goocanvas.CanvasView.__init__(self) + self._shell = shell + + self.connect("item_view_created", self.__item_view_created_cb) + + canvas_model = Model(shell) + self.set_model(canvas_model) + + def __item_view_created_cb(self, view, item_view, item): + pass diff --git a/shell/home/HomeWindow.py b/shell/home/HomeWindow.py index d66020ea..ca3007da 100644 --- a/shell/home/HomeWindow.py +++ b/shell/home/HomeWindow.py @@ -2,8 +2,13 @@ import gtk from home.MeshView import MeshView from home.HomeView import HomeView +from home.FriendsView import FriendsView class HomeWindow(gtk.Window): + HOME_VIEW = 0 + FRIENDS_VIEW = 1 + MESH_VIEW = 2 + def __init__(self, shell): gtk.Window.__init__(self) @@ -17,6 +22,11 @@ class HomeWindow(gtk.Window): self._nb.append_page(home_view) self._setup_canvas(home_view) home_view.show() + + friends_view = FriendsView(shell) + self._nb.append_page(friends_view) + self._setup_canvas(friends_view) + friends_view.show() mesh_view = MeshView(shell) self._setup_canvas(mesh_view) @@ -26,6 +36,9 @@ class HomeWindow(gtk.Window): self.add(self._nb) self._nb.show() + def set_view(self, view): + self._nb.set_current_page(view) + def _setup_canvas(self, canvas): canvas.set_bounds(0, 0, 1200, 900) canvas.set_scale(float(800) / float(1200)) diff --git a/shell/home/Makefile.am b/shell/home/Makefile.am index 184570e5..72445ecd 100644 --- a/shell/home/Makefile.am +++ b/shell/home/Makefile.am @@ -1,6 +1,7 @@ sugardir = $(pkgdatadir)/shell sugar_PYTHON = \ __init__.py \ + FriendsView.py \ MeshView.py \ HomeView.py \ - Window.py + HomeWindow.py diff --git a/shell/sugar-zoom b/shell/sugar-zoom new file mode 100644 index 00000000..ee435170 --- /dev/null +++ b/shell/sugar-zoom @@ -0,0 +1,14 @@ +#!/usr/bin/python +import sys + +import dbus +import dbus.glib + +bus = dbus.SessionBus() +proxy_obj = bus.get_object('com.redhat.Sugar.Shell', '/com/redhat/Sugar/Shell') +shell = dbus.Interface(proxy_obj, 'com.redhat.Sugar.Shell') + +if sys.argv[1] == 'in': + shell.zoom_in() +elif sys.argv[1] == 'out': + shell.zoom_out() From 0d4acb6e78ecd52b31b0b861bcf4a77b22936e59 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 19 Aug 2006 11:12:25 +0200 Subject: [PATCH 16/18] Start implementing friends/mesh --- configure.ac | 1 + shell/Shell.py | 4 +- shell/home/FriendsModel.py | 40 +++++++++++++++++++ shell/home/FriendsView.py | 31 ++++++++++++-- shell/home/HomeModel.py | 13 ++++++ shell/home/HomeWindow.py | 6 +-- shell/home/Makefile.am | 7 +++- .../home/{ActivitiesModel.py => MeshModel.py} | 2 +- shell/home/MeshView.py | 10 +++-- sugar/Makefile.am | 2 +- sugar/canvas/IconItem.py | 2 +- sugar/canvas/Makefile.am | 1 + 12 files changed, 104 insertions(+), 15 deletions(-) create mode 100644 shell/home/FriendsModel.py create mode 100644 shell/home/HomeModel.py rename shell/home/{ActivitiesModel.py => MeshModel.py} (97%) diff --git a/configure.ac b/configure.ac index 735c2cc9..e569750c 100644 --- a/configure.ac +++ b/configure.ac @@ -36,6 +36,7 @@ shell/PresenceService/Makefile sugar/Makefile sugar/__installed__.py sugar/activity/Makefile +sugar/canvas/Makefile sugar/chat/Makefile sugar/chat/sketchpad/Makefile sugar/p2p/Makefile diff --git a/shell/Shell.py b/shell/Shell.py index c8ebe06a..5876f703 100755 --- a/shell/Shell.py +++ b/shell/Shell.py @@ -9,6 +9,7 @@ import wnck from ActivityRegistry import ActivityRegistry from home.HomeWindow import HomeWindow +from home.HomeModel import HomeModel from sugar import env from Owner import ShellOwner from sugar.presence.PresenceService import PresenceService @@ -77,7 +78,8 @@ class Shell(gobject.GObject): self._chat_controller = ChatController(self) self._chat_controller.listen() - self._home_window = HomeWindow(self) + home_model = HomeModel(self._registry) + self._home_window = HomeWindow(self, home_model) self._home_window.show() self._screen.connect('window-opened', self.__window_opened_cb) diff --git a/shell/home/FriendsModel.py b/shell/home/FriendsModel.py new file mode 100644 index 00000000..6f18083a --- /dev/null +++ b/shell/home/FriendsModel.py @@ -0,0 +1,40 @@ +import gobject + +from sugar.presence.PresenceService import PresenceService + +class Friend: + def __init__(self, buddy): + self._buddy = buddy + + def get_name(self): + return self._buddy.get_name() + +class FriendsModel(gobject.GObject): + __gsignals__ = { + 'friend-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'friend-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._friends = [] + + self._pservice = PresenceService() + self._pservice.connect("buddy-appeared", self.__buddy_appeared_cb) + + for buddy in self._pservice.get_buddies(): + self.add_friend(buddy) + + def add_friend(self, buddy): + friend = Friend(buddy) + self._friends.append(friend) + self.emit('friend-added', friend) + + def __iter__(self): + return self._friends.__iter__() + + def __buddy_appeared_cb(self, pservice, buddy): + self.add_friend(buddy) diff --git a/shell/home/FriendsView.py b/shell/home/FriendsView.py index baa834bd..b12616a4 100644 --- a/shell/home/FriendsView.py +++ b/shell/home/FriendsView.py @@ -1,19 +1,44 @@ +import random + import goocanvas +from sugar.canvas.IconItem import IconItem + class Model(goocanvas.CanvasModelSimple): - def __init__(self, shell): + def __init__(self, data_model): goocanvas.CanvasModelSimple.__init__(self) root = self.get_root_item() + item = goocanvas.Rect(width=1200, height=900, + fill_color="#d8d8d8") + root.add_child(item) + + for friend in data_model: + self.add_friend(friend) + + data_model.connect('friend-added', self.__friend_added_cb) + + def add_friend(self, friend): + root = self.get_root_item() + + icon = IconItem('stock-buddy', 'green', 48) + icon.set_property('x', random.random() * 1100) + icon.set_property('y', random.random() * 800) + + root.add_child(icon) + + def __friend_added_cb(self, data_model, friend): + self.add_friend(friend) + class FriendsView(goocanvas.CanvasView): - def __init__(self, shell): + def __init__(self, shell, data_model): goocanvas.CanvasView.__init__(self) self._shell = shell self.connect("item_view_created", self.__item_view_created_cb) - canvas_model = Model(shell) + canvas_model = Model(data_model) self.set_model(canvas_model) def __item_view_created_cb(self, view, item_view, item): diff --git a/shell/home/HomeModel.py b/shell/home/HomeModel.py new file mode 100644 index 00000000..5f4965d3 --- /dev/null +++ b/shell/home/HomeModel.py @@ -0,0 +1,13 @@ +from home.FriendsModel import FriendsModel +from home.MeshModel import MeshModel + +class HomeModel: + def __init__(self, registry): + self._friends = FriendsModel() + self._mesh = MeshModel(registry) + + def get_friends(self): + return self._friends + + def get_mesh(self): + return self._mesh diff --git a/shell/home/HomeWindow.py b/shell/home/HomeWindow.py index ca3007da..86c83ab1 100644 --- a/shell/home/HomeWindow.py +++ b/shell/home/HomeWindow.py @@ -9,7 +9,7 @@ class HomeWindow(gtk.Window): FRIENDS_VIEW = 1 MESH_VIEW = 2 - def __init__(self, shell): + def __init__(self, shell, model): gtk.Window.__init__(self) self.connect('realize', self.__realize_cb) @@ -23,12 +23,12 @@ class HomeWindow(gtk.Window): self._setup_canvas(home_view) home_view.show() - friends_view = FriendsView(shell) + friends_view = FriendsView(shell, model.get_friends()) self._nb.append_page(friends_view) self._setup_canvas(friends_view) friends_view.show() - mesh_view = MeshView(shell) + mesh_view = MeshView(shell, model.get_mesh()) self._setup_canvas(mesh_view) self._nb.append_page(mesh_view) mesh_view.show() diff --git a/shell/home/Makefile.am b/shell/home/Makefile.am index 72445ecd..2a86a261 100644 --- a/shell/home/Makefile.am +++ b/shell/home/Makefile.am @@ -1,7 +1,10 @@ -sugardir = $(pkgdatadir)/shell +sugardir = $(pkgdatadir)/shell/home sugar_PYTHON = \ __init__.py \ + FriendsModel.py \ FriendsView.py \ + MeshModel.py \ MeshView.py \ HomeView.py \ - HomeWindow.py + HomeWindow.py \ + HomeModel.py diff --git a/shell/home/ActivitiesModel.py b/shell/home/MeshModel.py similarity index 97% rename from shell/home/ActivitiesModel.py rename to shell/home/MeshModel.py index c46256d2..b2163ac6 100644 --- a/shell/home/ActivitiesModel.py +++ b/shell/home/MeshModel.py @@ -19,7 +19,7 @@ class ActivityInfo: def get_service(self): return self._service -class ActivitiesModel(gobject.GObject): +class MeshModel(gobject.GObject): __gsignals__ = { 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), diff --git a/shell/home/MeshView.py b/shell/home/MeshView.py index 7b2e5f67..611ad182 100644 --- a/shell/home/MeshView.py +++ b/shell/home/MeshView.py @@ -1,19 +1,23 @@ import goocanvas class Model(goocanvas.CanvasModelSimple): - def __init__(self, shell): + def __init__(self, data_model): goocanvas.CanvasModelSimple.__init__(self) root = self.get_root_item() + item = goocanvas.Rect(width=1200, height=900, + fill_color="#4f4f4f") + root.add_child(item) + class MeshView(goocanvas.CanvasView): - def __init__(self, shell): + def __init__(self, shell, data_model): goocanvas.CanvasView.__init__(self) self._shell = shell self.connect("item_view_created", self.__item_view_created_cb) - canvas_model = Model(shell) + canvas_model = Model(data_model) self.set_model(canvas_model) def __item_view_created_cb(self, view, item_view, item): diff --git a/sugar/Makefile.am b/sugar/Makefile.am index 086d2918..a01427d0 100644 --- a/sugar/Makefile.am +++ b/sugar/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = activity chat p2p presence +SUBDIRS = activity canvas chat p2p presence sugardir = $(pythondir)/sugar sugar_PYTHON = \ diff --git a/sugar/canvas/IconItem.py b/sugar/canvas/IconItem.py index 153699b8..bf57bce9 100644 --- a/sugar/canvas/IconItem.py +++ b/sugar/canvas/IconItem.py @@ -21,7 +21,7 @@ class IconCache(gobject.GObject): icon_file.close() if color != None: - style = '.fill-color {fill: %s;}' % color + style = '.fill-color {fill: %s; stroke: %s;}' % (color, color) data = re.sub('\.fill-color \{.*\}', style, data) loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/svg-xml') diff --git a/sugar/canvas/Makefile.am b/sugar/canvas/Makefile.am index 9120ba25..35146695 100644 --- a/sugar/canvas/Makefile.am +++ b/sugar/canvas/Makefile.am @@ -1,4 +1,5 @@ sugardir = $(pythondir)/sugar/canvas sugar_PYTHON = \ __init__.py \ + DonutItem.py \ IconItem.py From 5a2653bf1edd835c1551a4410c2e468e9334dbf7 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 19 Aug 2006 11:33:58 +0200 Subject: [PATCH 17/18] Functional mesh view --- shell/home/MeshView.py | 47 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/shell/home/MeshView.py b/shell/home/MeshView.py index 611ad182..bd8f230c 100644 --- a/shell/home/MeshView.py +++ b/shell/home/MeshView.py @@ -1,15 +1,48 @@ +import random + import goocanvas +from sugar.canvas.IconItem import IconItem + +class ActivityItem(IconItem): + def __init__(self, activity, registry): + info = registry.get_activity(activity.get_type()) + icon_name = info.get_icon() + + IconItem.__init__(self, icon_name, 'green', 48) + + self._activity = activity + + def get_service(self): + return self._activity.get_service() + class Model(goocanvas.CanvasModelSimple): - def __init__(self, data_model): + def __init__(self, data_model, registry): goocanvas.CanvasModelSimple.__init__(self) + self._registry = registry root = self.get_root_item() item = goocanvas.Rect(width=1200, height=900, - fill_color="#4f4f4f") + fill_color="#d8d8d8") root.add_child(item) + for activity in data_model: + self.add_activity(activity) + + data_model.connect('activity-added', self.__activity_added_cb) + + def add_activity(self, activity): + root = self.get_root_item() + + item = ActivityItem(activity, self._registry) + item.set_property('x', random.random() * 1100) + item.set_property('y', random.random() * 800) + root.add_child(item) + + def __activity_added_cb(self, data_model, activity): + self.add_activity(activity) + class MeshView(goocanvas.CanvasView): def __init__(self, shell, data_model): goocanvas.CanvasView.__init__(self) @@ -17,8 +50,14 @@ class MeshView(goocanvas.CanvasView): self.connect("item_view_created", self.__item_view_created_cb) - canvas_model = Model(data_model) + canvas_model = Model(data_model, shell.get_registry()) self.set_model(canvas_model) + def __activity_button_press_cb(self, view, target, event, service): + self._shell.join_activity(service) + def __item_view_created_cb(self, view, item_view, item): - pass + if isinstance(item, ActivityItem): + item_view.connect("button_press_event", + self.__activity_button_press_cb, + item.get_service()) From f8c4f0bd66309fda38047d47fb57c47409dcecf0 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 19 Aug 2006 11:54:41 +0200 Subject: [PATCH 18/18] Skeleton group chat activity. Fix positioning in the activity bar. --- activities/Makefile.am | 2 +- activities/browser/browser.activity | 2 +- activities/groupchat/GroupChatActivity.py | 8 ++++++++ activities/groupchat/Makefile.am | 6 ++++++ activities/groupchat/__init__.py | 0 activities/groupchat/groupchat.activity | 6 ++++++ configure.ac | 1 + shell/home/HomeView.py | 7 ++++++- sugar/canvas/IconItem.py | 5 ++++- 9 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 activities/groupchat/GroupChatActivity.py create mode 100644 activities/groupchat/Makefile.am create mode 100644 activities/groupchat/__init__.py create mode 100644 activities/groupchat/groupchat.activity diff --git a/activities/Makefile.am b/activities/Makefile.am index 84bfa853..c900fb64 100644 --- a/activities/Makefile.am +++ b/activities/Makefile.am @@ -1 +1 @@ -SUBDIRS = browser chat terminal +SUBDIRS = browser chat groupchat terminal diff --git a/activities/browser/browser.activity b/activities/browser/browser.activity index 6a1d3cdf..47eea7f3 100644 --- a/activities/browser/browser.activity +++ b/activities/browser/browser.activity @@ -1,7 +1,7 @@ [Activity] name = Web id = com.redhat.Sugar.BrowserActivity -icon = browser-activity +icon = activity-web python_module = browser.BrowserActivity.BrowserActivity default_type = _web_olpc._udp show_launcher = yes diff --git a/activities/groupchat/GroupChatActivity.py b/activities/groupchat/GroupChatActivity.py new file mode 100644 index 00000000..32cfac55 --- /dev/null +++ b/activities/groupchat/GroupChatActivity.py @@ -0,0 +1,8 @@ +from gettext import gettext as _ + +from sugar.activity.Activity import Activity + +class GroupChatActivity(Activity): + def __init__(self): + Activity.__init__(self) + self.set_title(_('Group chat')) diff --git a/activities/groupchat/Makefile.am b/activities/groupchat/Makefile.am new file mode 100644 index 00000000..66e99151 --- /dev/null +++ b/activities/groupchat/Makefile.am @@ -0,0 +1,6 @@ +sugardir = $(pkgdatadir)/activities/groupchat +sugar_PYTHON = \ + __init__.py \ + GroupChatActivity.py + +EXTRA_DIST = groupchat.activity diff --git a/activities/groupchat/__init__.py b/activities/groupchat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/activities/groupchat/groupchat.activity b/activities/groupchat/groupchat.activity new file mode 100644 index 00000000..2c8734a1 --- /dev/null +++ b/activities/groupchat/groupchat.activity @@ -0,0 +1,6 @@ +[Activity] +name = GroupChat +icon = activity-groupchat +id = com.redhat.Sugar.GroupChatActivity +python_module = groupchat.GroupChatActivity.GroupChatActivity +show_launcher = yes diff --git a/configure.ac b/configure.ac index e569750c..f9b516a4 100644 --- a/configure.ac +++ b/configure.ac @@ -27,6 +27,7 @@ dbus-installed.conf activities/Makefile activities/browser/Makefile activities/chat/Makefile +activities/groupchat/Makefile activities/terminal/Makefile shell/Makefile shell/data/Makefile diff --git a/shell/home/HomeView.py b/shell/home/HomeView.py index 29f2e233..09627b66 100644 --- a/shell/home/HomeView.py +++ b/shell/home/HomeView.py @@ -38,8 +38,11 @@ class TasksItem(DonutItem): self._items[activity.get_id()] = item class ActivityItem(IconItem): + ICON_SIZE = 30 + def __init__(self, activity): - IconItem.__init__(self, activity.get_icon(), 'white', 30) + IconItem.__init__(self, activity.get_icon(), 'white', + ActivityItem.ICON_SIZE) self._activity = activity def get_activity_id(self): @@ -58,6 +61,8 @@ class ActivityBar(goocanvas.Group): def add_activity(self, activity): item = ActivityItem(activity) + x = (ActivityItem.ICON_SIZE + 6) * self.get_n_children() + item.set_property('x', x) self.add_child(item) class Background(goocanvas.Group): diff --git a/sugar/canvas/IconItem.py b/sugar/canvas/IconItem.py index bf57bce9..73098b72 100644 --- a/sugar/canvas/IconItem.py +++ b/sugar/canvas/IconItem.py @@ -21,9 +21,12 @@ class IconCache(gobject.GObject): icon_file.close() if color != None: - style = '.fill-color {fill: %s; stroke: %s;}' % (color, color) + style = '.fill-color {fill: %s;}' % (color) data = re.sub('\.fill-color \{.*\}', style, data) + style = '.fill-and-stroke-color {fill: %s; stroke: %s;}' % (color, color) + data = re.sub('\.fill-and-stroke-color \{.*\}', style, data) + loader = gtk.gdk.pixbuf_loader_new_with_mime_type('image/svg-xml') loader.set_size(size, size) loader.write(data)