From cc745f990ff2d53eb3e448353d0b013eeb929b1a Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 7 Aug 2007 17:56:14 -0400 Subject: [PATCH 01/34] DevConsole: Fix broken procfs parser --- .../memphis/plugins/memphis_init/info.py | 1 + services/console/lib/procmem/proc.py | 47 ++++++++++++------- services/console/lib/procmem/proc_smaps.py | 4 +- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/services/console/interface/memphis/plugins/memphis_init/info.py b/services/console/interface/memphis/plugins/memphis_init/info.py index 6e524c72..667645c5 100644 --- a/services/console/interface/memphis/plugins/memphis_init/info.py +++ b/services/console/interface/memphis/plugins/memphis_init/info.py @@ -11,3 +11,4 @@ def plg_on_top_data_refresh(self, ppinfo): data = [ppinfo['pid'], ppinfo['name'], ppinfo['state_name']] return data + diff --git a/services/console/lib/procmem/proc.py b/services/console/lib/procmem/proc.py index adc2f6b9..d50242be 100644 --- a/services/console/lib/procmem/proc.py +++ b/services/console/lib/procmem/proc.py @@ -1,4 +1,6 @@ -import sys, os +import os +import re +import sys import string class ProcInfo: @@ -36,10 +38,12 @@ class ProcInfo: return None # Parsing data , check 'man 5 proc' for details - data = infile.read().split() - + stat_data = infile.read() infile.close() - + + process_name = self._get_process_name(stat_data) + data = self._get_safe_split(stat_data) + state_dic = { 'R': 'Running', 'S': 'Sleeping', @@ -48,27 +52,34 @@ class ProcInfo: 'T': 'Traced/Stopped', 'W': 'Paging' } - + # user and group owners pidstat = os.stat(pidfile) - info = { - 'pid': int(data[0]), # Process ID - 'name': data[1].strip('()'), # Process name - 'state': data[2], # Process State, ex: R|S|D|Z|T|W - 'state_name': state_dic[data[2]], # Process State name, ex: Running, sleeping, Zombie, etc - 'ppid': int(data[3]), # Parent process ID - 'utime': int(data[13]), # Used jiffies in user mode - 'stime': int(data[14]), # Used jiffies in kernel mode - 'start_time': int(data[21]), # Process time from system boot (jiffies) - 'vsize': int(data[22]), # Virtual memory size used (bytes) - 'rss': int(data[23])*4, # Resident Set Size (bytes) + 'pid': int(data[0]), # Process ID + 'name': process_name, + 'state': data[2], # Process State, ex: R|S|D|Z|T|W + 'state_name': state_dic[data[2]], # Process State name, ex: Running, sleeping, Zombie, etc + 'ppid': int(data[3]), # Parent process ID + 'utime': int(data[13]), # Used jiffies in user mode + 'stime': int(data[14]), # Used jiffies in kernel mode + 'start_time': int(data[21]), # Process time from system boot (jiffies) + 'vsize': int(data[22]), # Virtual memory size used (bytes) + 'rss': int(data[23])*4, # Resident Set Size (bytes) 'user_id': pidstat.st_uid, # process owner 'group_id': pidstat.st_gid # owner group } - + return info - + + # Return the process name + def _get_process_name(self, data): + m = re.search("\(.*\)", data) + return m.string[m.start()+1:m.end()-1] + + def _get_safe_split(self, data): + new_data = re.sub("\(.*\)", '()', data) + return new_data.split() # Returns the CPU usage expressed in Jiffies def get_CPU_usage(self, cpu_hz, used_jiffies, start_time): diff --git a/services/console/lib/procmem/proc_smaps.py b/services/console/lib/procmem/proc_smaps.py index ce93cb21..174bc6be 100644 --- a/services/console/lib/procmem/proc_smaps.py +++ b/services/console/lib/procmem/proc_smaps.py @@ -56,8 +56,8 @@ class ProcSmaps: mapping = Mapping (size, rss, shared_clean, shared_dirty, private_clean, private_dirty, permissions, name) self.mappings.append (mapping) - num_lines -= 7 - line_idx += 7 + num_lines -= 8 + line_idx += 8 # Parses a line of the form "foo: 42 kB" and returns an integer for the "42" field def parse_smaps_size_line (self, line): From a3efc1284f44b3305c2310ecf4ee4aeb7b4d498c Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 7 Aug 2007 17:59:38 -0400 Subject: [PATCH 02/34] DevConsole: Fix Memphis start/stop button --- services/console/interface/memphis/memphis.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/console/interface/memphis/memphis.py b/services/console/interface/memphis/memphis.py index 0dd52fcd..5b1ce405 100644 --- a/services/console/interface/memphis/memphis.py +++ b/services/console/interface/memphis/memphis.py @@ -130,15 +130,14 @@ class Data: treeview.set_model(self.store) def _start_memphis(self, button): - # Update information every 1.5 second button.hide() self.interface.button_stop.show() self._running_status = True - gobject.timeout_add(1500, self.load_data, self.treeview) + self._gid = gobject.timeout_add(1500, self.load_data, self.treeview) def _stop_memphis(self, button): - + gobject.source_remove(self._gid) self._running_status = False button.hide() self.interface.button_start.show() From 91f68897a61fb6180fcb7cfba76bd3cc0c87be30 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 7 Aug 2007 18:14:19 -0400 Subject: [PATCH 03/34] DevConsole: New referenced memory usage field in Memphis (introduced in kernel 2.6.22) --- .../memphis/plugins/dirty_size/__init__.py | 17 ---------------- .../plugins/{dirty_size => smaps}/Makefile.am | 0 .../plugins/{dirty_size => smaps}/README | 0 .../memphis/plugins/smaps/__init__.py | 17 ++++++++++++++++ .../plugins/{dirty_size => smaps}/info.py | 11 +++++----- services/console/lib/procmem/analysis.py | 20 ++++++++++--------- services/console/lib/procmem/proc_smaps.py | 16 ++++++++++++--- 7 files changed, 46 insertions(+), 35 deletions(-) delete mode 100644 services/console/interface/memphis/plugins/dirty_size/__init__.py rename services/console/interface/memphis/plugins/{dirty_size => smaps}/Makefile.am (100%) rename services/console/interface/memphis/plugins/{dirty_size => smaps}/README (100%) create mode 100644 services/console/interface/memphis/plugins/smaps/__init__.py rename services/console/interface/memphis/plugins/{dirty_size => smaps}/info.py (68%) diff --git a/services/console/interface/memphis/plugins/dirty_size/__init__.py b/services/console/interface/memphis/plugins/dirty_size/__init__.py deleted file mode 100644 index f8e9e0a7..00000000 --- a/services/console/interface/memphis/plugins/dirty_size/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ - -import info - - -INTERNALS = { - # Basic information - 'PLGNAME': "Dirty Size", - 'TABNAME': None, # No tabbed plugin - 'AUTHOR': "Eduardo Silva", - 'DESC': "Get dirty size memory usage", - - # Plugin API - 'Plg': None, # Plugin object - - 'top_data': [int], # Top data types needed by memphis core plugin - 'top_cols': ["PDRSS (kb)"] - } diff --git a/services/console/interface/memphis/plugins/dirty_size/Makefile.am b/services/console/interface/memphis/plugins/smaps/Makefile.am similarity index 100% rename from services/console/interface/memphis/plugins/dirty_size/Makefile.am rename to services/console/interface/memphis/plugins/smaps/Makefile.am diff --git a/services/console/interface/memphis/plugins/dirty_size/README b/services/console/interface/memphis/plugins/smaps/README similarity index 100% rename from services/console/interface/memphis/plugins/dirty_size/README rename to services/console/interface/memphis/plugins/smaps/README diff --git a/services/console/interface/memphis/plugins/smaps/__init__.py b/services/console/interface/memphis/plugins/smaps/__init__.py new file mode 100644 index 00000000..5977d4bd --- /dev/null +++ b/services/console/interface/memphis/plugins/smaps/__init__.py @@ -0,0 +1,17 @@ + +import info + + +INTERNALS = { + # Basic information + 'PLGNAME': "SMaps", + 'TABNAME': None, # No tabbed plugin + 'AUTHOR': "Eduardo Silva", + 'DESC': "Get dirty size and reference memory usage", + + # Plugin API + 'Plg': None, # Plugin object + + 'top_data': [int, int], # Top data types needed by memphis core plugin + 'top_cols': ["PDRSS (kb)", "Referenced (kb)"] + } diff --git a/services/console/interface/memphis/plugins/dirty_size/info.py b/services/console/interface/memphis/plugins/smaps/info.py similarity index 68% rename from services/console/interface/memphis/plugins/dirty_size/info.py rename to services/console/interface/memphis/plugins/smaps/info.py index 54a2e7e7..998a1a25 100644 --- a/services/console/interface/memphis/plugins/dirty_size/info.py +++ b/services/console/interface/memphis/plugins/smaps/info.py @@ -8,13 +8,12 @@ def plg_on_top_data_refresh(self, ppinfo): - - dirty_sizes = get_dirty(self, ppinfo['pid']) + smaps = get_data(self, ppinfo['pid']) - # memhis need an array - return [dirty_sizes['private']] + # memphis need an array + return [smaps['private_dirty'], smaps['referenced']] -def get_dirty(pself, pid): +def get_data(pself, pid): ProcAnalysis = pself.INTERNALS['Plg'].proc_analysis(pid) - return ProcAnalysis.DirtyRSS() + return ProcAnalysis.SMaps() diff --git a/services/console/lib/procmem/analysis.py b/services/console/lib/procmem/analysis.py index d2a247a6..e9d7aec8 100644 --- a/services/console/lib/procmem/analysis.py +++ b/services/console/lib/procmem/analysis.py @@ -7,20 +7,22 @@ class Analysis: def __init__(self, pid): self.pid = pid - def DirtyRSS(self): + def SMaps(self): smaps = proc_smaps.ProcSmaps(self.pid) - dirty = [] + private_dirty = 0 + shared_dirty = 0 + referenced = 0 - private = 0 - shared = 0 - for map in smaps.mappings: - private += map.private_dirty - shared += map.shared_dirty + private_dirty += map.private_dirty + shared_dirty += map.shared_dirty + referenced += map.referenced - dirty = {"private": int(private), "shared": int(shared)} + smaps = {"private_dirty": int(private_dirty), \ + "shared_dirty": int(shared_dirty),\ + "referenced": int(referenced)} - return dirty + return smaps def ApproxRealMemoryUsage(self): maps = proc_smaps.ProcMaps(self.pid) diff --git a/services/console/lib/procmem/proc_smaps.py b/services/console/lib/procmem/proc_smaps.py index 174bc6be..422866cc 100644 --- a/services/console/lib/procmem/proc_smaps.py +++ b/services/console/lib/procmem/proc_smaps.py @@ -36,7 +36,8 @@ class ProcSmaps: # Shared_Dirty: 0 kB # Private_Clean: 8 kB # Private_Dirty: 0 kB - + # Referenced: 4 kb -> Introduced in kernel 2.6.22 + while num_lines > 0: fields = lines[line_idx].split (" ", 5) if len (fields) == 6: @@ -51,13 +52,20 @@ class ProcSmaps: shared_dirty = self.parse_smaps_size_line (lines[line_idx + 4]) private_clean = self.parse_smaps_size_line (lines[line_idx + 5]) private_dirty = self.parse_smaps_size_line (lines[line_idx + 6]) + referenced = self.parse_smaps_size_line (lines[line_idx + 7]) name = name.strip () - mapping = Mapping (size, rss, shared_clean, shared_dirty, private_clean, private_dirty, permissions, name) + mapping = Mapping (size, rss, shared_clean, shared_dirty, \ + private_clean, private_dirty, referenced, permissions, name) self.mappings.append (mapping) num_lines -= 8 line_idx += 8 + + self._clear_reference(pid) + + def _clear_reference(self, pid): + os.system("echo 1 > /proc/%s/clear_refs" % pid) # Parses a line of the form "foo: 42 kB" and returns an integer for the "42" field def parse_smaps_size_line (self, line): @@ -66,13 +74,15 @@ class ProcSmaps: return int(fields[1]) class Mapping: - def __init__ (self, size, rss, shared_clean, shared_dirty, private_clean, private_dirty, permissions, name): + def __init__ (self, size, rss, shared_clean, shared_dirty, \ + private_clean, private_dirty, referenced, permissions, name): self.size = size self.rss = rss self.shared_clean = shared_clean self.shared_dirty = shared_dirty self.private_clean = private_clean self.private_dirty = private_dirty + self.referenced = referenced self.permissions = permissions self.name = name From 26a7e087fad007215f420e6d2a270c9b435483ca Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 7 Aug 2007 18:18:15 -0400 Subject: [PATCH 04/34] DevConsole: ups! fix Makefile.am --- services/console/interface/memphis/plugins/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/console/interface/memphis/plugins/Makefile.am b/services/console/interface/memphis/plugins/Makefile.am index a18eafe7..d026419d 100644 --- a/services/console/interface/memphis/plugins/Makefile.am +++ b/services/console/interface/memphis/plugins/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = clean_size cpu dirty_size memphis_init +SUBDIRS = clean_size cpu smaps memphis_init sugardir = $(pkgdatadir)/services/console/interface/memphis/plugins sugar_PYTHON = From 0b355dcbb96a46e496401444377e92577ffd1939 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 8 Aug 2007 03:07:00 +0200 Subject: [PATCH 05/34] First go at support of focusable widgets inside the palette. Not enabled yet because focus confuses our deactivation logic but it seem to work otherwise. --- lib/ui/sugar-menu.c | 43 ++-------- lib/ui/sugar-menu.h | 12 ++- sugar/_sugaruiext.defs | 14 ++-- sugar/_sugaruiext.override | 1 + sugar/graphics/palette.py | 165 ++++++++++++++----------------------- 5 files changed, 82 insertions(+), 153 deletions(-) diff --git a/lib/ui/sugar-menu.c b/lib/ui/sugar-menu.c index 6ed482ee..d822867e 100644 --- a/lib/ui/sugar-menu.c +++ b/lib/ui/sugar-menu.c @@ -28,54 +28,21 @@ static void sugar_menu_init (SugarMenu *menu); G_DEFINE_TYPE(SugarMenu, sugar_menu, GTK_TYPE_MENU) void -sugar_menu_popup(SugarMenu *menu, - int x, - int y) +sugar_menu_set_active(SugarMenu *menu, gboolean active) { - GtkWidget *window; - - window = GTK_MENU(menu)->toplevel; - g_return_if_fail(window != NULL); - - GTK_MENU_SHELL(menu)->active = TRUE; - - gtk_widget_show(GTK_WIDGET(menu)); - - gtk_window_move(GTK_WINDOW(window), x, y); - gtk_widget_show(window); + GTK_MENU_SHELL(menu)->active = active; } void -sugar_menu_popdown(SugarMenu *menu) +sugar_menu_embed(SugarMenu *menu, GtkContainer *parent) { - GtkWidget *window; - - window = GTK_MENU(menu)->toplevel; - g_return_if_fail(window != NULL); - - GTK_MENU_SHELL(menu)->active = FALSE; - - gtk_widget_hide(GTK_WIDGET(menu)); - gtk_widget_hide(window); -} - -static void -sugar_menu_size_request (GtkWidget *widget, - GtkRequisition *requisition) -{ - SugarMenu *menu = SUGAR_MENU(widget); - - (* GTK_WIDGET_CLASS (sugar_menu_parent_class)->size_request) (widget, requisition); - - requisition->width = MAX(requisition->width, menu->min_width); + GTK_MENU(menu)->toplevel = gtk_widget_get_toplevel(GTK_WIDGET(parent)); + gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(parent)); } static void sugar_menu_class_init(SugarMenuClass *menu_class) { - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(menu_class); - - widget_class->size_request = sugar_menu_size_request; } static void diff --git a/lib/ui/sugar-menu.h b/lib/ui/sugar-menu.h index 24e2865b..05e28024 100644 --- a/lib/ui/sugar-menu.h +++ b/lib/ui/sugar-menu.h @@ -44,13 +44,11 @@ struct _SugarMenuClass { GtkMenuClass base_class; }; -GType sugar_menu_get_type (void); -void sugar_menu_popup (SugarMenu *menu, - int x, - int y); -void sugar_menu_set_min_width (SugarMenu *menu, - int min_width); -void sugar_menu_popdown (SugarMenu *menu); +GType sugar_menu_get_type (void); +void sugar_menu_set_active (SugarMenu *menu, + gboolean active); +void sugar_menu_embed (SugarMenu *menu, + GtkContainer *parent); G_END_DECLS diff --git a/sugar/_sugaruiext.defs b/sugar/_sugaruiext.defs index c4efbbc3..456344a1 100644 --- a/sugar/_sugaruiext.defs +++ b/sugar/_sugaruiext.defs @@ -26,20 +26,22 @@ ;; From sugar-menu.h -(define-method popup +(define-method set_active (of-object "SugarMenu") - (c-name "sugar_menu_popup") + (c-name "sugar_menu_set_active") (return-type "none") (parameters - '("gint" "x") - '("gint" "y") + '("gboolean" "active") ) ) -(define-method popdown +(define-method embed (of-object "SugarMenu") - (c-name "sugar_menu_popdown") + (c-name "sugar_menu_embed") (return-type "none") + (parameters + '("GtkContainer" "container") + ) ) ;; From sugar-key-grabber.h diff --git a/sugar/_sugaruiext.override b/sugar/_sugaruiext.override index 02e900ea..6daafc3b 100644 --- a/sugar/_sugaruiext.override +++ b/sugar/_sugaruiext.override @@ -18,6 +18,7 @@ modulename _sugarext import gobject.GObject as PyGObject_Type import gtk.Entry as PyGtkEntry_Type import gtk.Menu as PyGtkMenu_Type +import gtk.Container as PyGtkContainer_Type import gtk.gdk.Window as PyGdkWindow_Type %% ignore-glob diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index 45ac0572..8d4c48f3 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -36,7 +36,7 @@ _RIGHT_TOP = 5 _TOP_LEFT = 6 _TOP_RIGHT = 7 -class Palette(gobject.GObject): +class Palette(gtk.Window): DEFAULT = 0 AT_CURSOR = 1 AROUND = 2 @@ -65,12 +65,16 @@ class Palette(gobject.GObject): } def __init__(self, label, accel_path=None): - gobject.GObject.__init__(self) + gtk.Window.__init__(self) + + self.set_decorated(False) + self.set_resizable(False) + self.connect('realize', self._realize_cb) self._full_request = [0, 0] self._cursor_x = 0 self._cursor_y = 0 - self._state = self._SECONDARY + self._state = self._PRIMARY self._invoker = None self._group_id = None self._up = False @@ -86,56 +90,56 @@ class Palette(gobject.GObject): self._popdown_anim = animator.Animator(0.6, 10) self._popdown_anim.add(_PopdownAnimation(self)) + vbox = gtk.VBox() + vbox.set_border_width(style.DEFAULT_PADDING) + + self._label = gtk.Label() + vbox.pack_start(self._label, False) + + self._separator = gtk.HSeparator() + vbox.pack_start(self._separator) + + menu_box = gtk.VBox() + vbox.pack_start(menu_box) + menu_box.show() + self._menu = _sugaruiext.Menu() + self._menu.embed(menu_box) - self._primary = _PrimaryMenuItem(label, accel_path) - self._menu.append(self._primary) - self._primary.show() + self._content = gtk.VBox() + vbox.pack_start(self._content) - self._separator = gtk.SeparatorMenuItem() - self._menu.append(self._separator) + self._button_bar = gtk.HButtonBox() + vbox.pack_start(self._button_bar) - self._content = _ContentMenuItem() - self._menu.append(self._content) + self.add(vbox) + vbox.show() - self._button_bar = _ButtonBarMenuItem() - self._menu.append(self._button_bar) + self.connect('enter-notify-event', + self._enter_notify_event_cb) + self.connect('leave-notify-event', + self._leave_notify_event_cb) - self._menu.connect('enter-notify-event', - self._enter_notify_event_cb) - self._menu.connect('leave-notify-event', - self._leave_notify_event_cb) + self.set_primary_text(label, accel_path) def is_up(self): return self._up def set_primary_text(self, label, accel_path=None): - self._primary.set_label(label, accel_path) + self._label.set_text(label) + self._label.show() def append_menu_item(self, item): - self._separator.show() - self._menu.insert(item, len(self._menu.get_children()) - 2) + self._menu.append(item) def insert_menu_item(self, item, index=-1): - self._separator.show() - if index < 0: - self._menu.insert(item, len(self._menu.get_children()) - 2) - else: - self._menu.insert(item, index + 2) + self._menu.insert(item, index) def remove_menu_item(self, index): - if index > len(self._menu.get_children()) - 4: - raise ValueError('index %i out of range' % index) - self._menu.remove(self._menu.get_children()[index + 2]) - if len(self._menu.get_children()) == 0: - self._separator.hide() + self._menu.remove(self._menu.get_children()[index]) - def menu_item_count(self): - return len(self._menu.get_children()) - 4 - def set_content(self, widget): - self._content.set_widget(widget) - self._content.show() + self._content.pack_start(widget) def append_button(self, button): self._button_bar.append_button(button) @@ -168,6 +172,10 @@ class Palette(gobject.GObject): else: raise AssertionError + def _realize_cb(self, widget): + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self.window.set_accept_focus(False) + def _in_screen(self, x, y): [width, height] = self._full_request screen_area = self._invoker.get_screen_area() @@ -182,7 +190,7 @@ class Palette(gobject.GObject): if inv_rect == None: inv_rect = self._invoker.get_rect() - palette_width, palette_height = self._menu.size_request() + palette_width, palette_height = self.size_request() x = inv_rect.x + inv_rect.width * invoker_halign + \ palette_width * palette_halign @@ -241,12 +249,12 @@ class Palette(gobject.GObject): def _update_full_request(self): state = self._state - self._menu.set_size_request(-1, -1) + self.set_size_request(-1, -1) self._set_state(self._SECONDARY) - self._full_request = self._menu.size_request() + self._full_request = self.size_request() - self._menu.set_size_request(self._full_request[0], -1) + self.set_size_request(self._full_request[0], -1) self._set_state(state) @@ -282,7 +290,7 @@ class Palette(gobject.GObject): elif position == self.TOP: x, y = self._get_top_position() - self._menu.popup(x, y) + self.move(x, y) def _show(self): if self._up: @@ -296,6 +304,8 @@ class Palette(gobject.GObject): self._palette_observer_popup_cb) self._update_position() + self._menu.set_active(True) + self.show() self._up = True _palette_observer.emit('popup', self) @@ -305,7 +315,8 @@ class Palette(gobject.GObject): if not self._palette_popup_sid is None: _palette_observer.disconnect(self._palette_popup_sid) self._palette_popup_sid = None - self._menu.popdown() + self._menu.set_active(False) + self.hide() self._up = False self.emit('popdown') @@ -329,25 +340,22 @@ class Palette(gobject.GObject): return if state == self._PRIMARY: - self._primary.show() - for menu_item in self._menu.get_children()[1:]: - menu_item.hide() + self._menu.hide() + self._content.hide() + self._separator.hide() + self._button_bar.hide() elif state == self._SECONDARY: - middle_menu_items = self._menu.get_children() - middle_menu_items = middle_menu_items[2:len(middle_menu_items) - 2] - if middle_menu_items or \ - not self._content.is_empty() or \ - not self._button_bar.is_empty(): - self._separator.show() + has_menu_items = len(self._menu.get_children()) > 0 + self._menu.props.visible = has_menu_items - for menu_item in middle_menu_items: - menu_item.show() + has_content = len(self._content.get_children()) > 0 + self._content.props.visible = has_content - if not self._content.is_empty(): - self._content.show() + has_buttons = len(self._button_bar.get_children()) > 0 + self._button_bar.props.visible = has_buttons - if not self._button_bar.is_empty(): - self._button_bar.show() + has_separator = has_buttons or has_menu_items or has_content + self._separator.props.visible = has_separator self._state = state @@ -373,53 +381,6 @@ class Palette(gobject.GObject): if self != palette: self._hide() -class _PrimaryMenuItem(gtk.MenuItem): - def __init__(self, label, accel_path): - gtk.MenuItem.__init__(self) - self.set_border_width(style.DEFAULT_PADDING) - self._set_label(label, accel_path) - - def set_label(self, label, accel_path): - self.remove(self._label) - self._set_label(label, accel_path) - - def _set_label(self, label, accel_path): - self._label = gtk.AccelLabel(label) - self._label.set_accel_widget(self) - - if accel_path: - self.set_accel_path(accel_path) - self._label.set_alignment(0.0, 0.5) - - self.add(self._label) - self._label.show() - -class _ContentMenuItem(gtk.MenuItem): - def __init__(self): - gtk.MenuItem.__init__(self) - - def set_widget(self, widget): - if self.child: - self.remove(self.child) - self.add(widget) - - def is_empty(self): - return self.child is None or not self.child.props.visible - -class _ButtonBarMenuItem(gtk.MenuItem): - def __init__(self): - gtk.MenuItem.__init__(self) - - self._hbar = gtk.HButtonBox() - self.add(self._hbar) - self._hbar.show() - - def append_button(self, button): - self._hbar.pack_start(button) - - def is_empty(self): - return len(self._hbar.get_children()) == 0 - class _PopupAnimation(animator.Animation): def __init__(self, palette): animator.Animation.__init__(self, 0.0, 1.0) From 8a6ea5b13f9761d259c0b58cb18071ce09f58691 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Wed, 8 Aug 2007 10:34:11 +0200 Subject: [PATCH 06/34] Added missing file in configure.ac --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 2e7e3399..f6626693 100644 --- a/configure.ac +++ b/configure.ac @@ -64,7 +64,7 @@ services/console/Makefile services/console/interface/Makefile services/console/interface/xo/Makefile services/console/interface/memphis/plugins/clean_size/Makefile -services/console/interface/memphis/plugins/dirty_size/Makefile +services/console/interface/memphis/plugins/smaps/Makefile services/console/interface/memphis/plugins/Makefile services/console/interface/memphis/plugins/memphis_init/Makefile services/console/interface/memphis/plugins/cpu/Makefile From 51ce8abdef7a9af0ebef554e4b2904f45f8d747e Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Wed, 8 Aug 2007 10:56:12 +0200 Subject: [PATCH 07/34] Adapt to new color constants. --- shell/view/clipboardicon.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py index a47104d8..2e60e36b 100644 --- a/shell/view/clipboardicon.py +++ b/shell/view/clipboardicon.py @@ -73,9 +73,9 @@ class ClipboardIcon(CanvasIcon): self._selected = selected if selected: if not self._hover: - self.props.background_color = style.COLOR_PANEL_GREY.get_int() + self.props.background_color = style.COLOR_SELECTION_GREY.get_int() else: - self.props.background_color = style.COLOR_TOOLBAR_GREY.get_int() + self.props.background_color = style.COLOR_PANEL_GREY.get_int() def set_state(self, name, percent, icon_name, preview, activity): cb_service = clipboardservice.get_instance() @@ -107,11 +107,11 @@ class ClipboardIcon(CanvasIcon): def prelight(self, enter): if enter: self._hover = True - self.props.background_color = color.BLACK.get_int() + self.props.background_color = style.COLOR_BLACK.get_int() else: self._hover = False if self._selected: - self.props.background_color = color.DESKTOP_BACKGROUND.get_int() + self.props.background_color = style.COLOR_SELECTION_GREY.get_int() else: - self.props.background_color = color.TOOLBAR_BACKGROUND.get_int() + self.props.background_color = style.COLOR_PANEL_GREY.get_int() From 3a33e4cedd8b31afe0856ed76d68cecba535a193 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 8 Aug 2007 11:53:41 +0200 Subject: [PATCH 08/34] Enable focus when there is content. Remove the focus out check on the invoker toplevel, we will have to do that differently. --- sugar/graphics/palette.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index 8d4c48f3..0fb7182c 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -139,6 +139,7 @@ class Palette(gtk.Window): self._menu.remove(self._menu.get_children()[index]) def set_content(self, widget): + self._update_accept_focus() self._content.pack_start(widget) def append_button(self, button): @@ -158,7 +159,6 @@ class Palette(gtk.Window): self._invoker = value self._invoker.connect('mouse-enter', self._invoker_mouse_enter_cb) self._invoker.connect('mouse-leave', self._invoker_mouse_leave_cb) - self._invoker.connect('focus-out', self._invoker_focus_out_cb) elif pspec.name == 'position': self._position = value else: @@ -172,9 +172,14 @@ class Palette(gtk.Window): else: raise AssertionError + def _update_accept_focus(self): + accept_focus = len(self._content.get_children()) + if self.window: + self.window.set_accept_focus(accept_focus) + def _realize_cb(self, widget): self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) - self.window.set_accept_focus(False) + self._update_accept_focus() def _in_screen(self, x, y): [width, height] = self._full_request @@ -299,9 +304,8 @@ class Palette(gtk.Window): self._update_cursor_position() self._update_full_request() - self._invoker.connect_to_parent() - self._palette_popup_sid = _palette_observer.connect('popup', - self._palette_observer_popup_cb) + self._palette_popup_sid = _palette_observer.connect( + 'popup', self._palette_observer_popup_cb) self._update_position() self._menu.set_active(True) @@ -365,9 +369,6 @@ class Palette(gtk.Window): def _invoker_mouse_leave_cb(self, invoker): self.popdown() - def _invoker_focus_out_cb(self, invoker): - self._hide() - def _enter_notify_event_cb(self, widget, event): if event.detail == gtk.gdk.NOTIFY_NONLINEAR: self._popdown_anim.stop() @@ -430,13 +431,6 @@ class Invoker(gobject.GObject): height = gtk.gdk.screen_height() return gtk.gdk.Rectangle(0, 0, width, height) - def connect_to_parent(self): - window = self.get_toplevel() - window.connect('focus-out-event', self._window_focus_out_event_cb) - - def _window_focus_out_event_cb(self, widget, event): - self.emit('focus-out') - class WidgetInvoker(Invoker): def __init__(self, widget): Invoker.__init__(self) From 2c0ad08fcfed2dba9677b46118e89c3a614f8316 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 8 Aug 2007 11:53:41 +0200 Subject: [PATCH 09/34] Call embed after the widget hierarchy is setup so that we get the right toplevel. --- sugar/graphics/palette.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index 0fb7182c..9d578773 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -103,9 +103,6 @@ class Palette(gtk.Window): vbox.pack_start(menu_box) menu_box.show() - self._menu = _sugaruiext.Menu() - self._menu.embed(menu_box) - self._content = gtk.VBox() vbox.pack_start(self._content) @@ -115,6 +112,9 @@ class Palette(gtk.Window): self.add(vbox) vbox.show() + self._menu = _sugaruiext.Menu() + self._menu.embed(menu_box) + self.connect('enter-notify-event', self._enter_notify_event_cb) self.connect('leave-notify-event', From 6dd6b0275b34ab6aa029427ee33ec59686319f21 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 8 Aug 2007 12:56:19 +0200 Subject: [PATCH 10/34] Expose palette.menu rather than wrapping methods. --- shell/view/BuddyMenu.py | 5 ++- shell/view/home/HomeBox.py | 2 +- shell/view/home/activitiesdonut.py | 4 +- sugar/graphics/palette.py | 72 ++++++++++++++++-------------- 4 files changed, 45 insertions(+), 38 deletions(-) diff --git a/shell/view/BuddyMenu.py b/shell/view/BuddyMenu.py index 3162ab1f..8022b0b3 100644 --- a/shell/view/BuddyMenu.py +++ b/shell/view/BuddyMenu.py @@ -85,7 +85,8 @@ class BuddyMenu(Palette): else: menu_item = MenuItem(_('Make friend'), 'stock-add') menu_item.connect('activate', self._make_friend_cb) - self.append_menu_item(menu_item) + + self.menu.append(menu_item) menu_item.show() activity = shell_model.get_home().get_current_activity() @@ -96,7 +97,7 @@ class BuddyMenu(Palette): menu_item = MenuItem(_('Invite'), 'stock-invite') menu_item.connect('activate', self._invite_friend_cb) - self.append_menu_item(menu_item) + self.menu.append(menu_item) menu_item.show() def _buddy_icon_changed_cb(self, buddy): diff --git a/shell/view/home/HomeBox.py b/shell/view/home/HomeBox.py index bfb4265f..2fa21834 100644 --- a/shell/view/home/HomeBox.py +++ b/shell/view/home/HomeBox.py @@ -130,7 +130,7 @@ class HomeMyIcon(MyIcon): shutdown_menu_item = gtk.MenuItem(_('Shutdown')) shutdown_menu_item.connect('activate', self._shutdown_activate_cb) - palette.append_menu_item(shutdown_menu_item) + palette.menu.append(shutdown_menu_item) shutdown_menu_item.show() self.set_palette(palette) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index 0c690b2e..28c4c087 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -88,14 +88,14 @@ class ActivityIcon(CanvasIcon): resume_menu_item = gtk.MenuItem(_('Resume')) resume_menu_item.connect('activate', self._resume_activate_cb) - palette.append_menu_item(resume_menu_item) + palette.menu.append(resume_menu_item) resume_menu_item.show() # FIXME: kludge if self._activity.get_type() != "org.laptop.JournalActivity": stop_menu_item = gtk.MenuItem(_('Stop')) stop_menu_item.connect('activate', self._stop_activate_cb) - palette.append_menu_item(stop_menu_item) + palette.menu.append(stop_menu_item) stop_menu_item.show() def _launching_changed_cb(self, activity, pspec): diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index 9d578773..4d09499c 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -96,24 +96,30 @@ class Palette(gtk.Window): self._label = gtk.Label() vbox.pack_start(self._label, False) + self._secondary_box = gtk.VBox() + vbox.pack_start(self._secondary_box) + self._separator = gtk.HSeparator() - vbox.pack_start(self._separator) + self._secondary_box.pack_start(self._separator) menu_box = gtk.VBox() - vbox.pack_start(menu_box) + self._secondary_box.pack_start(menu_box) menu_box.show() self._content = gtk.VBox() - vbox.pack_start(self._content) + self._secondary_box.pack_start(self._content) + self._content.show() self._button_bar = gtk.HButtonBox() - vbox.pack_start(self._button_bar) + self._secondary_box.pack_start(self._button_bar) + self._button_bar.show() self.add(vbox) vbox.show() - self._menu = _sugaruiext.Menu() - self._menu.embed(menu_box) + self.menu = _Menu(self) + self.menu.embed(menu_box) + self.menu.show() self.connect('enter-notify-event', self._enter_notify_event_cb) @@ -129,18 +135,15 @@ class Palette(gtk.Window): self._label.set_text(label) self._label.show() - def append_menu_item(self, item): - self._menu.append(item) - - def insert_menu_item(self, item, index=-1): - self._menu.insert(item, index) - - def remove_menu_item(self, index): - self._menu.remove(self._menu.get_children()[index]) - def set_content(self, widget): + if len(self._content.get_children()) > 0: + self.remove(self._content.get_children()[0]) + + if widget is not None: + self._content.add(widget) + self._update_accept_focus() - self._content.pack_start(widget) + self._update_separator() def append_button(self, button): self._button_bar.append_button(button) @@ -172,6 +175,11 @@ class Palette(gtk.Window): else: raise AssertionError + def _update_separator(self): + visible = len(self.menu.get_children()) > 0 or \ + len(self._content.get_children()) > 0 + self._separator.props.visible = visible + def _update_accept_focus(self): accept_focus = len(self._content.get_children()) if self.window: @@ -308,7 +316,7 @@ class Palette(gtk.Window): 'popup', self._palette_observer_popup_cb) self._update_position() - self._menu.set_active(True) + self.menu.set_active(True) self.show() self._up = True @@ -319,7 +327,7 @@ class Palette(gtk.Window): if not self._palette_popup_sid is None: _palette_observer.disconnect(self._palette_popup_sid) self._palette_popup_sid = None - self._menu.set_active(False) + self.menu.set_active(False) self.hide() self._up = False @@ -344,22 +352,9 @@ class Palette(gtk.Window): return if state == self._PRIMARY: - self._menu.hide() - self._content.hide() - self._separator.hide() - self._button_bar.hide() + self._secondary_box.hide() elif state == self._SECONDARY: - has_menu_items = len(self._menu.get_children()) > 0 - self._menu.props.visible = has_menu_items - - has_content = len(self._content.get_children()) > 0 - self._content.props.visible = has_content - - has_buttons = len(self._button_bar.get_children()) > 0 - self._button_bar.props.visible = has_buttons - - has_separator = has_buttons or has_menu_items or has_content - self._separator.props.visible = has_separator + self._secondary_box.show() self._state = state @@ -382,6 +377,17 @@ class Palette(gtk.Window): if self != palette: self._hide() +class _Menu(_sugaruiext.Menu): + __gtype_name__ = 'SugarPaletteMenu' + + def __init__(self, palette): + _sugaruiext.Menu.__init__(self) + self._palette = palette + + def do_insert(self, item, position): + _sugaruiext.Menu.do_insert(self, item, position) + self._palette._update_separator() + class _PopupAnimation(animator.Animation): def __init__(self, palette): animator.Animation.__init__(self, 0.0, 1.0) From 6ae38464156c5ba2c06826ab9d8b9d4bd226c2bb Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 8 Aug 2007 13:03:09 +0200 Subject: [PATCH 11/34] Expose palette.action_bar --- sugar/graphics/palette.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index 4d09499c..a8774880 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -110,9 +110,9 @@ class Palette(gtk.Window): self._secondary_box.pack_start(self._content) self._content.show() - self._button_bar = gtk.HButtonBox() - self._secondary_box.pack_start(self._button_bar) - self._button_bar.show() + self.action_bar = PaletteActionBar() + self._secondary_box.pack_start(self.action_bar) + self.action_bar.show() self.add(vbox) vbox.show() @@ -145,10 +145,6 @@ class Palette(gtk.Window): self._update_accept_focus() self._update_separator() - def append_button(self, button): - self._button_bar.append_button(button) - self._button_bar.show() - def set_group_id(self, group_id): if self._group_id: group = palettegroup.get_group(self._group_id) @@ -377,6 +373,18 @@ class Palette(gtk.Window): if self != palette: self._hide() +class PaletteActionBar(gtk.HButtonBox): + def add_action(label, icon_name=None): + button = Button(label) + + if icon_name: + icon = Icon(icon_name) + button.set_image(icon) + icon.show() + + self.pack_start(button) + button.show() + class _Menu(_sugaruiext.Menu): __gtype_name__ = 'SugarPaletteMenu' From 1145f0f99c727656a6d0772255da6b5e03abd3f6 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 8 Aug 2007 14:41:30 +0200 Subject: [PATCH 12/34] Override menu shell deactivate to go through the palette. --- sugar/graphics/palette.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index a8774880..70e27f9a 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -396,6 +396,9 @@ class _Menu(_sugaruiext.Menu): _sugaruiext.Menu.do_insert(self, item, position) self._palette._update_separator() + def do_deactivate(self): + self._palette._hide() + class _PopupAnimation(animator.Animation): def __init__(self, palette): animator.Animation.__init__(self, 0.0, 1.0) From 670eb981acf958437a50569009f45b162cd77060 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Wed, 8 Aug 2007 14:56:43 +0200 Subject: [PATCH 13/34] Ignore only INFERIOR enter/leave events. --- sugar/graphics/palette.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index 70e27f9a..e8532024 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -361,12 +361,12 @@ class Palette(gtk.Window): self.popdown() def _enter_notify_event_cb(self, widget, event): - if event.detail == gtk.gdk.NOTIFY_NONLINEAR: + if event.detail != gtk.gdk.NOTIFY_INFERIOR: self._popdown_anim.stop() self._secondary_anim.start() def _leave_notify_event_cb(self, widget, event): - if event.detail == gtk.gdk.NOTIFY_NONLINEAR: + if event.detail != gtk.gdk.NOTIFY_INFERIOR: self.popdown() def _palette_observer_popup_cb(self, observer, palette): From 6ca80a71461d414695dd83448d1d75d352be0438 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 8 Aug 2007 09:27:37 -0400 Subject: [PATCH 14/34] Always get the activity list from the shell view, not sometimes from the model --- shell/view/BuddyMenu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/view/BuddyMenu.py b/shell/view/BuddyMenu.py index 3162ab1f..e986e034 100644 --- a/shell/view/BuddyMenu.py +++ b/shell/view/BuddyMenu.py @@ -88,9 +88,9 @@ class BuddyMenu(Palette): self.append_menu_item(menu_item) menu_item.show() - activity = shell_model.get_home().get_current_activity() + activity = self._shell.get_current_activity() if activity != None: - activity_ps = pservice.get_activity(activity.get_activity_id()) + activity_ps = pservice.get_activity(activity.get_id()) # FIXME check that the buddy is not in the activity already From c85c89ce839e952b05b20853b715abbf1b268ca5 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 8 Aug 2007 09:30:45 -0400 Subject: [PATCH 15/34] Make Journal active at startup. #2159 Also distinguish between "active" in the sense of "the user is actually using this activity" and "active" in the sense of "the activity would be active *if* the user switched to Activity view". It's the latter sense, now called "pending" that the Journal is in at startup. Pending is also used for the next-up activity when you quit the current active activity. --- shell/model/homemodel.py | 117 +++++++++++++++++++---------- shell/view/Shell.py | 11 +++ shell/view/home/activitiesdonut.py | 4 +- 3 files changed, 89 insertions(+), 43 deletions(-) diff --git a/shell/model/homemodel.py b/shell/model/homemodel.py index 7d1039b1..c5e761cd 100644 --- a/shell/model/homemodel.py +++ b/shell/model/homemodel.py @@ -51,7 +51,10 @@ class HomeModel(gobject.GObject): ([gobject.TYPE_PYOBJECT])), 'active-activity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), + 'pending-activity-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) } def __init__(self): @@ -59,7 +62,8 @@ class HomeModel(gobject.GObject): self._activities = [] self._bundle_registry = bundleregistry.get_registry() - self._current_activity = None + self._active_activity = None + self._pending_activity = None screen = wnck.screen_get_default() screen.connect('window-opened', self._window_opened_cb) @@ -67,8 +71,55 @@ class HomeModel(gobject.GObject): screen.connect('active-window-changed', self._active_window_changed_cb) - def get_current_activity(self): - return self._current_activity + def get_pending_activity(self): + """Returns the activity that would be seen in the Activity zoom level + + In the Home (or Neighborhood or Groups) zoom level, this + indicates the activity that would become active if the user + switched to the Activity zoom level. (In the Activity zoom + level, this just returns the currently-active activity.) + Unlike get_active_activity(), this never returns None as long + as there is any activity running. + """ + return self._pending_activity + + def _set_pending_activity(self, activity): + if self._pending_activity == activity: + return + + self._pending_activity = activity + self.emit('pending-activity-changed', self._pending_activity) + + def get_active_activity(self): + """Returns the activity that the user is currently working in + + In the Activity zoom level, this returns the currently-active + activity. In the other zoom levels, it returns the activity + that was most-recently active in the Activity zoom level, or + None if the most-recently-active activity is no longer + running. + """ + return self._active_activity + + def _set_active_activity(self, activity): + if self._active_activity == activity: + return + + if self._active_activity: + service = self._active_activity.get_service() + if service: + service.set_active(False, + reply_handler=self._set_active_success, + error_handler=self._set_active_error) + if activity: + service = activity.get_service() + if service: + service.set_active(True, + reply_handler=self._set_active_success, + error_handler=self._set_active_error) + + self._active_activity = activity + self.emit('active-activity-changed', self._active_activity) def __iter__(self): return iter(self._activities) @@ -106,12 +157,12 @@ class HomeModel(gobject.GObject): activity.props.launching = False self.emit('activity-started', activity) + if self._pending_activity is None: + self._set_pending_activity(activity) + def _window_closed_cb(self, screen, window): if window.get_window_type() == wnck.WINDOW_NORMAL: self._remove_activity_by_xid(window.get_xid()) - if not self._activities: - self.emit('active-activity-changed', None) - self._notify_activity_activation(self._current_activity, None) def _get_activity_by_xid(self, xid): for activity in self._activities: @@ -125,24 +176,6 @@ class HomeModel(gobject.GObject): return activity return None - def _notify_activity_activation(self, old_activity, new_activity): - if old_activity == new_activity: - return - - if old_activity: - service = old_activity.get_service() - if service: - service.set_active(False, - reply_handler=self._set_active_success, - error_handler=self._set_active_error) - - if new_activity: - service = new_activity.get_service() - if service: - service.set_active(True, - reply_handler=self._set_active_success, - error_handler=self._set_active_error) - def _set_active_success(self): pass @@ -151,32 +184,34 @@ class HomeModel(gobject.GObject): def _active_window_changed_cb(self, screen): window = screen.get_active_window() - if window == None: - self.emit('active-activity-changed', None) - self._notify_activity_activation(self._current_activity, None) - return - if window.get_window_type() != wnck.WINDOW_NORMAL: + if window is None or window.get_window_type() != wnck.WINDOW_NORMAL: return xid = window.get_xid() - act = self._get_activity_by_xid(window.get_xid()) - if act: - self._notify_activity_activation(self._current_activity, act) - self._current_activity = act - else: - self._notify_activity_activation(self._current_activity, None) - self._current_activity = None + act = self._get_activity_by_xid(xid) + if act is None: logging.error('Model for window %d does not exist.' % xid) - - self.emit('active-activity-changed', self._current_activity) + self._set_pending_activity(act) + self._set_active_activity(act) def _add_activity(self, activity): self._activities.append(activity) self.emit('activity-added', activity) def _remove_activity(self, activity): - if activity == self._current_activity: - self._current_activity = None + if activity == self._active_activity: + self._set_active_activity(None) + # Figure out the new _pending_activity. + windows = wnck.screen_get_default().get_windows_stacked() + windows.reverse() + for window in windows: + new_activity = self._get_activity_by_xid(window.get_xid()) + if new_activity is not None: + self._set_pending_activity(new_activity) + break + else: + logging.error('No activities are running') + self._set_pending_activity(None) self.emit('activity-removed', activity) self._activities.remove(activity) diff --git a/shell/view/Shell.py b/shell/view/Shell.py index 697dc1c8..af9036a4 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -47,6 +47,7 @@ class Shell(gobject.GObject): self._hosts = {} self._screen = wnck.screen_get_default() self._current_host = None + self._pending_host = None self._screen_rotation = 0 self._key_handler = KeyHandler(self) @@ -65,6 +66,8 @@ class Shell(gobject.GObject): home_model.connect('activity-removed', self._activity_removed_cb) home_model.connect('active-activity-changed', self._active_activity_changed_cb) + home_model.connect('pending-activity-changed', + self._pending_activity_changed_cb) # Unfreeze the display when it's stable hw_manager = hardwaremanager.get_manager() @@ -100,6 +103,12 @@ class Shell(gobject.GObject): self._current_host = host + def _pending_activity_changed_cb(self, home_model, home_activity): + if home_activity: + self._pending_host = self._hosts[home_activity.get_xid()] + else: + self._pending_host = None + def get_model(self): return self._model @@ -156,6 +165,8 @@ class Shell(gobject.GObject): return if level == ShellModel.ZOOM_ACTIVITY: + if self._pending_host is not None: + self._pending_host.present() self._screen.toggle_showing_desktop(False) else: self._model.set_zoom_level(level) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index 0c690b2e..f629fe9d 100644 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -189,7 +189,7 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem): self._model = shell.get_model().get_home() self._model.connect('activity-added', self._activity_added_cb) self._model.connect('activity-removed', self._activity_removed_cb) - self._model.connect('active-activity-changed', self._activity_changed_cb) + self._model.connect('pending-activity-changed', self._activity_changed_cb) self.connect('button-release-event', self._button_release_event_cb) @@ -385,7 +385,7 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem): cr.fill() # Selected Wedge - current_activity = self._model.get_current_activity() + current_activity = self._model.get_pending_activity() if current_activity is not None: selected_index = self._model.index(current_activity) [angle_start, angle_end] = self._get_angles(selected_index) From bfcab6b0b64d1b10d4010c34596e748f54335ba9 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Wed, 8 Aug 2007 09:31:50 -0400 Subject: [PATCH 16/34] Fix objectchooser, replace frame with canvasroundbox module --- sugar/graphics/objectchooser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sugar/graphics/objectchooser.py b/sugar/graphics/objectchooser.py index c75cec0a..5b09e13d 100644 --- a/sugar/graphics/objectchooser.py +++ b/sugar/graphics/objectchooser.py @@ -21,12 +21,12 @@ import time import gtk import hippo -from sugar.graphics.frame import Frame from sugar.activity.bundle import Bundle from sugar.date import Date from sugar.graphics import style from sugar.graphics.canvasicon import CanvasIcon from sugar.graphics.xocolor import XoColor +from sugar.graphics.canvasroundbox import CanvasRoundBox from sugar.datastore import datastore from sugar import activity from sugar.objects import objecttype @@ -95,12 +95,12 @@ class ObjectChooser(gtk.Dialog): else: return None -class CollapsedEntry(Frame): +class CollapsedEntry(CanvasRoundBox): _DATE_COL_WIDTH = style.zoom(100) _BUDDIES_COL_WIDTH = style.zoom(50) def __init__(self, jobject): - Frame.__init__(self) + CanvasRoundBox.__init__(self) self.props.box_height = style.zoom(75) self.props.spacing = style.DEFAULT_SPACING self.props.border_color = style.COLOR_BLACK.get_int() From fa8bcd2ba58fca8605bc2fea72478a23f9bd9998 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Wed, 8 Aug 2007 18:08:07 +0200 Subject: [PATCH 17/34] Move the activity register to the clipboard service. --- services/clipboard/Makefile.am | 14 ++- services/clipboard/activityregistryservice.py | 104 ++++++++++++++++++ .../clipboard}/bundleregistry.py | 7 ++ services/clipboard/clipboardobject.py | 25 +---- services/clipboard/clipboardservice.py | 2 +- .../org.laptop.ActivityRegistry.service.in | 4 + services/clipboard/sugar-clipboard | 3 + shell/model/Makefile.am | 1 - shell/shellservice.py | 69 ------------ sugar/activity/registry.py | 12 +- 10 files changed, 140 insertions(+), 101 deletions(-) create mode 100644 services/clipboard/activityregistryservice.py rename {shell/model => services/clipboard}/bundleregistry.py (94%) create mode 100644 services/clipboard/org.laptop.ActivityRegistry.service.in diff --git a/services/clipboard/Makefile.am b/services/clipboard/Makefile.am index e5a03e43..6d2cd9c9 100644 --- a/services/clipboard/Makefile.am +++ b/services/clipboard/Makefile.am @@ -1,13 +1,18 @@ servicedir = $(datadir)/dbus-1/services service_in_files = \ + org.laptop.ActivityRegistry.service.in \ org.laptop.Clipboard.service.in \ org.laptop.ObjectTypeRegistry.service.in service_DATA = \ + org.laptop.ActivityRegistry.service \ org.laptop.Clipboard.service \ org.laptop.ObjectTypeRegistry.service +org.laptop.ActivityRegistry.service: org.laptop.ActivityRegistry.service.in Makefile + @sed -e "s|\@bindir\@|$(bindir)|" $< > $@ + org.laptop.Clipboard.service: org.laptop.Clipboard.service.in Makefile @sed -e "s|\@bindir\@|$(bindir)|" $< > $@ @@ -16,10 +21,11 @@ org.laptop.ObjectTypeRegistry.service: org.laptop.ObjectTypeRegistry.service.in sugardir = $(pkgdatadir)/services/clipboard -sugar_PYTHON = \ - __init__.py \ - clipboardobject.py \ - clipboardservice.py \ +sugar_PYTHON = \ + __init__.py \ + activityregistryservice.py \ + clipboardobject.py \ + clipboardservice.py \ objecttypeservice.py bin_SCRIPTS = sugar-clipboard diff --git a/services/clipboard/activityregistryservice.py b/services/clipboard/activityregistryservice.py new file mode 100644 index 00000000..60293d47 --- /dev/null +++ b/services/clipboard/activityregistryservice.py @@ -0,0 +1,104 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2007 One Laptop Per Child +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import dbus +import dbus.service + +import bundleregistry + +_ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry' +_ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry' +_ACTIVITY_REGISTRY_PATH = '/org/laptop/ActivityRegistry' + +class ActivityRegistry(dbus.service.Object): + def __init__(self): + bus = dbus.SessionBus() + bus_name = dbus.service.BusName(_ACTIVITY_REGISTRY_SERVICE_NAME, bus=bus) + dbus.service.Object.__init__(self, bus_name, _ACTIVITY_REGISTRY_PATH) + + bundle_registry = bundleregistry.get_registry() + bundle_registry.connect('bundle-added', self._bundle_added_cb) + + @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, + in_signature='s', out_signature='b') + def AddBundle(self, bundle_path): + '''Register the activity bundle with the global registry + + bundle_path -- path to the activity bundle's root directory, + that is, the directory with activity/activity.info as a + child of the directory. + + The bundleregistry.BundleRegistry is responsible for setting + up a set of d-bus service mappings for each available activity. + ''' + registry = bundleregistry.get_registry() + return registry.add_bundle(bundle_path) + + @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, + in_signature='s', out_signature='a{sv}') + def GetActivity(self, service_name): + registry = bundleregistry.get_registry() + bundle = registry.get_bundle(service_name) + if not bundle: + return {} + + return self._bundle_to_dict(bundle) + + @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, + in_signature='s', out_signature='aa{sv}') + def FindActivity(self, name): + result = [] + key = name.lower() + + for bundle in bundleregistry.get_registry(): + name = bundle.get_name().lower() + service_name = bundle.get_service_name().lower() + if name.find(key) != -1 or service_name.find(key) != -1: + result.append(self._bundle_to_dict(bundle)) + + return result + + @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, + in_signature='s', out_signature='aa{sv}') + def GetActivitiesForType(self, mime_type): + result = [] + registry = bundleregistry.get_registry() + for bundle in registry.get_activities_for_type(mime_type): + result.append(self._bundle_to_dict(bundle)) + return result + + @dbus.service.signal(_ACTIVITY_REGISTRY_IFACE, signature='a{sv}') + def ActivityAdded(self, activity_info): + pass + + def _bundle_to_dict(self, bundle): + return {'name': bundle.get_name(), + 'icon': bundle.get_icon(), + 'service_name': bundle.get_service_name(), + 'path': bundle.get_path()} + + def _bundle_added_cb(self, bundle_registry, bundle): + self.ActivityAdded(self._bundle_to_dict(bundle)) + +_instance = None + +def get_instance(): + global _instance + if not _instance: + _instance = ActivityRegistry() + return _instance + diff --git a/shell/model/bundleregistry.py b/services/clipboard/bundleregistry.py similarity index 94% rename from shell/model/bundleregistry.py rename to services/clipboard/bundleregistry.py index bc8eec93..65a23483 100644 --- a/shell/model/bundleregistry.py +++ b/services/clipboard/bundleregistry.py @@ -106,6 +106,13 @@ class BundleRegistry(gobject.GObject): else: return False + def get_activities_for_type(self, mime_type): + result = [] + for bundle in self._bundles.values(): + if bundle.get_mime_types() and mime_type in bundle.get_mime_types(): + result.append(bundle) + return result + def get_registry(): return _bundle_registry diff --git a/services/clipboard/clipboardobject.py b/services/clipboard/clipboardobject.py index d7512747..bc51f472 100644 --- a/services/clipboard/clipboardobject.py +++ b/services/clipboard/clipboardobject.py @@ -19,9 +19,9 @@ import logging import urlparse from sugar.objects import mime -from sugar import activity import objecttypeservice +import bundleregistry class ClipboardObject: @@ -66,30 +66,15 @@ class ClipboardObject: return '' def get_activity(self): - logging.debug('get_activity') - mapping = {'text/html' : 'org.laptop.WebActivity', - 'image/jpeg' : 'org.laptop.WebActivity', - 'image/gif' : 'org.laptop.WebActivity', - 'image/png' : 'org.laptop.WebActivity', - 'text/plain' : 'org.laptop.AbiWordActivity', - 'text/rtf' : 'org.laptop.AbiWordActivity', - 'text/richtext' : 'org.laptop.AbiWordActivity', - 'application/pdf' : 'org.laptop.sugar.ReadActivity', - 'application/x-squeak-project' : 'org.vpri.EtoysActivity'} mime = self.get_mime_type() if not mime: return '' - """ - registry = activity.get_registry() + + registry = bundleregistry.get_registry() activities = registry.get_activities_for_type(self.get_mime_type()) # TODO: should we return several activities? if activities: - return activities[0] - else: - return '' - """ - if mapping.has_key(mime): - return mapping[mime] + return activities[0].get_service_name() else: return '' @@ -101,8 +86,6 @@ class ClipboardObject: def add_format(self, format): self._formats[format.get_type()] = format - # We want to get the activity early in order to prevent a DBus lockup. - activity = self.get_activity() def get_formats(self): return self._formats diff --git a/services/clipboard/clipboardservice.py b/services/clipboard/clipboardservice.py index 639f29c6..19958a7c 100644 --- a/services/clipboard/clipboardservice.py +++ b/services/clipboard/clipboardservice.py @@ -74,7 +74,7 @@ class ClipboardService(dbus.service.Object): def add_object_format(self, object_path, format_type, data, on_disk): logging.debug('ClipboardService.add_object_format') cb_object = self._objects[str(object_path)] - + if on_disk and cb_object.get_percent() == 100: new_uri = self._copy_file(data) cb_object.add_format(Format(format_type, new_uri, on_disk)) diff --git a/services/clipboard/org.laptop.ActivityRegistry.service.in b/services/clipboard/org.laptop.ActivityRegistry.service.in new file mode 100644 index 00000000..051ab006 --- /dev/null +++ b/services/clipboard/org.laptop.ActivityRegistry.service.in @@ -0,0 +1,4 @@ +[D-BUS Service] +Name = org.laptop.ActivityRegistry +Exec = @bindir@/sugar-clipboard + diff --git a/services/clipboard/sugar-clipboard b/services/clipboard/sugar-clipboard index 4cffa33b..9531e94b 100755 --- a/services/clipboard/sugar-clipboard +++ b/services/clipboard/sugar-clipboard @@ -34,6 +34,7 @@ sys.path.append(env.get_service_path('clipboard')) import clipboardservice import objecttypeservice +import activityregistryservice logging.info('Starting clipboard service.') @@ -42,9 +43,11 @@ dbus.glib.threads_init() clipboard_service = clipboardservice.get_instance() object_type_registry = objecttypeservice.get_instance() +activity_registry = activityregistryservice.get_instance() loop = gobject.MainLoop() try: loop.run() except KeyboardInterrupt: print 'Ctrl+C pressed, exiting...' + diff --git a/shell/model/Makefile.am b/shell/model/Makefile.am index 486ad091..0b7d14cd 100644 --- a/shell/model/Makefile.am +++ b/shell/model/Makefile.am @@ -4,7 +4,6 @@ sugardir = $(pkgdatadir)/shell/model sugar_PYTHON = \ __init__.py \ accesspointmodel.py \ - bundleregistry.py \ BuddyModel.py \ Friends.py \ Invites.py \ diff --git a/shell/shellservice.py b/shell/shellservice.py index 5728e449..d577a447 100644 --- a/shell/shellservice.py +++ b/shell/shellservice.py @@ -17,10 +17,7 @@ """D-bus service providing access to the shell's functionality""" import dbus -from model import bundleregistry - _DBUS_SERVICE = "org.laptop.Shell" -_DBUS_ACTIVITY_REGISTRY_IFACE = "org.laptop.Shell.ActivityRegistry" _DBUS_SHELL_IFACE = "org.laptop.Shell" _DBUS_OWNER_IFACE = "org.laptop.Shell.Owner" _DBUS_PATH = "/org/laptop/Shell" @@ -56,9 +53,6 @@ class ShellService(dbus.service.Object): self._home_model.connect('active-activity-changed', self._cur_activity_changed_cb) - bundle_registry = bundleregistry.get_registry() - bundle_registry.connect('bundle-added', self._bundle_added_cb) - bus = dbus.SessionBus() bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus) dbus.service.Object.__init__(self, bus_name, _DBUS_PATH) @@ -83,60 +77,6 @@ class ShellService(dbus.service.Object): def NotifyLaunchFailure(self, activity_id): self._shell.notify_launch_failure(activity_id) - @dbus.service.method(_DBUS_ACTIVITY_REGISTRY_IFACE, - in_signature="s", out_signature="b") - def AddBundle(self, bundle_path): - """Register the activity bundle with the global registry - - bundle_path -- path to the activity bundle's root directory, - that is, the directory with activity/activity.info as a - child of the directory. - - The bundleregistry.BundleRegistry is responsible for setting - up a set of d-bus service mappings for each available activity. - """ - registry = bundleregistry.get_registry() - return registry.add_bundle(bundle_path) - - @dbus.service.method(_DBUS_ACTIVITY_REGISTRY_IFACE, - in_signature="s", out_signature="a{sv}") - def GetActivity(self, service_name): - registry = bundleregistry.get_registry() - bundle = registry.get_bundle(service_name) - if not bundle: - return {} - - return self._bundle_to_dict(bundle) - - @dbus.service.method(_DBUS_ACTIVITY_REGISTRY_IFACE, - in_signature="s", out_signature="aa{sv}") - def FindActivity(self, name): - result = [] - key = name.lower() - - for bundle in bundleregistry.get_registry(): - name = bundle.get_name().lower() - service_name = bundle.get_service_name().lower() - if name.find(key) != -1 or service_name.find(key) != -1: - result.append(self._bundle_to_dict(bundle)) - - return result - - @dbus.service.method(_DBUS_ACTIVITY_REGISTRY_IFACE, - in_signature="s", out_signature="aa{sv}") - def GetActivitiesForType(self, mime_type): - result = [] - - for bundle in bundleregistry.get_registry(): - if bundle.get_mime_types() and mime_type in bundle.get_mime_types(): - result.append(self._bundle_to_dict(bundle)) - - return result - - @dbus.service.signal(_DBUS_ACTIVITY_REGISTRY_IFACE, signature="a{sv}") - def ActivityAdded(self, activity_info): - pass - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s") def ColorChanged(self, color): pass @@ -169,12 +109,3 @@ class ShellService(dbus.service.Object): if new_id: self.CurrentActivityChanged(new_id) - def _bundle_to_dict(self, bundle): - return {'name': bundle.get_name(), - 'icon': bundle.get_icon(), - 'service_name': bundle.get_service_name(), - 'path': bundle.get_path()} - - def _bundle_added_cb(self, bundle_registry, bundle): - self.ActivityAdded(self._bundle_to_dict(bundle)) - diff --git a/sugar/activity/registry.py b/sugar/activity/registry.py index b19abee6..430a2df2 100644 --- a/sugar/activity/registry.py +++ b/sugar/activity/registry.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2007 One Laptop Per Child # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -19,9 +20,9 @@ import logging import dbus -_SHELL_SERVICE = "org.laptop.Shell" -_SHELL_PATH = "/org/laptop/Shell" -_REGISTRY_IFACE = "org.laptop.Shell.ActivityRegistry" +_ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry' +_ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry' +_ACTIVITY_REGISTRY_PATH = '/org/laptop/ActivityRegistry' def _activity_info_from_dict(info_dict): if not info_dict: @@ -39,8 +40,9 @@ class ActivityInfo(object): class ActivityRegistry(object): def __init__(self): bus = dbus.SessionBus() - bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH) - self._registry = dbus.Interface(bus_object, _REGISTRY_IFACE) + bus_object = bus.get_object(_ACTIVITY_REGISTRY_SERVICE_NAME, + _ACTIVITY_REGISTRY_PATH) + self._registry = dbus.Interface(bus_object, _ACTIVITY_REGISTRY_IFACE) self._registry.connect_to_signal('ActivityAdded', self._activity_added_cb) # Two caches fo saving some travel across dbus. From cac6038665239aa037bcab475038bb5f33b34533 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Wed, 8 Aug 2007 18:33:10 +0200 Subject: [PATCH 18/34] Rename the clipboard service as shell service. --- configure.ac | 2 +- services/Makefile.am | 2 +- services/{clipboard => shell}/Makefile.am | 6 ++++-- services/{clipboard => shell}/__init__.py | 0 services/{clipboard => shell}/activityregistryservice.py | 0 services/{clipboard => shell}/bundleregistry.py | 0 services/{clipboard => shell}/clipboardobject.py | 0 services/{clipboard => shell}/clipboardservice.py | 0 services/{clipboard => shell}/objecttypeservice.py | 0 .../org.laptop.ActivityRegistry.service.in | 2 +- .../{clipboard => shell}/org.laptop.Clipboard.service.in | 2 +- .../org.laptop.ObjectTypeRegistry.service.in | 2 +- .../sugar-clipboard => shell/sugar-shell-service} | 6 +++--- 13 files changed, 12 insertions(+), 10 deletions(-) rename services/{clipboard => shell}/Makefile.am (91%) rename services/{clipboard => shell}/__init__.py (100%) rename services/{clipboard => shell}/activityregistryservice.py (100%) rename services/{clipboard => shell}/bundleregistry.py (100%) rename services/{clipboard => shell}/clipboardobject.py (100%) rename services/{clipboard => shell}/clipboardservice.py (100%) rename services/{clipboard => shell}/objecttypeservice.py (100%) rename services/{clipboard => shell}/org.laptop.ActivityRegistry.service.in (59%) rename services/{clipboard => shell}/org.laptop.Clipboard.service.in (55%) rename services/{clipboard => shell}/org.laptop.ObjectTypeRegistry.service.in (60%) rename services/{clipboard/sugar-clipboard => shell/sugar-shell-service} (91%) diff --git a/configure.ac b/configure.ac index f6626693..e0acaf19 100644 --- a/configure.ac +++ b/configure.ac @@ -45,7 +45,7 @@ data/Makefile lib/Makefile lib/ui/Makefile services/Makefile -services/clipboard/Makefile +services/shell/Makefile shell/Makefile shell/intro/Makefile shell/hardware/Makefile diff --git a/services/Makefile.am b/services/Makefile.am index 7d8e3516..e230030d 100644 --- a/services/Makefile.am +++ b/services/Makefile.am @@ -1 +1 @@ -SUBDIRS = clipboard console +SUBDIRS = shell console diff --git a/services/clipboard/Makefile.am b/services/shell/Makefile.am similarity index 91% rename from services/clipboard/Makefile.am rename to services/shell/Makefile.am index 6d2cd9c9..b34b974c 100644 --- a/services/clipboard/Makefile.am +++ b/services/shell/Makefile.am @@ -19,17 +19,19 @@ org.laptop.Clipboard.service: org.laptop.Clipboard.service.in Makefile org.laptop.ObjectTypeRegistry.service: org.laptop.ObjectTypeRegistry.service.in Makefile @sed -e "s|\@bindir\@|$(bindir)|" $< > $@ -sugardir = $(pkgdatadir)/services/clipboard +sugardir = $(pkgdatadir)/services/shell sugar_PYTHON = \ __init__.py \ activityregistryservice.py \ + bundleregistry.py \ clipboardobject.py \ clipboardservice.py \ objecttypeservice.py -bin_SCRIPTS = sugar-clipboard +bin_SCRIPTS = sugar-shell-service DISTCLEANFILES = $(service_DATA) EXTRA_DIST = $(service_in_files) $(bin_SCRIPTS) + diff --git a/services/clipboard/__init__.py b/services/shell/__init__.py similarity index 100% rename from services/clipboard/__init__.py rename to services/shell/__init__.py diff --git a/services/clipboard/activityregistryservice.py b/services/shell/activityregistryservice.py similarity index 100% rename from services/clipboard/activityregistryservice.py rename to services/shell/activityregistryservice.py diff --git a/services/clipboard/bundleregistry.py b/services/shell/bundleregistry.py similarity index 100% rename from services/clipboard/bundleregistry.py rename to services/shell/bundleregistry.py diff --git a/services/clipboard/clipboardobject.py b/services/shell/clipboardobject.py similarity index 100% rename from services/clipboard/clipboardobject.py rename to services/shell/clipboardobject.py diff --git a/services/clipboard/clipboardservice.py b/services/shell/clipboardservice.py similarity index 100% rename from services/clipboard/clipboardservice.py rename to services/shell/clipboardservice.py diff --git a/services/clipboard/objecttypeservice.py b/services/shell/objecttypeservice.py similarity index 100% rename from services/clipboard/objecttypeservice.py rename to services/shell/objecttypeservice.py diff --git a/services/clipboard/org.laptop.ActivityRegistry.service.in b/services/shell/org.laptop.ActivityRegistry.service.in similarity index 59% rename from services/clipboard/org.laptop.ActivityRegistry.service.in rename to services/shell/org.laptop.ActivityRegistry.service.in index 051ab006..ab6647c9 100644 --- a/services/clipboard/org.laptop.ActivityRegistry.service.in +++ b/services/shell/org.laptop.ActivityRegistry.service.in @@ -1,4 +1,4 @@ [D-BUS Service] Name = org.laptop.ActivityRegistry -Exec = @bindir@/sugar-clipboard +Exec = @bindir@/sugar-shell-service diff --git a/services/clipboard/org.laptop.Clipboard.service.in b/services/shell/org.laptop.Clipboard.service.in similarity index 55% rename from services/clipboard/org.laptop.Clipboard.service.in rename to services/shell/org.laptop.Clipboard.service.in index b38bf2b1..7ce3f6ef 100644 --- a/services/clipboard/org.laptop.Clipboard.service.in +++ b/services/shell/org.laptop.Clipboard.service.in @@ -1,4 +1,4 @@ [D-BUS Service] Name = org.laptop.Clipboard -Exec = @bindir@/sugar-clipboard +Exec = @bindir@/sugar-shell-service diff --git a/services/clipboard/org.laptop.ObjectTypeRegistry.service.in b/services/shell/org.laptop.ObjectTypeRegistry.service.in similarity index 60% rename from services/clipboard/org.laptop.ObjectTypeRegistry.service.in rename to services/shell/org.laptop.ObjectTypeRegistry.service.in index 66477eb6..563a600d 100644 --- a/services/clipboard/org.laptop.ObjectTypeRegistry.service.in +++ b/services/shell/org.laptop.ObjectTypeRegistry.service.in @@ -1,4 +1,4 @@ [D-BUS Service] Name = org.laptop.ObjectTypeRegistry -Exec = @bindir@/sugar-clipboard +Exec = @bindir@/sugar-shell-service diff --git a/services/clipboard/sugar-clipboard b/services/shell/sugar-shell-service similarity index 91% rename from services/clipboard/sugar-clipboard rename to services/shell/sugar-shell-service index 9531e94b..370c2ea4 100755 --- a/services/clipboard/sugar-clipboard +++ b/services/shell/sugar-shell-service @@ -23,20 +23,20 @@ import os import logging from sugar import logger -logger.start('clipboard') +logger.start('shellservice') import gobject import dbus.glib from sugar import env -sys.path.append(env.get_service_path('clipboard')) +sys.path.append(env.get_service_path('shell')) import clipboardservice import objecttypeservice import activityregistryservice -logging.info('Starting clipboard service.') +logging.info('Starting shell service.') gobject.threads_init() dbus.glib.threads_init() From 77094c961ac30c71788eaa4e43458854da4cdbbc Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Wed, 8 Aug 2007 18:39:39 +0200 Subject: [PATCH 19/34] Adapt to new palette API. --- shell/view/clipboardmenu.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/shell/view/clipboardmenu.py b/shell/view/clipboardmenu.py index 35802dcb..1d9f85c0 100644 --- a/shell/view/clipboardmenu.py +++ b/shell/view/clipboardmenu.py @@ -64,11 +64,13 @@ class ClipboardMenu(Palette): self._remove_item = MenuItem(_('Remove'), 'stock-remove') self._remove_item.connect('activate', self._remove_item_activate_cb) - self.append_menu_item(self._remove_item) + self.menu.append(self._remove_item) + self._remove_item.show() self._open_item = MenuItem(_('Open'), 'stock-keep') self._open_item.connect('activate', self._open_item_activate_cb) - self.append_menu_item(self._open_item) + self.menu.append(self._open_item) + self._open_item.show() #self._stop_item = MenuItem(_('Stop download'), 'stock-close') # TODO: Implement stopping downloads @@ -77,7 +79,8 @@ class ClipboardMenu(Palette): self._journal_item = MenuItem(_('Add to journal'), 'document-save') self._journal_item.connect('activate', self._journal_item_activate_cb) - self.append_menu_item(self._journal_item) + self.menu.append(self._journal_item) + self._journal_item.show() self._update_items_visibility(installable) From c57e842c0430ead0ff8abc33d75e24b57b3b1fc5 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Wed, 8 Aug 2007 13:24:47 -0400 Subject: [PATCH 20/34] DevConsole: logviewer now reads Xorg logfile --- .../console/interface/logviewer/logviewer.py | 105 +++++++++++------- 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/services/console/interface/logviewer/logviewer.py b/services/console/interface/logviewer/logviewer.py index 3d90f09e..8aaf347f 100644 --- a/services/console/interface/logviewer/logviewer.py +++ b/services/console/interface/logviewer/logviewer.py @@ -28,29 +28,29 @@ import pango from sugar import env class MultiLogView(gtk.VBox): - def __init__(self, path): + def __init__(self, path, extra_files): self._active_log = None - self._iters = [] - + self._extra_files = extra_files + # Creating Main treeview with Actitivities list - tv_menu = gtk.TreeView() - tv_menu.connect('cursor-changed', self._load_log) - tv_menu.set_rules_hint(True) + self._tv_menu = gtk.TreeView() + self._tv_menu.connect('cursor-changed', self._load_log) + self._tv_menu.set_rules_hint(True) # Set width box_width = gtk.gdk.screen_width() * 80 / 100 - tv_menu.set_size_request(box_width*25/100, 0) + self._tv_menu.set_size_request(box_width*25/100, 0) - self.store_menu = gtk.TreeStore(str) - tv_menu.set_model(self.store_menu) + self._store_menu = gtk.TreeStore(str) + self._tv_menu.set_model(self._store_menu) - self._add_column(tv_menu, 'Sugar logs', 0) + self._add_column(self._tv_menu, 'Sugar logs', 0) self._logs_path = os.path.join(env.get_profile_path(), 'logs') self._activity = {} # Activities menu self.hbox = gtk.HBox(False, 3) - self.hbox.pack_start(tv_menu, True, True, 0) + self.hbox.pack_start(self._tv_menu, True, True, 0) # Activity log, set width self._view = LogView() @@ -59,52 +59,62 @@ class MultiLogView(gtk.VBox): self.hbox.pack_start(self._view, True, True, 0) self.hbox.show_all() - gobject.timeout_add(1000, self._update, tv_menu) + gobject.timeout_add(1000, self._update) # Load the log information in View (textview) def _load_log(self, treeview): treeselection = treeview.get_selection() - treestore, iter = treeselection.get_selected() - + # Get current selection - act_log = self.store_menu.get_value(iter, 0) - + act_log = self._store_menu.get_value(iter, 0) + # Set buffer and scroll down self._view.textview.set_buffer(self._activity[act_log]) self._view.textview.scroll_to_mark(self._activity[act_log].get_insert(), 0); self._active_log = act_log - - def _update(self, tv_menu): + + def _update(self): # Searching log files for logfile in os.listdir(self._logs_path): full_log_path = os.path.join(self._logs_path, logfile) - - if os.path.isdir(full_log_path): - continue + self._add_log_file(full_log_path) - if not self._activity.has_key(logfile): - self._add_activity(logfile) - model = LogBuffer(full_log_path) - self._activity[logfile] = model - - self._activity[logfile].update() - written = self._activity[logfile]._written - - # Load the first iter - if self._active_log == None: - self._active_log = logfile - iter = tv_menu.get_model().get_iter_root() - tv_menu.get_selection().select_iter(iter) - self._load_log(tv_menu) - - if written > 0 and self._active_log == logfile: - self._view.textview.scroll_to_mark(self._activity[logfile].get_insert(), 0); + for ext in self._extra_files: + self._add_log_file(ext) return True - + + def _get_filename_from_path(self, path): + return path.split('/')[-1] + + def _add_log_file(self, path): + if os.path.isdir(path): + return False + + logfile = self._get_filename_from_path(path) + + if not self._activity.has_key(logfile): + self._add_activity(logfile) + model = LogBuffer(path) + self._activity[logfile] = model + + self._activity[logfile].update() + written = self._activity[logfile]._written + + # Load the first iter + if self._active_log == None: + self._active_log = logfile + iter = self._tv_menu.get_model().get_iter_root() + self._tv_menu.get_selection().select_iter(iter) + self._load_log(self._tv_menu) + + if written > 0 and self._active_log == logfile: + self._view.textview.scroll_to_mark(self._activity[logfile].get_insert(), 0) + + def _add_activity(self, name): - self._insert_row(self.store_menu, None, name) + self._insert_row(self._store_menu, None, name) # Add a new column to the main treeview, (code from Memphis) def _add_column(self, treeview, column_name, index): @@ -171,9 +181,20 @@ class LogView(gtk.ScrolledWindow): self.textview.show() class Interface: - def __init__(self): path = None - viewer = MultiLogView(path) + xserver_logfile = self._get_xserver_logfile_path() + + # Aditional files to watch in logviewer + ext_files = [] + ext_files.append(xserver_logfile) + + viewer = MultiLogView(path, ext_files) self.widget = viewer.hbox + # Get the Xorg log file path, we have two ways to get the path: do a system + # call and exec a 'xset -q' or just read directly the file that we know + # always be the right one for a XO machine... + def _get_xserver_logfile_path(self): + path = "/var/log/Xorg.0.log" + return path From 985fe6ef358ce28d7d04f199d9a342f4a4fe0a79 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 8 Aug 2007 18:19:05 -0400 Subject: [PATCH 21/34] Make activity icons in frame outline-only. #2668 --- shell/view/frame/ActivitiesBox.py | 5 ++++- sugar/graphics/canvasicon.py | 8 ++++---- sugar/graphics/iconbutton.py | 2 +- sugar/graphics/style.py | 7 +++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/shell/view/frame/ActivitiesBox.py b/shell/view/frame/ActivitiesBox.py index a46e8e9d..5a9e0de7 100644 --- a/shell/view/frame/ActivitiesBox.py +++ b/shell/view/frame/ActivitiesBox.py @@ -20,6 +20,7 @@ import logging from sugar.graphics.palette import Palette from sugar.graphics.xocolor import XoColor from sugar.graphics.iconbutton import IconButton +from sugar.graphics import style from sugar import profile from model import bundleregistry @@ -27,7 +28,9 @@ from frameinvoker import FrameCanvasInvoker class ActivityButton(IconButton): def __init__(self, activity): - IconButton.__init__(self, icon_name=activity.get_icon()) + IconButton.__init__(self, icon_name=activity.get_icon(), + stroke_color=style.COLOR_WHITE, + fill_color=style.COLOR_TRANSPARENT) palette = Palette(activity.get_name()) palette.props.invoker = FrameCanvasInvoker(self) diff --git a/sugar/graphics/canvasicon.py b/sugar/graphics/canvasicon.py index e879f05f..39f13586 100644 --- a/sugar/graphics/canvasicon.py +++ b/sugar/graphics/canvasicon.py @@ -231,13 +231,13 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): stroke_color = None if self._active: if self._fill_color: - fill_color = self._fill_color.get_html() + fill_color = self._fill_color.get_svg() if self._stroke_color: - stroke_color = self._stroke_color.get_html() + stroke_color = self._stroke_color.get_svg() else: - stroke_color = color.ICON_STROKE_INACTIVE.get_html() + stroke_color = color.ICON_STROKE_INACTIVE.get_svg() if self._fill_color: - fill_color = self._fill_color.get_html() + fill_color = self._fill_color.get_svg() return [fill_color, stroke_color] def _get_handle(self, name, handle): diff --git a/sugar/graphics/iconbutton.py b/sugar/graphics/iconbutton.py index 85ea4e43..ba9fad8d 100644 --- a/sugar/graphics/iconbutton.py +++ b/sugar/graphics/iconbutton.py @@ -53,7 +53,7 @@ class IconButton(CanvasIcon, hippo.CanvasItem): if self.props.active: self.props.background_color = 0x000000FF else: - self.props.background_color = 0x404040FF + self.props.background_color = 0x00000000 def _icon_clicked_cb(self, button): if self._palette: diff --git a/sugar/graphics/style.py b/sugar/graphics/style.py index 55b4a4b8..a5186da9 100644 --- a/sugar/graphics/style.py +++ b/sugar/graphics/style.py @@ -81,6 +81,12 @@ class Color(object): return (r, g, b) + def get_svg(self): + if self._a == 0.0: + return 'none' + else: + return self.get_html() + _XO_DPI = 200.0 _FOCUS_LINE_WIDTH = 2 @@ -113,6 +119,7 @@ TOOLBOX_TAB_LABEL_WIDTH = zoom(150 - 15 * 2) COLOR_BLACK = Color('#000000') COLOR_WHITE = Color('#FFFFFF') +COLOR_TRANSPARENT = Color('#FFFFFF', alpha=0.0) COLOR_PANEL_GREY = Color('#C0C0C0') COLOR_SELECTION_GREY = Color('#A6A6A6') COLOR_INACTIVE_FILL = Color('#9D9FA1') From d2aba901c3f85e66ba9c5b6b5d506f472552b48b Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Wed, 8 Aug 2007 18:24:11 -0400 Subject: [PATCH 22/34] Notice when (non-sugar) activity factories fail to launch. #1975 (still need a better shell UI for when this happens) --- sugar/activity/activityfactory.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sugar/activity/activityfactory.py b/sugar/activity/activityfactory.py index 404e5f4c..b1d55eb2 100644 --- a/sugar/activity/activityfactory.py +++ b/sugar/activity/activityfactory.py @@ -114,7 +114,7 @@ class ActivityCreationHandler(gobject.GObject): self._factory.create(self._activity_handle.get_dict(), timeout=120 * 1000, - reply_handler=self._no_reply_handler, + reply_handler=self._create_reply_handler, error_handler=self._create_error_handler) def get_activity_id(self): @@ -137,7 +137,10 @@ class ActivityCreationHandler(gobject.GObject): def _activate_error_handler(self, err): logging.debug("Activity activation request failed %s" % err) - def _create_reply_handler(self, xid): + def _create_reply_handler(self, xid=None): + if xid is None: + self._create_error_handler('D-Bus error') + return logging.debug("Activity created %s (%s)." % (self._activity_handle.activity_id, self._service_name)) From f00f3e2f8da0549a54f10ef20419ff6e11824685 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Thu, 9 Aug 2007 15:26:52 +0200 Subject: [PATCH 23/34] Correctly include .mo files in bundles. --- sugar/activity/bundle.py | 7 +++++-- sugar/activity/bundlebuilder.py | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sugar/activity/bundle.py b/sugar/activity/bundle.py index a9c246d8..bc24092a 100644 --- a/sugar/activity/bundle.py +++ b/sugar/activity/bundle.py @@ -41,6 +41,7 @@ class NotInstalledException(Exception): pass class InvalidPathException(Exception): pass class ZipExtractException(Exception): pass class RegistrationException(Exception): pass +class MalformedBundleException(Exception): pass class Bundle: """Metadata description of a given application/activity @@ -265,10 +266,12 @@ class Bundle: if not bundle_root_dir: bundle_root_dir = file_name.split('/')[0] if not bundle_root_dir.endswith('.activity'): - raise 'Incorrect bundle.' + raise MalformedBundleException( + 'The activity directory name must end with .activity') else: if not file_name.startswith(bundle_root_dir): - raise 'Incorrect bundle.' + raise MalformedBundleException( + 'All files in the bundle must be inside the activity directory') return bundle_root_dir diff --git a/sugar/activity/bundlebuilder.py b/sugar/activity/bundlebuilder.py index b255cfb8..3bbe4546 100644 --- a/sugar/activity/bundlebuilder.py +++ b/sugar/activity/bundlebuilder.py @@ -162,8 +162,7 @@ def _get_mo_list(manifest): for lang in _get_po_list(manifest).keys(): filename = _get_service_name() + '.mo' - mo_list.append(os.path.join(_get_source_path(), 'locale', - lang, 'LC_MESSAGES', filename)) + mo_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename)) return mo_list From cff8ffc32895f3e304f843389c7d4a984502727e Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Thu, 9 Aug 2007 18:10:16 +0200 Subject: [PATCH 24/34] Complete move to external bundle registry. --- services/shell/activityregistryservice.py | 12 ++- shell/model/MeshModel.py | 26 +++--- shell/model/homeactivity.py | 18 ++--- shell/model/homemodel.py | 97 ++++++++++++----------- shell/view/Shell.py | 12 +-- shell/view/clipboardmenu.py | 26 +----- shell/view/frame/ActivitiesBox.py | 40 +++++----- shell/view/home/FriendView.py | 18 ++--- sugar/activity/bundle.py | 7 +- sugar/activity/registry.py | 25 +++++- 10 files changed, 141 insertions(+), 140 deletions(-) diff --git a/services/shell/activityregistryservice.py b/services/shell/activityregistryservice.py index 60293d47..44c99694 100644 --- a/services/shell/activityregistryservice.py +++ b/services/shell/activityregistryservice.py @@ -48,6 +48,15 @@ class ActivityRegistry(dbus.service.Object): registry = bundleregistry.get_registry() return registry.add_bundle(bundle_path) + @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, + in_signature='', out_signature='aa{sv}') + def GetActivities(self): + result = [] + registry = bundleregistry.get_registry() + for bundle in registry: + result.append(self._bundle_to_dict(bundle)) + return result + @dbus.service.method(_ACTIVITY_REGISTRY_IFACE, in_signature='s', out_signature='a{sv}') def GetActivity(self, service_name): @@ -89,7 +98,8 @@ class ActivityRegistry(dbus.service.Object): return {'name': bundle.get_name(), 'icon': bundle.get_icon(), 'service_name': bundle.get_service_name(), - 'path': bundle.get_path()} + 'path': bundle.get_path(), + 'show_launcher': bundle.get_show_launcher()} def _bundle_added_cb(self, bundle_registry, bundle): self.ActivityAdded(self._bundle_to_dict(bundle)) diff --git a/shell/model/MeshModel.py b/shell/model/MeshModel.py index 44974e21..82980c34 100644 --- a/shell/model/MeshModel.py +++ b/shell/model/MeshModel.py @@ -18,29 +18,29 @@ import gobject from sugar.graphics.xocolor import XoColor from sugar.presence import presenceservice +from sugar import activity -from model import bundleregistry from model.BuddyModel import BuddyModel from model.accesspointmodel import AccessPointModel from hardware import hardwaremanager from hardware import nmclient class ActivityModel: - def __init__(self, activity, bundle): + def __init__(self, activity, activity_info): self._activity = activity - self._bundle = bundle + self._activity_info = activity_info def get_id(self): return self._activity.props.id def get_icon_name(self): - return self._bundle.get_icon() + return self._activity_info.icon def get_color(self): return XoColor(self._activity.props.color) def get_service_name(self): - return self._bundle.get_service_name() + return self._activity_info.service_name def get_title(self): return self._activity.props.name @@ -75,7 +75,6 @@ class MeshModel(gobject.GObject): self._buddies = {} self._access_points = {} self._mesh = None - self._bundle_registry = bundleregistry.get_registry() self._pservice = presenceservice.get_instance() self._pservice.connect("activity-appeared", @@ -196,13 +195,14 @@ class MeshModel(gobject.GObject): def _activity_appeared_cb(self, pservice, activity): self._check_activity(activity) - def _check_activity(self, activity): - bundle = self._bundle_registry.get_bundle(activity.props.type) - if not bundle: + def _check_activity(self, presence_activity): + registry = activity.get_registry() + activity_info = registry.get_activity(presence_activity.props.type) + if not activity_info: return - if self.has_activity(activity.props.id): + if self.has_activity(presence_activity.props.id): return - self.add_activity(bundle, activity) + self.add_activity(activity_info, presence_activity) def has_activity(self, activity_id): return self._activities.has_key(activity_id) @@ -213,8 +213,8 @@ class MeshModel(gobject.GObject): else: return None - def add_activity(self, bundle, activity): - model = ActivityModel(activity, bundle) + def add_activity(self, activity_info, activity): + model = ActivityModel(activity, activity_info) self._activities[model.get_id()] = model self.emit('activity-added', model) diff --git a/shell/model/homeactivity.py b/shell/model/homeactivity.py index c45e5c75..e95fe9ad 100644 --- a/shell/model/homeactivity.py +++ b/shell/model/homeactivity.py @@ -44,10 +44,10 @@ class HomeActivity(gobject.GObject): gobject.PARAM_READWRITE), } - def __init__(self, bundle, activity_id): + def __init__(self, activity_info, activity_id): """Initialise the HomeActivity - bundle -- sugar.activity.bundle.Bundle instance, + activity_info -- sugar.activity.registry.ActivityInfo instance, provides the information required to actually create the new instance. This is, in effect, the "type" of activity being created. @@ -61,7 +61,7 @@ class HomeActivity(gobject.GObject): self._pid = None self._service = None self._activity_id = activity_id - self._bundle = bundle + self._activity_info = activity_info self._launch_time = time.time() self._launching = False @@ -99,9 +99,9 @@ class HomeActivity(gobject.GObject): return self._window.get_name() def get_icon_name(self): - """Retrieve the bundle's icon (file) name""" - if self._bundle: - return self._bundle.get_icon() + """Retrieve the activity's icon (file) name""" + if self._activity_info: + return self._activity_info.icon else: return 'theme:stock-missing' @@ -156,9 +156,9 @@ class HomeActivity(gobject.GObject): return self._window def get_type(self): - """Retrieve bundle's "service_name" for future reference""" - if self._bundle: - return self._bundle.get_service_name() + """Retrieve activity_info's "service_name" for future reference""" + if self._activity_info: + return self._activity_info.service_name else: return None diff --git a/shell/model/homemodel.py b/shell/model/homemodel.py index c5e761cd..dac434a3 100644 --- a/shell/model/homemodel.py +++ b/shell/model/homemodel.py @@ -21,9 +21,9 @@ import wnck import dbus from sugar import wm +from sugar import activity from model.homeactivity import HomeActivity -from model import bundleregistry class HomeModel(gobject.GObject): """Model of the "Home" view (activity management) @@ -61,7 +61,6 @@ class HomeModel(gobject.GObject): gobject.GObject.__init__(self) self._activities = [] - self._bundle_registry = bundleregistry.get_registry() self._active_activity = None self._pending_activity = None @@ -83,11 +82,11 @@ class HomeModel(gobject.GObject): """ return self._pending_activity - def _set_pending_activity(self, activity): - if self._pending_activity == activity: + def _set_pending_activity(self, home_activity): + if self._pending_activity == home_activity: return - self._pending_activity = activity + self._pending_activity = home_activity self.emit('pending-activity-changed', self._pending_activity) def get_active_activity(self): @@ -101,8 +100,8 @@ class HomeModel(gobject.GObject): """ return self._active_activity - def _set_active_activity(self, activity): - if self._active_activity == activity: + def _set_active_activity(self, home_activity): + if self._active_activity == home_activity: return if self._active_activity: @@ -111,14 +110,14 @@ class HomeModel(gobject.GObject): service.set_active(False, reply_handler=self._set_active_success, error_handler=self._set_active_error) - if activity: - service = activity.get_service() + if home_activity: + service = home_activity.get_service() if service: service.set_active(True, reply_handler=self._set_active_success, error_handler=self._set_active_error) - self._active_activity = activity + self._active_activity = home_activity self.emit('active-activity-changed', self._active_activity) def __iter__(self): @@ -135,45 +134,46 @@ class HomeModel(gobject.GObject): def _window_opened_cb(self, screen, window): if window.get_window_type() == wnck.WINDOW_NORMAL: - activity = None + home_activity = None activity_id = wm.get_activity_id(window) - bundle_id = wm.get_bundle_id(window) - if bundle_id: - bundle = self._bundle_registry.get_bundle(bundle_id) + service_name = wm.get_bundle_id(window) + if service_name: + registry = activity.get_registry() + activity_info = registry.get_activity(service_name) else: - bundle = None + activity_info = None if activity_id: - activity = self._get_activity_by_id(activity_id) + home_activity = self._get_activity_by_id(activity_id) - if not activity: - activity = HomeActivity(bundle, activity_id) - self._add_activity(activity) + if not home_activity: + home_activity = HomeActivity(activity_info, activity_id) + self._add_activity(home_activity) - activity.set_window(window) + home_activity.set_window(window) - activity.props.launching = False - self.emit('activity-started', activity) + home_activity.props.launching = False + self.emit('activity-started', home_activity) if self._pending_activity is None: - self._set_pending_activity(activity) + self._set_pending_activity(home_activity) def _window_closed_cb(self, screen, window): if window.get_window_type() == wnck.WINDOW_NORMAL: self._remove_activity_by_xid(window.get_xid()) def _get_activity_by_xid(self, xid): - for activity in self._activities: - if activity.get_xid() == xid: - return activity + for home_activity in self._activities: + if home_activity.get_xid() == xid: + return home_activity return None def _get_activity_by_id(self, activity_id): - for activity in self._activities: - if activity.get_activity_id() == activity_id: - return activity + for home_activity in self._activities: + if home_activity.get_activity_id() == activity_id: + return home_activity return None def _set_active_success(self): @@ -194,12 +194,12 @@ class HomeModel(gobject.GObject): self._set_pending_activity(act) self._set_active_activity(act) - def _add_activity(self, activity): - self._activities.append(activity) - self.emit('activity-added', activity) + def _add_activity(self, home_activity): + self._activities.append(home_activity) + self.emit('activity-added', home_activity) - def _remove_activity(self, activity): - if activity == self._active_activity: + def _remove_activity(self, home_activity): + if home_activity == self._active_activity: self._set_active_activity(None) # Figure out the new _pending_activity. windows = wnck.screen_get_default().get_windows_stacked() @@ -213,28 +213,29 @@ class HomeModel(gobject.GObject): logging.error('No activities are running') self._set_pending_activity(None) - self.emit('activity-removed', activity) - self._activities.remove(activity) + self.emit('activity-removed', home_activity) + self._activities.remove(home_activity) def _remove_activity_by_xid(self, xid): - activity = self._get_activity_by_xid(xid) - if activity: - self._remove_activity(activity) + home_activity = self._get_activity_by_xid(xid) + if home_activity: + self._remove_activity(home_activity) else: logging.error('Model for window %d does not exist.' % xid) def notify_activity_launch(self, activity_id, service_name): - bundle = self._bundle_registry.get_bundle(service_name) - if not bundle: + registry = activity.get_registry() + activity_info = registry.get_activity(service_name) + if not activity_info: raise ValueError("Activity service name '%s' was not found in the bundle registry." % service_name) - activity = HomeActivity(bundle, activity_id) - activity.props.launching = True - self._add_activity(activity) + home_activity = HomeActivity(activity_info, activity_id) + home_activity.props.launching = True + self._add_activity(home_activity) def notify_activity_launch_failed(self, activity_id): - activity = self._get_activity_by_id(activity_id) - if activity: - logging.debug("Activity %s (%s) launch failed" % (activity_id, activity.get_type())) - self._remove_activity(activity) + home_activity = self._get_activity_by_id(activity_id) + if home_activity: + logging.debug("Activity %s (%s) launch failed" % (activity_id, home_activity.get_type())) + self._remove_activity(home_activity) else: logging.error('Model for activity id %s does not exist.' % activity_id) diff --git a/shell/view/Shell.py b/shell/view/Shell.py index af9036a4..044cbde4 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -26,6 +26,7 @@ import gtk import wnck from sugar.activity.activityhandle import ActivityHandle +from sugar import activity from sugar.activity import activityfactory from sugar.datastore import datastore from sugar import profile @@ -34,7 +35,6 @@ from view.ActivityHost import ActivityHost from view.frame.frame import Frame from view.keyhandler import KeyHandler from view.home.HomeWindow import HomeWindow -from model import bundleregistry from model.shellmodel import ShellModel from hardware import hardwaremanager @@ -116,16 +116,16 @@ class Shell(gobject.GObject): return self._frame def join_activity(self, bundle_id, activity_id): - activity = self.get_activity(activity_id) - if activity: - activity.present() + activity_host = self.get_activity(activity_id) + if activity_host: + activity_host.present() return # Get the service name for this activity, if # we have a bundle on the system capable of handling # this activity type - breg = bundleregistry.get_registry() - bundle = breg.get_bundle(bundle_id) + registry = activity.get_registry() + bundle = registry.get_activity(bundle_id) if not bundle: logging.error("Couldn't find activity for type %s" % bundle_id) return diff --git a/shell/view/clipboardmenu.py b/shell/view/clipboardmenu.py index 1d9f85c0..28ea0bb6 100644 --- a/shell/view/clipboardmenu.py +++ b/shell/view/clipboardmenu.py @@ -123,32 +123,8 @@ class ClipboardMenu(Palette): def _open_item_activate_cb(self, menu_item): if self._percent < 100: return - jobject = self._copy_to_journal() - # TODO: we cannot simply call resume() right now because we would lock - # the shell as we are sharing the same loop as the shell service. - #jobject.resume() - - # TODO: take this out when we fix the mess that is the shell/shellservice. - from model import bundleregistry - from sugar.activity.bundle import Bundle - from sugar.activity import activityfactory - if jobject.is_bundle(): - bundle = Bundle(jobject.file_path) - if not bundle.is_installed(): - bundle.install() - - activityfactory.create(bundle.get_service_name()) - else: - service_name = None - mime_type = jobject.metadata['mime_type'] - for bundle in bundleregistry.get_registry(): - if bundle.get_mime_types() and mime_type in bundle.get_mime_types(): - service_name = bundle.get_service_name() - break - if service_name: - activityfactory.create_with_object_id(service_name, - jobject.object_id) + jobject.resume() def _remove_item_activate_cb(self, menu_item): cb_service = clipboardservice.get_instance() diff --git a/shell/view/frame/ActivitiesBox.py b/shell/view/frame/ActivitiesBox.py index 5a9e0de7..909a5f2c 100644 --- a/shell/view/frame/ActivitiesBox.py +++ b/shell/view/frame/ActivitiesBox.py @@ -22,31 +22,31 @@ from sugar.graphics.xocolor import XoColor from sugar.graphics.iconbutton import IconButton from sugar.graphics import style from sugar import profile +from sugar import activity -from model import bundleregistry from frameinvoker import FrameCanvasInvoker class ActivityButton(IconButton): - def __init__(self, activity): - IconButton.__init__(self, icon_name=activity.get_icon(), + def __init__(self, activity_info): + IconButton.__init__(self, icon_name=activity_info.icon, stroke_color=style.COLOR_WHITE, fill_color=style.COLOR_TRANSPARENT) - palette = Palette(activity.get_name()) + palette = Palette(activity_info.name) palette.props.invoker = FrameCanvasInvoker(self) palette.set_group_id('frame') self.set_palette(palette) - self._activity = activity + self._activity_info = activity_info def get_bundle_id(self): - return self._activity.get_service_name() + return self._activity_info.service_name class InviteButton(IconButton): - def __init__(self, activity, invite): - IconButton.__init__(self, icon_name=activity.get_icon()) + def __init__(self, activity_model, invite): + IconButton.__init__(self, icon_name=activity_model.get_color()) - self.props.xo_color = activity.get_color() + self.props.xo_color = activity_model.get_color() self._invite = invite def get_activity_id(self): @@ -67,12 +67,12 @@ class ActivitiesBox(hippo.CanvasBox): self._invite_to_item = {} self._invites = self._shell_model.get_invites() - bundle_registry = bundleregistry.get_registry() - for bundle in bundle_registry: - if bundle.get_show_launcher(): - self.add_activity(bundle) + registry = activity.get_registry() + for activity_info in registry.get_activities(): + if activity_info.show_launcher: + self.add_activity(activity_info) - bundle_registry.connect('bundle-added', self._bundle_added_cb) + registry.connect('activity-added', self._activity_added_cb) for invite in self._invites: self.add_invite(invite) @@ -93,19 +93,19 @@ class ActivitiesBox(hippo.CanvasBox): def _invite_removed_cb(self, invites, invite): self.remove_invite(invite) - def _bundle_added_cb(self, bundle_registry, bundle): - self.add_activity(bundle) + def _activity_added_cb(self, activity_registry, activity_info): + self.add_activity(activity_info) - def add_activity(self, activity): - item = ActivityButton(activity) + def add_activity(self, activity_info): + item = ActivityButton(activity_info) item.connect('activated', self._activity_clicked_cb) self.append(item, 0) def add_invite(self, invite): mesh = self._shell_model.get_mesh() - activity = mesh.get_activity(invite.get_activity_id()) + activity_model = mesh.get_activity(invite.get_activity_id()) if activity: - item = InviteButton(activity, invite) + item = InviteButton(activity_model, invite) item.connect('activated', self._invite_clicked_cb) self.append(item, 0) diff --git a/shell/view/home/FriendView.py b/shell/view/home/FriendView.py index c585312c..ed058927 100644 --- a/shell/view/home/FriendView.py +++ b/shell/view/home/FriendView.py @@ -20,8 +20,8 @@ import gobject from sugar.graphics.canvasicon import CanvasIcon from sugar.graphics import style from sugar.presence import presenceservice +from sugar import activity -from model import bundleregistry from view.BuddyIcon import BuddyIcon class FriendView(hippo.CanvasBox): @@ -46,9 +46,9 @@ class FriendView(hippo.CanvasBox): self._buddy.connect('disappeared', self._buddy_disappeared_cb) self._buddy.connect('color-changed', self._buddy_color_changed_cb) - def _get_new_icon_name(self, activity): - registry = bundleregistry.get_registry() - bundle = registry.get_bundle(activity.get_type()) + def _get_new_icon_name(self, home_activity): + registry = activity.get_registry() + bundle = registry.get_bundle(home_activity.get_type()) if bundle: return bundle.get_icon() return None @@ -58,14 +58,14 @@ class FriendView(hippo.CanvasBox): self.remove(self._activity_icon) self._activity_icon_visible = False - def _buddy_activity_changed_cb(self, buddy, activity=None): - if not activity: + def _buddy_activity_changed_cb(self, buddy, home_activity=None): + if not home_activity: self._remove_activity_icon() return # FIXME: use some sort of "unknown activity" icon rather # than hiding the icon? - name = self._get_new_icon_name(activity) + name = self._get_new_icon_name(home_activity) if name: self._activity_icon.props.icon_name = name self._activity_icon.props.xo_color = buddy.get_color() @@ -76,8 +76,8 @@ class FriendView(hippo.CanvasBox): self._remove_activity_icon() def _buddy_appeared_cb(self, buddy): - activity = self._buddy.get_current_activity() - self._buddy_activity_changed_cb(buddy, activity) + home_activity = self._buddy.get_current_activity() + self._buddy_activity_changed_cb(buddy, home_activity) def _buddy_disappeared_cb(self, buddy): self._buddy_activity_changed_cb(buddy, None) diff --git a/sugar/activity/bundle.py b/sugar/activity/bundle.py index bc24092a..d361c621 100644 --- a/sugar/activity/bundle.py +++ b/sugar/activity/bundle.py @@ -296,11 +296,8 @@ class Bundle: raise ZipExtractException self._init_with_path(bundle_path) - - bus = dbus.SessionBus() - proxy_obj = bus.get_object(_DBUS_SHELL_SERVICE, _DBUS_SHELL_PATH) - dbus_service = dbus.Interface(proxy_obj, _DBUS_ACTIVITY_REGISTRY_IFACE) - if not dbus_service.AddBundle(bundle_path): + + if not activity.get_registry().add_bundle(bundle_path): raise RegistrationException def deinstall(self): diff --git a/sugar/activity/registry.py b/sugar/activity/registry.py index 430a2df2..1483a786 100644 --- a/sugar/activity/registry.py +++ b/sugar/activity/registry.py @@ -19,6 +19,7 @@ import logging import dbus +import gobject _ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry' _ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry' @@ -28,17 +29,25 @@ def _activity_info_from_dict(info_dict): if not info_dict: return None return ActivityInfo(info_dict['name'], info_dict['icon'], - info_dict['service_name'], info_dict['path']) + info_dict['service_name'], info_dict['path'], + info_dict['show_launcher']) class ActivityInfo(object): - def __init__(self, name, icon, service_name, path): + def __init__(self, name, icon, service_name, path, show_launcher): self.name = name self.icon = icon self.service_name = service_name self.path = path + self.show_launcher = show_launcher -class ActivityRegistry(object): +class ActivityRegistry(gobject.GObject): + __gsignals__ = { + 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } def __init__(self): + gobject.GObject.__init__(self) + bus = dbus.SessionBus() bus_object = bus.get_object(_ACTIVITY_REGISTRY_SERVICE_NAME, _ACTIVITY_REGISTRY_PATH) @@ -57,6 +66,10 @@ class ActivityRegistry(object): return result + def get_activities(self): + info_list = self._registry.GetActivities() + return self._convert_info_list(info_list) + def get_activity(self, service_name): if self._service_name_to_activity_info.has_key(service_name): return self._service_name_to_activity_info[service_name] @@ -81,10 +94,14 @@ class ActivityRegistry(object): self._mime_type_to_activities[mime_type] = activities return activities - def _activity_added_cb(self, bundle): + def add_bundle(self, bundle_path): + return self._registry.AddBundle(bundle_path) + + def _activity_added_cb(self, info_dict): logging.debug('ActivityRegistry._activity_added_cb: flushing caches') self._service_name_to_activity_info.clear() self._mime_type_to_activities.clear() + self.emit('activity-added', _activity_info_from_dict(info_dict)) _registry = None From 6aeb95aa467c30c878ba29c66461d9254c7a7392 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Thu, 9 Aug 2007 18:29:10 -0400 Subject: [PATCH 25/34] Install MIME data to map ".xo" files to "application/vnd.olpc-x-sugar" --- .gitignore | 2 ++ data/Makefile.am | 20 +++++++++++++++++++- data/sugar.xml.in | 7 +++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 data/sugar.xml.in diff --git a/.gitignore b/.gitignore index 6217e617..5ecb9c79 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ mkinstalldirs po/Makefile.in.in po/POTFILES po/*.gmo +po/.intltool-merge-cache sugar/__installed__.py tools/sugar-setup-activity threadframe @@ -53,4 +54,5 @@ browser/sugar-marshal.h bin/sugar shell/extensions/_extensions.c data/sugar.gtkrc +data/sugar.xml data/sugar-xo.gtkrc diff --git a/data/Makefile.am b/data/Makefile.am index b2fd17ae..4c613ab1 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -14,5 +14,23 @@ GTKRC_FILES = \ sugar.gtkrc \ sugar-xo.gtkrc -EXTRA_DIST = $(sugar_DATA) em.py gtkrc.em + +mime_xml_in_files = sugar.xml.in +mime_xml_files = $(mime_xml_in_files:.xml.in=.xml) +@INTLTOOL_XML_RULE@ + +mimedir = $(datadir)/mime/packages +mime_DATA = $(mime_xml_files) + +install-data-hook: + if [ -z "$$DESTDIR" ]; then \ + update-mime-database "$(datadir)/mime"; \ + fi + +uninstall-hook: + if [ -z "$$DESTDIR" ]; then \ + update-mime-database "$(datadir)/mime"; \ + fi + +EXTRA_DIST = $(sugar_DATA) $(mime_xml_in_files) em.py gtkrc.em CLEANFILES = $(GTKRC_FILES) diff --git a/data/sugar.xml.in b/data/sugar.xml.in new file mode 100644 index 00000000..39f6026e --- /dev/null +++ b/data/sugar.xml.in @@ -0,0 +1,7 @@ + + + + <_comment>Sugar activity bundle + + + \ No newline at end of file From 6573e8c5e6ed9ec8c9e7af10c6ad34acb10bcd67 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 10 Aug 2007 18:29:28 +0200 Subject: [PATCH 26/34] Draw an invoker rectangle that looks connected to the main palette. There is one catch though, the menu placement inside the palette seems broken. (Probably was already broken before.) Not sure what is going on there. --- NEWS | 1 + sugar/graphics/palette.py | 119 ++++++++++++++++++++++++++++- sugar/graphics/radiotoolbutton.py | 18 +++++ sugar/graphics/toggletoolbutton.py | 18 +++++ sugar/graphics/toolbutton.py | 17 +++++ 5 files changed, 172 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index e08efc6b..87173398 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,4 @@ +* Draw an invoker that is connected with the palette for toolbuttons. (benzea) * Fix traceback when reading in saved WPA2 network configs (dcbw) * #2475 Retrieve correctly the file path for files in removable devices. (tomeu) * #2119 If config is missing start intro. (marco) diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index e8532024..b251ca77 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -36,6 +36,39 @@ _RIGHT_TOP = 5 _TOP_LEFT = 6 _TOP_RIGHT = 7 + +# Helper function to find the gap position and size of widget a +def _calculate_gap(a, b): + # Test for each side if the palette and invoker are + # adjacent to each other. + gap = True + + if a.y + a.height == b.y: + gap_side = gtk.POS_BOTTOM + elif a.x + a.width == b.x: + gap_side = gtk.POS_RIGHT + elif a.x == b.x + b.width: + gap_side = gtk.POS_LEFT + elif a.y == b.y + b.height: + gap_side = gtk.POS_TOP + else: + gap = False + + if gap: + if gap_side == gtk.POS_BOTTOM or gap_side == gtk.POS_TOP: + gap_start = min(a.width, max(0, b.x - b.x)) + gap_size = max(0, min(a.width, + (b.x + b.width) - a.x) - gap_start) + elif gap_side == gtk.POS_RIGHT or gap_side == gtk.POS_LEFT: + gap_start = min(a.height, max(0, b.y - a.y)) + gap_size = max(0, min(a.height, + (b.y + b.height) - a.y) - gap_start) + + if gap and gap_size > 0: + return (gap_side, gap_start, gap_size) + else: + return False + class Palette(gtk.Window): DEFAULT = 0 AT_CURSOR = 1 @@ -54,7 +87,9 @@ class Palette(gtk.Window): 'invoker' : (object, None, None, gobject.PARAM_READWRITE), 'position' : (gobject.TYPE_INT, None, None, 0, 6, - 0, gobject.PARAM_READWRITE) + 0, gobject.PARAM_READWRITE), + 'draw-gap' : (bool, None, None, False, + gobject.PARAM_READWRITE) } __gsignals__ = { @@ -79,6 +114,7 @@ class Palette(gtk.Window): self._group_id = None self._up = False self._position = self.DEFAULT + self._draw_gap = False self._palette_popup_sid = None self._popup_anim = animator.Animator(0.3, 10) @@ -131,6 +167,17 @@ class Palette(gtk.Window): def is_up(self): return self._up + def get_rect(self): + win_x, win_y = self.window.get_origin() + rectangle = self.get_allocation() + + x = win_x + rectangle.x + y = win_y + rectangle.y + width = rectangle.width + height = rectangle.height + + return gtk.gdk.Rectangle(x, y, width, height) + def set_primary_text(self, label, accel_path=None): self._label.set_text(label) self._label.show() @@ -160,6 +207,9 @@ class Palette(gtk.Window): self._invoker.connect('mouse-leave', self._invoker_mouse_leave_cb) elif pspec.name == 'position': self._position = value + elif pspec.name == 'draw-gap': + self._draw_gap = value + self.queue_draw() else: raise AssertionError @@ -168,9 +218,39 @@ class Palette(gtk.Window): return self._invoker elif pspec.name == 'position': return self._position + elif pspec.name == 'draw-gap': + return self._draw_gap else: raise AssertionError + def do_expose_event(self, event): + # We want to draw a border with a beautiful gap + if self._draw_gap: + invoker = self._invoker.get_rect() + palette = self.get_rect() + + gap = _calculate_gap(palette, invoker) + else: + gap = False + + if gap: + self.style.paint_box_gap(event.window, gtk.STATE_PRELIGHT, + gtk.SHADOW_IN, event.area, self, "palette", + 0, 0, + self.allocation.width, + self.allocation.height, + gap[0], gap[1], gap[2]) + else: + self.style.paint_box(event.window, gtk.STATE_PRELIGHT, + gtk.SHADOW_IN, event.area, self, "palette", + 0, 0, + self.allocation.width, + self.allocation.height) + + # Fall trough to the container expose handler. + # (Leaving out the window expose handler which redraws everything) + gtk.Bin.do_expose_event(self, event) + def _update_separator(self): visible = len(self.menu.get_children()) > 0 or \ len(self._content.get_children()) > 0 @@ -396,6 +476,12 @@ class _Menu(_sugaruiext.Menu): _sugaruiext.Menu.do_insert(self, item, position) self._palette._update_separator() + def do_expose_event(self, event): + # Ignore the Menu expose, just do the MenuShell expose to prevent any + # border from being drawn here. A border is drawn by the palette object + # around everything. + gtk.MenuShell.do_expose_event(self, event) + def do_deactivate(self): self._palette._hide() @@ -467,6 +553,37 @@ class WidgetInvoker(Invoker): return gtk.gdk.Rectangle(x, y, width, height) + def draw_invoker_rect(self, event, palette): + style = self._widget.style + if palette.is_up(): + gap = _calculate_gap(self.get_rect(), palette.get_rect()) + + if gap: + style.paint_box_gap(event.window, gtk.STATE_PRELIGHT, + gtk.SHADOW_IN, event.area, self._widget, + "palette-invoker", + self._widget.allocation.x, + self._widget.allocation.y, + self._widget.allocation.width, + self._widget.allocation.height, + gap[0], gap[1], gap[2]) + else: + style.paint_box(event.window, gtk.STATE_PRELIGHT, + gtk.SHADOW_IN, event.area, self._widget, + "palette-invoker", + self._widget.allocation.x, + self._widget.allocation.y, + self._widget.allocation.width, + self._widget.allocation.height) + else: + style.paint_box(event.window, gtk.STATE_PRELIGHT, + gtk.SHADOW_NONE, event.area, self._widget, + "palette-invoker", + self._widget.allocation.x, + self._widget.allocation.y, + self._widget.allocation.width, + self._widget.allocation.height) + def _enter_notify_event_cb(self, widget, event): self.emit('mouse-enter') diff --git a/sugar/graphics/radiotoolbutton.py b/sugar/graphics/radiotoolbutton.py index 94ff6ba8..52fe61c7 100644 --- a/sugar/graphics/radiotoolbutton.py +++ b/sugar/graphics/radiotoolbutton.py @@ -22,6 +22,8 @@ from sugar.graphics.icon import Icon from sugar.graphics.palette import Palette, WidgetInvoker class RadioToolButton(gtk.RadioToolButton): + __gtype_name__ = "SugarRadioToolButton" + def __init__(self, named_icon=None, group=None): gtk.RadioToolButton.__init__(self, group=group) self._palette = None @@ -38,9 +40,25 @@ class RadioToolButton(gtk.RadioToolButton): def set_palette(self, palette): self._palette = palette self._palette.props.invoker = WidgetInvoker(self.child) + self._palette.props.draw_gap = True + + self._palette.connect("popup", self._palette_changed) + self._palette.connect("popdown", self._palette_changed) def set_tooltip(self, text): self._palette = Palette(text) self._palette.props.invoker = WidgetInvoker(self.child) + + def do_expose_event(self, event): + if self._palette: + if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT: + invoker = self._palette.props.invoker + invoker.draw_invoker_rect(event, self._palette) + + gtk.RadioToolButton.do_expose_event(self, event) + + def _palette_changed(self, palette): + # Force a redraw to update the invoker rectangle + self.queue_draw() palette = property(get_palette, set_palette) diff --git a/sugar/graphics/toggletoolbutton.py b/sugar/graphics/toggletoolbutton.py index 3684e9ca..975e78a4 100644 --- a/sugar/graphics/toggletoolbutton.py +++ b/sugar/graphics/toggletoolbutton.py @@ -21,6 +21,8 @@ from sugar.graphics.icon import Icon from sugar.graphics.palette import Palette, WidgetInvoker class ToggleToolButton(gtk.ToggleToolButton): + __gtype_name__ = "SugarToggleToolButton" + def __init__(self, named_icon=None): gtk.ToggleToolButton.__init__(self) self._palette = None @@ -37,9 +39,25 @@ class ToggleToolButton(gtk.ToggleToolButton): def set_palette(self, palette): self._palette = palette self._palette.props.invoker = WidgetInvoker(self.child) + self._palette.props.draw_gap = True + + self._palette.connect("popup", self._palette_changed) + self._palette.connect("popdown", self._palette_changed) def set_tooltip(self, text): self._palette = Palette(text) self._palette.props.invoker = WidgetInvoker(self.child) + def do_expose_event(self, event): + if self._palette: + if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT: + invoker = self._palette.props.invoker + invoker.draw_invoker_rect(event, self._palette) + + gtk.ToggleToolButton.do_expose_event(self, event) + + def _palette_changed(self, palette): + # Force a redraw to update the invoker rectangle + self.queue_draw() + palette = property(get_palette, set_palette) diff --git a/sugar/graphics/toolbutton.py b/sugar/graphics/toolbutton.py index e5d90ab8..c447b3c6 100644 --- a/sugar/graphics/toolbutton.py +++ b/sugar/graphics/toolbutton.py @@ -23,6 +23,7 @@ from sugar.graphics.icon import Icon from sugar.graphics.palette import Palette, WidgetInvoker class ToolButton(gtk.ToolButton): + __gtype_name__ = "SugarToolButton" def __init__(self, icon_name=None): gtk.ToolButton.__init__(self) @@ -41,12 +42,28 @@ class ToolButton(gtk.ToolButton): def set_palette(self, palette): self._palette = palette self._palette.props.invoker = WidgetInvoker(self.child) + self._palette.props.draw_gap = True + + self._palette.connect("popup", self._palette_changed) + self._palette.connect("popdown", self._palette_changed) def set_tooltip(self, text): self.set_palette(Palette(text)) + def do_expose_event(self, event): + if self._palette: + if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT: + invoker = self._palette.props.invoker + invoker.draw_invoker_rect(event, self._palette) + + gtk.ToolButton.do_expose_event(self, event) + def _button_clicked_cb(self, widget): if self._palette: self._palette.popdown(True) + def _palette_changed(self, palette): + # Force a redraw to update the invoker rectangle + self.queue_draw() + palette = property(get_palette, set_palette) From 476189323d1bde66b73928273c3c3cee35992493 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 10 Aug 2007 18:52:15 +0200 Subject: [PATCH 27/34] Don't draw the invoker rectangle for tooltips. --- sugar/graphics/radiotoolbutton.py | 2 +- sugar/graphics/toggletoolbutton.py | 2 +- sugar/graphics/toolbutton.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sugar/graphics/radiotoolbutton.py b/sugar/graphics/radiotoolbutton.py index 52fe61c7..fb584ee2 100644 --- a/sugar/graphics/radiotoolbutton.py +++ b/sugar/graphics/radiotoolbutton.py @@ -50,7 +50,7 @@ class RadioToolButton(gtk.RadioToolButton): self._palette.props.invoker = WidgetInvoker(self.child) def do_expose_event(self, event): - if self._palette: + if self._palette and self._palette.props.draw_gap: if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT: invoker = self._palette.props.invoker invoker.draw_invoker_rect(event, self._palette) diff --git a/sugar/graphics/toggletoolbutton.py b/sugar/graphics/toggletoolbutton.py index 975e78a4..41050e21 100644 --- a/sugar/graphics/toggletoolbutton.py +++ b/sugar/graphics/toggletoolbutton.py @@ -49,7 +49,7 @@ class ToggleToolButton(gtk.ToggleToolButton): self._palette.props.invoker = WidgetInvoker(self.child) def do_expose_event(self, event): - if self._palette: + if self._palette and self._palette.props.draw_gap: if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT: invoker = self._palette.props.invoker invoker.draw_invoker_rect(event, self._palette) diff --git a/sugar/graphics/toolbutton.py b/sugar/graphics/toolbutton.py index c447b3c6..52a5d62c 100644 --- a/sugar/graphics/toolbutton.py +++ b/sugar/graphics/toolbutton.py @@ -51,7 +51,7 @@ class ToolButton(gtk.ToolButton): self.set_palette(Palette(text)) def do_expose_event(self, event): - if self._palette: + if self._palette and self._palette.props.draw_gap: if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT: invoker = self._palette.props.invoker invoker.draw_invoker_rect(event, self._palette) From 8ed11b6feeded0e30e7b9596fdb58a2bfa4858a5 Mon Sep 17 00:00:00 2001 From: "John (J5) Palmieri" Date: Fri, 10 Aug 2007 12:57:43 -0400 Subject: [PATCH 28/34] sugar console changes directory to the user's home directory and sets proc title --- services/console/sugar-console | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/console/sugar-console b/services/console/sugar-console index af709a63..357b7fee 100755 --- a/services/console/sugar-console +++ b/services/console/sugar-console @@ -6,7 +6,15 @@ pygtk.require('2.0') import os import sys from sugar import env +from sugar import util sys.path.append(env.get_service_path('console')) +# change to the user's home directory if it is set +# root if not +os.chdir(os.environ.get('HOME', '/')) + +#set the process title so it shows up as sugar-console not python +util.set_proc_title('sugar-console') + import console From d2e4e99226e2074f0d09dc9baeadc2e7f7e31c25 Mon Sep 17 00:00:00 2001 From: "John (J5) Palmieri" Date: Fri, 10 Aug 2007 13:32:24 -0400 Subject: [PATCH 29/34] hack to make sure vte does not wrap too early in first instance * vte does not reflow if you add the widget before its container is realized so we hack around this by calling set_size with a high number for width --- services/console/interface/terminal/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/console/interface/terminal/terminal.py b/services/console/interface/terminal/terminal.py index 5eebfb36..4aba8584 100644 --- a/services/console/interface/terminal/terminal.py +++ b/services/console/interface/terminal/terminal.py @@ -32,7 +32,7 @@ class Terminal(gtk.HBox): self._vte = vte.Terminal() self._configure_vte() - self._vte.set_size(30, 5) + self._vte.set_size(100, 5) self._vte.set_size_request(200, 450) self._vte.show() self.pack_start(self._vte) From 91d7dbcb0896544b4cf8098373de72c887bb9e31 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 10 Aug 2007 23:21:36 +0200 Subject: [PATCH 30/34] Ignore grab_notify in the palette menu so that eg. scales work. --- sugar/graphics/palette.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index b251ca77..bcc32110 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -482,6 +482,10 @@ class _Menu(_sugaruiext.Menu): # around everything. gtk.MenuShell.do_expose_event(self, event) + def do_grab_notify(self, was_grabbed): + # Ignore grab_notify as the menu would close otherwise + pass + def do_deactivate(self): self._palette._hide() From c5719938118f58d770bc1588d1584d8b6e98862e Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Fri, 10 Aug 2007 23:39:12 +0200 Subject: [PATCH 31/34] Fixed a typo in the palette gap calculation code. --- sugar/graphics/palette.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index bcc32110..c8d0d98c 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -56,7 +56,7 @@ def _calculate_gap(a, b): if gap: if gap_side == gtk.POS_BOTTOM or gap_side == gtk.POS_TOP: - gap_start = min(a.width, max(0, b.x - b.x)) + gap_start = min(a.width, max(0, b.x - a.x)) gap_size = max(0, min(a.width, (b.x + b.width) - a.x) - gap_start) elif gap_side == gtk.POS_RIGHT or gap_side == gtk.POS_LEFT: From 05b0d212a4c9d8c3accfab41bfaff8badb42f37a Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 11 Aug 2007 01:00:53 +0200 Subject: [PATCH 32/34] Separate libsugar and libsugarui ld flags to not link libsugar to gtk. --- configure.ac | 6 ++++-- sugar/Makefile.am | 24 ++++++++++++++---------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index e0acaf19..4363ac90 100644 --- a/configure.ac +++ b/configure.ac @@ -21,8 +21,10 @@ PKG_CHECK_MODULES(SHELL, pygtk-2.0 gtk+-2.0) PKG_CHECK_MODULES(NATIVE_FACTORY, dbus-1) -PKG_CHECK_MODULES(LIB, gtk+-2.0) -PKG_CHECK_MODULES(LIB_BINDINGS, pygtk-2.0) +PKG_CHECK_MODULES(LIBUI, gtk+-2.0) +PKG_CHECK_MODULES(LIBUI_BINDINGS, pygtk-2.0) + +PKG_CHECK_MODULES(LIB_BINDINGS, pygobject-2.0) PYGTK_DEFSDIR=`$PKG_CONFIG --variable=defsdir pygtk-2.0` AC_SUBST(PYGTK_DEFSDIR) diff --git a/sugar/Makefile.am b/sugar/Makefile.am index 5fcb3874..dffca334 100644 --- a/sugar/Makefile.am +++ b/sugar/Makefile.am @@ -12,21 +12,19 @@ sugar_PYTHON = \ util.py \ wm.py -INCLUDES = \ - $(LIB_CFLAGS) \ - $(LIB_BINDINGS_CFLAGS) \ - $(PYTHON_INCLUDES) \ - -I$(top_srcdir)/lib \ - -I$(top_srcdir)/lib/ui - pkgpyexecdir = $(pythondir)/sugar pkgpyexec_LTLIBRARIES = _sugarext.la _sugaruiext.la +_sugarext_la_CFLAGS = \ + $(LIB_CFLAGS) \ + $(LIB_BINDINGS_CFLAGS) \ + $(PYTHON_INCLUDES) \ + -I$(top_srcdir)/lib + _sugarext_la_LDFLAGS = -module -avoid-version _sugarext_la_LIBADD = \ $(LIB_BINDINGS_LIBS) \ - $(LIB_LIBS) \ $(top_builddir)/lib/libsugar.la _sugarext_la_SOURCES = \ @@ -36,10 +34,16 @@ nodist__sugarext_la_SOURCES = _sugarext.c _sugarext.c: _sugarext.defs _sugarext.override +_sugaruiext_la_CFLAGS = \ + $(LIBUI_CFLAGS) \ + $(LIBUI_BINDINGS_CFLAGS) \ + $(PYTHON_INCLUDES) \ + -I$(top_srcdir)/lib/ui + _sugaruiext_la_LDFLAGS = -module -avoid-version _sugaruiext_la_LIBADD = \ - $(LIB_BINDINGS_LIBS) \ - $(LIB_LIBS) \ + $(LIBUI_BINDINGS_LIBS) \ + $(LIBUI_LIBS) \ $(top_builddir)/lib/ui/libsugarui.la _sugaruiext_la_SOURCES = \ From a85bc85a824c51e5e81860203cd8d2bee834e02f Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 11 Aug 2007 11:58:39 +0200 Subject: [PATCH 33/34] Fix cflags --- lib/ui/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/Makefile.am b/lib/ui/Makefile.am index 3640a08a..bae36c6a 100644 --- a/lib/ui/Makefile.am +++ b/lib/ui/Makefile.am @@ -1,10 +1,10 @@ libsugarui_la_CPPFLAGS = \ - $(LIB_CFLAGS) + $(LIBUI_CFLAGS) noinst_LTLIBRARIES = libsugarui.la libsugarui_la_LIBADD = \ - $(LIB_LIBS) + $(LIBUI_LIBS) libsugarui_la_SOURCES = \ $(BUILT_SOURCES) \ From dcef110223e312d44955ca4aa1e2f306b9cb9e12 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 11 Aug 2007 12:16:49 +0200 Subject: [PATCH 34/34] Embed/unembed the menu on state changes, keeping it around cause some weird drawing issues. Redraw on palette size changes (patch by Benzea). --- lib/ui/sugar-menu.c | 12 ++++++++++++ lib/ui/sugar-menu.h | 3 ++- sugar/_sugaruiext.defs | 6 ++++++ sugar/graphics/palette.py | 13 +++++++++---- 4 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/ui/sugar-menu.c b/lib/ui/sugar-menu.c index d822867e..f19dc4b8 100644 --- a/lib/ui/sugar-menu.c +++ b/lib/ui/sugar-menu.c @@ -36,10 +36,21 @@ sugar_menu_set_active(SugarMenu *menu, gboolean active) void sugar_menu_embed(SugarMenu *menu, GtkContainer *parent) { + menu->orig_toplevel = GTK_MENU(menu)->toplevel; + GTK_MENU(menu)->toplevel = gtk_widget_get_toplevel(GTK_WIDGET(parent)); gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(parent)); } +void +sugar_menu_unembed(SugarMenu *menu) +{ + if (menu->orig_toplevel) { + GTK_MENU(menu)->toplevel = menu->orig_toplevel; + gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(menu->orig_toplevel)); + } +} + static void sugar_menu_class_init(SugarMenuClass *menu_class) { @@ -48,4 +59,5 @@ sugar_menu_class_init(SugarMenuClass *menu_class) static void sugar_menu_init(SugarMenu *menu) { + menu->orig_toplevel = NULL; } diff --git a/lib/ui/sugar-menu.h b/lib/ui/sugar-menu.h index 05e28024..8773a316 100644 --- a/lib/ui/sugar-menu.h +++ b/lib/ui/sugar-menu.h @@ -35,8 +35,9 @@ typedef struct _SugarMenuClass SugarMenuClass; #define SUGAR_MENU_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_MENU, SugarMenuClass)) struct _SugarMenu { - GtkMenu base_instance; + GtkMenu base_instance; + GtkWidget *orig_toplevel; int min_width; }; diff --git a/sugar/_sugaruiext.defs b/sugar/_sugaruiext.defs index 456344a1..3c011e15 100644 --- a/sugar/_sugaruiext.defs +++ b/sugar/_sugaruiext.defs @@ -44,6 +44,12 @@ ) ) +(define-method unembed + (of-object "SugarMenu") + (c-name "sugar_menu_unembed") + (return-type "none") +) + ;; From sugar-key-grabber.h (define-function sugar_key_grabber_get_type diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py index c8d0d98c..368a0f6d 100644 --- a/sugar/graphics/palette.py +++ b/sugar/graphics/palette.py @@ -138,9 +138,9 @@ class Palette(gtk.Window): self._separator = gtk.HSeparator() self._secondary_box.pack_start(self._separator) - menu_box = gtk.VBox() - self._secondary_box.pack_start(menu_box) - menu_box.show() + self._menu_box = gtk.VBox() + self._secondary_box.pack_start(self._menu_box) + self._menu_box.show() self._content = gtk.VBox() self._secondary_box.pack_start(self._content) @@ -154,7 +154,6 @@ class Palette(gtk.Window): vbox.show() self.menu = _Menu(self) - self.menu.embed(menu_box) self.menu.show() self.connect('enter-notify-event', @@ -223,6 +222,10 @@ class Palette(gtk.Window): else: raise AssertionError + def do_size_allocate(self, allocation): + gtk.Window.do_size_allocate(self, allocation) + self.queue_draw() + def do_expose_event(self, event): # We want to draw a border with a beautiful gap if self._draw_gap: @@ -428,8 +431,10 @@ class Palette(gtk.Window): return if state == self._PRIMARY: + self.menu.unembed() self._secondary_box.hide() elif state == self._SECONDARY: + self.menu.embed(self._menu_box) self._secondary_box.show() self._state = state