Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2007-08-11 12:16:13 (GMT)
committer Simon McVittie <simon.mcvittie@collabora.co.uk>2007-08-11 12:16:13 (GMT)
commit7368429ad6feadd9e81757433b68b63d9d27c04f (patch)
tree39090b5e1c19f2d6ba63d9b07477668efff9a7c9
parentdf3069a9317fc804b62bd3624d9591fb2e09df0f (diff)
parentdcef110223e312d44955ca4aa1e2f306b9cb9e12 (diff)
Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar
-rw-r--r--.gitignore2
-rw-r--r--NEWS1
-rw-r--r--configure.ac10
-rw-r--r--data/Makefile.am20
-rw-r--r--data/sugar.xml.in7
-rw-r--r--lib/ui/Makefile.am4
-rw-r--r--lib/ui/sugar-menu.c47
-rw-r--r--lib/ui/sugar-menu.h15
-rw-r--r--services/Makefile.am2
-rw-r--r--services/console/interface/logviewer/logviewer.py107
-rw-r--r--services/console/interface/memphis/memphis.py5
-rw-r--r--services/console/interface/memphis/plugins/Makefile.am2
-rw-r--r--services/console/interface/memphis/plugins/dirty_size/__init__.py17
-rw-r--r--services/console/interface/memphis/plugins/memphis_init/info.py1
-rw-r--r--services/console/interface/memphis/plugins/smaps/Makefile.am (renamed from services/console/interface/memphis/plugins/dirty_size/Makefile.am)0
-rw-r--r--services/console/interface/memphis/plugins/smaps/README (renamed from services/console/interface/memphis/plugins/dirty_size/README)0
-rw-r--r--services/console/interface/memphis/plugins/smaps/__init__.py17
-rw-r--r--services/console/interface/memphis/plugins/smaps/info.py (renamed from services/console/interface/memphis/plugins/dirty_size/info.py)11
-rw-r--r--services/console/interface/terminal/terminal.py2
-rw-r--r--services/console/lib/procmem/analysis.py20
-rw-r--r--services/console/lib/procmem/proc.py47
-rw-r--r--services/console/lib/procmem/proc_smaps.py20
-rwxr-xr-xservices/console/sugar-console8
-rw-r--r--services/shell/Makefile.am (renamed from services/clipboard/Makefile.am)20
-rw-r--r--services/shell/__init__.py (renamed from services/clipboard/__init__.py)0
-rw-r--r--services/shell/activityregistryservice.py114
-rw-r--r--services/shell/bundleregistry.py (renamed from shell/model/bundleregistry.py)7
-rw-r--r--services/shell/clipboardobject.py (renamed from services/clipboard/clipboardobject.py)25
-rw-r--r--services/shell/clipboardservice.py (renamed from services/clipboard/clipboardservice.py)2
-rw-r--r--services/shell/objecttypeservice.py (renamed from services/clipboard/objecttypeservice.py)0
-rw-r--r--services/shell/org.laptop.ActivityRegistry.service.in4
-rw-r--r--services/shell/org.laptop.Clipboard.service.in (renamed from services/clipboard/org.laptop.Clipboard.service.in)2
-rw-r--r--services/shell/org.laptop.ObjectTypeRegistry.service.in (renamed from services/clipboard/org.laptop.ObjectTypeRegistry.service.in)2
-rwxr-xr-xservices/shell/sugar-shell-service (renamed from services/clipboard/sugar-clipboard)9
-rw-r--r--shell/model/Makefile.am1
-rw-r--r--shell/model/MeshModel.py26
-rw-r--r--shell/model/homeactivity.py18
-rw-r--r--shell/model/homemodel.py200
-rw-r--r--shell/shellservice.py69
-rw-r--r--shell/view/BuddyMenu.py9
-rw-r--r--shell/view/Shell.py23
-rw-r--r--shell/view/clipboardicon.py10
-rw-r--r--shell/view/clipboardmenu.py35
-rw-r--r--shell/view/frame/ActivitiesBox.py43
-rw-r--r--shell/view/home/FriendView.py18
-rw-r--r--shell/view/home/HomeBox.py2
-rw-r--r--shell/view/home/activitiesdonut.py8
-rw-r--r--sugar/Makefile.am24
-rw-r--r--sugar/_sugaruiext.defs20
-rw-r--r--sugar/_sugaruiext.override1
-rw-r--r--sugar/activity/activityfactory.py7
-rw-r--r--sugar/activity/bundle.py14
-rw-r--r--sugar/activity/bundlebuilder.py3
-rw-r--r--sugar/activity/registry.py37
-rw-r--r--sugar/graphics/canvasicon.py8
-rw-r--r--sugar/graphics/iconbutton.py2
-rw-r--r--sugar/graphics/objectchooser.py6
-rw-r--r--sugar/graphics/palette.py342
-rw-r--r--sugar/graphics/radiotoolbutton.py18
-rw-r--r--sugar/graphics/style.py7
-rw-r--r--sugar/graphics/toggletoolbutton.py18
-rw-r--r--sugar/graphics/toolbutton.py17
62 files changed, 933 insertions, 603 deletions
diff --git a/.gitignore b/.gitignore
index 6217e61..5ecb9c7 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/NEWS b/NEWS
index e08efc6..8717339 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/configure.ac b/configure.ac
index 2e7e339..4363ac9 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)
@@ -45,7 +47,7 @@ data/Makefile
lib/Makefile
lib/ui/Makefile
services/Makefile
-services/clipboard/Makefile
+services/shell/Makefile
shell/Makefile
shell/intro/Makefile
shell/hardware/Makefile
@@ -64,7 +66,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
diff --git a/data/Makefile.am b/data/Makefile.am
index b2fd17a..4c613ab 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 0000000..39f6026
--- /dev/null
+++ b/data/sugar.xml.in
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+ <mime-type type="application/vnd.olpc-x-sugar">
+ <_comment>Sugar activity bundle</_comment>
+ <glob pattern="*.xo"/>
+ </mime-type>
+</mime-info> \ No newline at end of file
diff --git a/lib/ui/Makefile.am b/lib/ui/Makefile.am
index 3640a08..bae36c6 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) \
diff --git a/lib/ui/sugar-menu.c b/lib/ui/sugar-menu.c
index 6ed482e..f19dc4b 100644
--- a/lib/ui/sugar-menu.c
+++ b/lib/ui/sugar-menu.c
@@ -28,57 +28,36 @@ 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;
+ menu->orig_toplevel = GTK_MENU(menu)->toplevel;
- gtk_widget_hide(GTK_WIDGET(menu));
- gtk_widget_hide(window);
+ GTK_MENU(menu)->toplevel = gtk_widget_get_toplevel(GTK_WIDGET(parent));
+ gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(parent));
}
-static void
-sugar_menu_size_request (GtkWidget *widget,
- GtkRequisition *requisition)
+void
+sugar_menu_unembed(SugarMenu *menu)
{
- SugarMenu *menu = SUGAR_MENU(widget);
-
- (* GTK_WIDGET_CLASS (sugar_menu_parent_class)->size_request) (widget, requisition);
-
- requisition->width = MAX(requisition->width, menu->min_width);
+ 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)
{
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(menu_class);
-
- widget_class->size_request = sugar_menu_size_request;
}
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 24e2865..8773a31 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;
};
@@ -44,13 +45,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/services/Makefile.am b/services/Makefile.am
index 7d8e351..e230030 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -1 +1 @@
-SUBDIRS = clipboard console
+SUBDIRS = shell console
diff --git a/services/console/interface/logviewer/logviewer.py b/services/console/interface/logviewer/logviewer.py
index 3d90f09..8aaf347 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
-
- 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);
+ self._add_log_file(full_log_path)
+
+ 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
diff --git a/services/console/interface/memphis/memphis.py b/services/console/interface/memphis/memphis.py
index 0dd52fc..5b1ce40 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()
diff --git a/services/console/interface/memphis/plugins/Makefile.am b/services/console/interface/memphis/plugins/Makefile.am
index a18eafe..d026419 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 =
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 f8e9e0a..0000000
--- 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/memphis_init/info.py b/services/console/interface/memphis/plugins/memphis_init/info.py
index 6e524c7..667645c 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/interface/memphis/plugins/dirty_size/Makefile.am b/services/console/interface/memphis/plugins/smaps/Makefile.am
index 1f81dfb..1f81dfb 100644
--- a/services/console/interface/memphis/plugins/dirty_size/Makefile.am
+++ b/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
index ee4d1a5..ee4d1a5 100644
--- a/services/console/interface/memphis/plugins/dirty_size/README
+++ b/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 0000000..5977d4b
--- /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
index 54a2e7e..998a1a2 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/interface/terminal/terminal.py b/services/console/interface/terminal/terminal.py
index 5eebfb3..4aba858 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)
diff --git a/services/console/lib/procmem/analysis.py b/services/console/lib/procmem/analysis.py
index d2a247a..e9d7aec 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.py b/services/console/lib/procmem/proc.py
index adc2f6b..d50242b 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 ce93cb2..422866c 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 -= 7
- line_idx += 7
+ 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
diff --git a/services/console/sugar-console b/services/console/sugar-console
index af709a6..357b7fe 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
diff --git a/services/clipboard/Makefile.am b/services/shell/Makefile.am
index e5a03e4..b34b974 100644
--- a/services/clipboard/Makefile.am
+++ b/services/shell/Makefile.am
@@ -1,29 +1,37 @@
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)|" $< > $@
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 \
- clipboardobject.py \
- clipboardservice.py \
+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
index 52b82c8..52b82c8 100644
--- a/services/clipboard/__init__.py
+++ b/services/shell/__init__.py
diff --git a/services/shell/activityregistryservice.py b/services/shell/activityregistryservice.py
new file mode 100644
index 0000000..44c9969
--- /dev/null
+++ b/services/shell/activityregistryservice.py
@@ -0,0 +1,114 @@
+# 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='', 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):
+ 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(),
+ 'show_launcher': bundle.get_show_launcher()}
+
+ 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/shell/bundleregistry.py
index bc8eec9..65a2348 100644
--- a/shell/model/bundleregistry.py
+++ b/services/shell/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/shell/clipboardobject.py
index d751274..bc51f47 100644
--- a/services/clipboard/clipboardobject.py
+++ b/services/shell/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/shell/clipboardservice.py
index 639f29c..19958a7 100644
--- a/services/clipboard/clipboardservice.py
+++ b/services/shell/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/objecttypeservice.py b/services/shell/objecttypeservice.py
index e12398e..e12398e 100644
--- a/services/clipboard/objecttypeservice.py
+++ b/services/shell/objecttypeservice.py
diff --git a/services/shell/org.laptop.ActivityRegistry.service.in b/services/shell/org.laptop.ActivityRegistry.service.in
new file mode 100644
index 0000000..ab6647c
--- /dev/null
+++ b/services/shell/org.laptop.ActivityRegistry.service.in
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name = org.laptop.ActivityRegistry
+Exec = @bindir@/sugar-shell-service
+
diff --git a/services/clipboard/org.laptop.Clipboard.service.in b/services/shell/org.laptop.Clipboard.service.in
index b38bf2b..7ce3f6e 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
index 66477eb..563a600 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
index 4cffa33..370c2ea 100755
--- a/services/clipboard/sugar-clipboard
+++ b/services/shell/sugar-shell-service
@@ -23,28 +23,31 @@ 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()
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 486ad09..0b7d14c 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/model/MeshModel.py b/shell/model/MeshModel.py
index 44974e2..82980c3 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 c45e5c7..e95fe9a 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 7d1039b..dac434a 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)
@@ -51,15 +51,18 @@ 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):
gobject.GObject.__init__(self)
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 +70,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, home_activity):
+ if self._pending_activity == home_activity:
+ return
+
+ self._pending_activity = home_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, home_activity):
+ if self._active_activity == home_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 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 = home_activity
+ self.emit('active-activity-changed', self._active_activity)
def __iter__(self):
return iter(self._activities)
@@ -84,65 +134,48 @@ 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(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())
- 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:
- 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 _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,55 +184,58 @@ 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)
-
- 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
-
- self.emit('activity-removed', activity)
- self._activities.remove(activity)
+ self._set_pending_activity(act)
+ self._set_active_activity(act)
+
+ def _add_activity(self, home_activity):
+ self._activities.append(home_activity)
+ self.emit('activity-added', home_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()
+ 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', 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/shellservice.py b/shell/shellservice.py
index 5728e44..d577a44 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/shell/view/BuddyMenu.py b/shell/view/BuddyMenu.py
index 3162ab1..e3efb5c 100644
--- a/shell/view/BuddyMenu.py
+++ b/shell/view/BuddyMenu.py
@@ -85,18 +85,19 @@ 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()
+ 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
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/Shell.py b/shell/view/Shell.py
index 697dc1c..044cbde 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
@@ -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
@@ -107,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
@@ -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/clipboardicon.py b/shell/view/clipboardicon.py
index a47104d..2e60e36 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()
diff --git a/shell/view/clipboardmenu.py b/shell/view/clipboardmenu.py
index 35802dc..28ea0bb 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)
@@ -120,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 a46e8e9..909a5f2 100644
--- a/shell/view/frame/ActivitiesBox.py
+++ b/shell/view/frame/ActivitiesBox.py
@@ -20,30 +20,33 @@ 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 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):
@@ -64,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)
@@ -90,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 c585312..ed05892 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/shell/view/home/HomeBox.py b/shell/view/home/HomeBox.py
index bfb4265..2fa2183 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 0c690b2..3b12a09 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):
@@ -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)
diff --git a/sugar/Makefile.am b/sugar/Makefile.am
index 5fcb387..dffca33 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 = \
diff --git a/sugar/_sugaruiext.defs b/sugar/_sugaruiext.defs
index c4efbbc..3c011e1 100644
--- a/sugar/_sugaruiext.defs
+++ b/sugar/_sugaruiext.defs
@@ -26,19 +26,27 @@
;; 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")
+ )
+)
+
+(define-method unembed
+ (of-object "SugarMenu")
+ (c-name "sugar_menu_unembed")
(return-type "none")
)
diff --git a/sugar/_sugaruiext.override b/sugar/_sugaruiext.override
index 02e900e..6daafc3 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/activity/activityfactory.py b/sugar/activity/activityfactory.py
index 404e5f4..b1d55eb 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))
diff --git a/sugar/activity/bundle.py b/sugar/activity/bundle.py
index a9c246d..d361c62 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
@@ -293,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/bundlebuilder.py b/sugar/activity/bundlebuilder.py
index b255cfb..3bbe454 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
diff --git a/sugar/activity/registry.py b/sugar/activity/registry.py
index b19abee..1483a78 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
@@ -18,29 +19,39 @@
import logging
import dbus
+import gobject
-_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:
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(_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.
@@ -55,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]
@@ -79,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
diff --git a/sugar/graphics/canvasicon.py b/sugar/graphics/canvasicon.py
index e879f05..39f1358 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 85ea4e4..ba9fad8 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/objectchooser.py b/sugar/graphics/objectchooser.py
index c75cec0..5b09e13 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()
diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py
index 45ac057..368a0f6 100644
--- a/sugar/graphics/palette.py
+++ b/sugar/graphics/palette.py
@@ -36,7 +36,40 @@ _RIGHT_TOP = 5
_TOP_LEFT = 6
_TOP_RIGHT = 7
-class Palette(gobject.GObject):
+
+# 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 - 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:
+ 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
AROUND = 2
@@ -54,7 +87,9 @@ class Palette(gobject.GObject):
'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__ = {
@@ -65,16 +100,21 @@ 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
self._position = self.DEFAULT
+ self._draw_gap = False
self._palette_popup_sid = None
self._popup_anim = animator.Animator(0.3, 10)
@@ -86,60 +126,70 @@ class Palette(gobject.GObject):
self._popdown_anim = animator.Animator(0.6, 10)
self._popdown_anim.add(_PopdownAnimation(self))
- self._menu = _sugaruiext.Menu()
+ vbox = gtk.VBox()
+ vbox.set_border_width(style.DEFAULT_PADDING)
- self._primary = _PrimaryMenuItem(label, accel_path)
- self._menu.append(self._primary)
- self._primary.show()
+ self._label = gtk.Label()
+ vbox.pack_start(self._label, False)
- self._separator = gtk.SeparatorMenuItem()
- self._menu.append(self._separator)
+ self._secondary_box = gtk.VBox()
+ vbox.pack_start(self._secondary_box)
- self._content = _ContentMenuItem()
- self._menu.append(self._content)
+ self._separator = gtk.HSeparator()
+ self._secondary_box.pack_start(self._separator)
- self._button_bar = _ButtonBarMenuItem()
- self._menu.append(self._button_bar)
+ self._menu_box = gtk.VBox()
+ self._secondary_box.pack_start(self._menu_box)
+ self._menu_box.show()
- self._menu.connect('enter-notify-event',
- self._enter_notify_event_cb)
- self._menu.connect('leave-notify-event',
- self._leave_notify_event_cb)
+ self._content = gtk.VBox()
+ self._secondary_box.pack_start(self._content)
+ self._content.show()
- def is_up(self):
- return self._up
+ self.action_bar = PaletteActionBar()
+ self._secondary_box.pack_start(self.action_bar)
+ self.action_bar.show()
- def set_primary_text(self, label, accel_path=None):
- self._primary.set_label(label, accel_path)
+ self.add(vbox)
+ vbox.show()
- def append_menu_item(self, item):
- self._separator.show()
- self._menu.insert(item, len(self._menu.get_children()) - 2)
+ self.menu = _Menu(self)
+ self.menu.show()
- 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.connect('enter-notify-event',
+ self._enter_notify_event_cb)
+ self.connect('leave-notify-event',
+ self._leave_notify_event_cb)
+
+ self.set_primary_text(label, accel_path)
- 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()
+ def is_up(self):
+ return self._up
+
+ def get_rect(self):
+ win_x, win_y = self.window.get_origin()
+ rectangle = self.get_allocation()
- def menu_item_count(self):
- return len(self._menu.get_children()) - 4
+ 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()
+
def set_content(self, widget):
- self._content.set_widget(widget)
- self._content.show()
+ if len(self._content.get_children()) > 0:
+ self.remove(self._content.get_children()[0])
+
+ if widget is not None:
+ self._content.add(widget)
- def append_button(self, button):
- self._button_bar.append_button(button)
- self._button_bar.show()
+ self._update_accept_focus()
+ self._update_separator()
def set_group_id(self, group_id):
if self._group_id:
@@ -154,9 +204,11 @@ class Palette(gobject.GObject):
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
+ elif pspec.name == 'draw-gap':
+ self._draw_gap = value
+ self.queue_draw()
else:
raise AssertionError
@@ -165,9 +217,57 @@ class Palette(gobject.GObject):
return self._invoker
elif pspec.name == 'position':
return self._position
+ elif pspec.name == 'draw-gap':
+ return self._draw_gap
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:
+ 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
+ self._separator.props.visible = visible
+
+ 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._update_accept_focus()
+
def _in_screen(self, x, y):
[width, height] = self._full_request
screen_area = self._invoker.get_screen_area()
@@ -182,7 +282,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 +341,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 +382,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:
@@ -291,11 +391,12 @@ class Palette(gobject.GObject):
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)
+ self.show()
self._up = True
_palette_observer.emit('popup', self)
@@ -305,7 +406,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 +431,11 @@ 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.unembed()
+ self._secondary_box.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()
-
- for menu_item in middle_menu_items:
- menu_item.show()
-
- if not self._content.is_empty():
- self._content.show()
-
- if not self._button_bar.is_empty():
- self._button_bar.show()
+ self.menu.embed(self._menu_box)
+ self._secondary_box.show()
self._state = state
@@ -357,68 +445,54 @@ class Palette(gobject.GObject):
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:
+ 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):
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)
+class PaletteActionBar(gtk.HButtonBox):
+ def add_action(label, icon_name=None):
+ button = Button(label)
- def set_label(self, label, accel_path):
- self.remove(self._label)
- self._set_label(label, accel_path)
+ if icon_name:
+ icon = Icon(icon_name)
+ button.set_image(icon)
+ icon.show()
- def _set_label(self, label, accel_path):
- self._label = gtk.AccelLabel(label)
- self._label.set_accel_widget(self)
+ self.pack_start(button)
+ button.show()
- if accel_path:
- self.set_accel_path(accel_path)
- self._label.set_alignment(0.0, 0.5)
+class _Menu(_sugaruiext.Menu):
+ __gtype_name__ = 'SugarPaletteMenu'
- 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
+ def __init__(self, palette):
+ _sugaruiext.Menu.__init__(self)
+ self._palette = palette
-class _ButtonBarMenuItem(gtk.MenuItem):
- def __init__(self):
- gtk.MenuItem.__init__(self)
+ def do_insert(self, item, position):
+ _sugaruiext.Menu.do_insert(self, item, position)
+ self._palette._update_separator()
- self._hbar = gtk.HButtonBox()
- self.add(self._hbar)
- self._hbar.show()
+ 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 append_button(self, button):
- self._hbar.pack_start(button)
+ def do_grab_notify(self, was_grabbed):
+ # Ignore grab_notify as the menu would close otherwise
+ pass
- def is_empty(self):
- return len(self._hbar.get_children()) == 0
+ def do_deactivate(self):
+ self._palette._hide()
class _PopupAnimation(animator.Animation):
def __init__(self, palette):
@@ -469,13 +543,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)
@@ -495,6 +562,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 94ff6ba..fb584ee 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 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)
+
+ 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/style.py b/sugar/graphics/style.py
index 55b4a4b..a5186da 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')
diff --git a/sugar/graphics/toggletoolbutton.py b/sugar/graphics/toggletoolbutton.py
index 3684e9c..41050e2 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 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)
+
+ 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 e5d90ab..52a5d62 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 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)
+
+ 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)