Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar

This commit is contained in:
Simon McVittie 2007-08-11 13:16:13 +01:00
commit 7368429ad6
62 changed files with 931 additions and 601 deletions

2
.gitignore vendored
View File

@ -38,6 +38,7 @@ mkinstalldirs
po/Makefile.in.in po/Makefile.in.in
po/POTFILES po/POTFILES
po/*.gmo po/*.gmo
po/.intltool-merge-cache
sugar/__installed__.py sugar/__installed__.py
tools/sugar-setup-activity tools/sugar-setup-activity
threadframe threadframe
@ -53,4 +54,5 @@ browser/sugar-marshal.h
bin/sugar bin/sugar
shell/extensions/_extensions.c shell/extensions/_extensions.c
data/sugar.gtkrc data/sugar.gtkrc
data/sugar.xml
data/sugar-xo.gtkrc data/sugar-xo.gtkrc

1
NEWS
View File

@ -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) * Fix traceback when reading in saved WPA2 network configs (dcbw)
* #2475 Retrieve correctly the file path for files in removable devices. (tomeu) * #2475 Retrieve correctly the file path for files in removable devices. (tomeu)
* #2119 If config is missing start intro. (marco) * #2119 If config is missing start intro. (marco)

View File

@ -21,8 +21,10 @@ PKG_CHECK_MODULES(SHELL, pygtk-2.0 gtk+-2.0)
PKG_CHECK_MODULES(NATIVE_FACTORY, dbus-1) PKG_CHECK_MODULES(NATIVE_FACTORY, dbus-1)
PKG_CHECK_MODULES(LIB, gtk+-2.0) PKG_CHECK_MODULES(LIBUI, gtk+-2.0)
PKG_CHECK_MODULES(LIB_BINDINGS, pygtk-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` PYGTK_DEFSDIR=`$PKG_CONFIG --variable=defsdir pygtk-2.0`
AC_SUBST(PYGTK_DEFSDIR) AC_SUBST(PYGTK_DEFSDIR)
@ -45,7 +47,7 @@ data/Makefile
lib/Makefile lib/Makefile
lib/ui/Makefile lib/ui/Makefile
services/Makefile services/Makefile
services/clipboard/Makefile services/shell/Makefile
shell/Makefile shell/Makefile
shell/intro/Makefile shell/intro/Makefile
shell/hardware/Makefile shell/hardware/Makefile
@ -64,7 +66,7 @@ services/console/Makefile
services/console/interface/Makefile services/console/interface/Makefile
services/console/interface/xo/Makefile services/console/interface/xo/Makefile
services/console/interface/memphis/plugins/clean_size/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/Makefile
services/console/interface/memphis/plugins/memphis_init/Makefile services/console/interface/memphis/plugins/memphis_init/Makefile
services/console/interface/memphis/plugins/cpu/Makefile services/console/interface/memphis/plugins/cpu/Makefile

View File

@ -14,5 +14,23 @@ GTKRC_FILES = \
sugar.gtkrc \ sugar.gtkrc \
sugar-xo.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) CLEANFILES = $(GTKRC_FILES)

7
data/sugar.xml.in Normal file
View File

@ -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>

View File

@ -1,10 +1,10 @@
libsugarui_la_CPPFLAGS = \ libsugarui_la_CPPFLAGS = \
$(LIB_CFLAGS) $(LIBUI_CFLAGS)
noinst_LTLIBRARIES = libsugarui.la noinst_LTLIBRARIES = libsugarui.la
libsugarui_la_LIBADD = \ libsugarui_la_LIBADD = \
$(LIB_LIBS) $(LIBUI_LIBS)
libsugarui_la_SOURCES = \ libsugarui_la_SOURCES = \
$(BUILT_SOURCES) \ $(BUILT_SOURCES) \

View File

@ -28,57 +28,36 @@ static void sugar_menu_init (SugarMenu *menu);
G_DEFINE_TYPE(SugarMenu, sugar_menu, GTK_TYPE_MENU) G_DEFINE_TYPE(SugarMenu, sugar_menu, GTK_TYPE_MENU)
void void
sugar_menu_popup(SugarMenu *menu, sugar_menu_set_active(SugarMenu *menu, gboolean active)
int x,
int y)
{ {
GtkWidget *window; GTK_MENU_SHELL(menu)->active = active;
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);
} }
void void
sugar_menu_popdown(SugarMenu *menu) sugar_menu_embed(SugarMenu *menu, GtkContainer *parent)
{ {
GtkWidget *window; menu->orig_toplevel = GTK_MENU(menu)->toplevel;
window = GTK_MENU(menu)->toplevel; GTK_MENU(menu)->toplevel = gtk_widget_get_toplevel(GTK_WIDGET(parent));
g_return_if_fail(window != NULL); gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(parent));
GTK_MENU_SHELL(menu)->active = FALSE;
gtk_widget_hide(GTK_WIDGET(menu));
gtk_widget_hide(window);
} }
static void void
sugar_menu_size_request (GtkWidget *widget, sugar_menu_unembed(SugarMenu *menu)
GtkRequisition *requisition)
{ {
SugarMenu *menu = SUGAR_MENU(widget); if (menu->orig_toplevel) {
GTK_MENU(menu)->toplevel = menu->orig_toplevel;
(* GTK_WIDGET_CLASS (sugar_menu_parent_class)->size_request) (widget, requisition); gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(menu->orig_toplevel));
}
requisition->width = MAX(requisition->width, menu->min_width);
} }
static void static void
sugar_menu_class_init(SugarMenuClass *menu_class) 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 static void
sugar_menu_init(SugarMenu *menu) sugar_menu_init(SugarMenu *menu)
{ {
menu->orig_toplevel = NULL;
} }

View File

@ -35,8 +35,9 @@ typedef struct _SugarMenuClass SugarMenuClass;
#define SUGAR_MENU_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_MENU, SugarMenuClass)) #define SUGAR_MENU_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_MENU, SugarMenuClass))
struct _SugarMenu { struct _SugarMenu {
GtkMenu base_instance; GtkMenu base_instance;
GtkWidget *orig_toplevel;
int min_width; int min_width;
}; };
@ -44,13 +45,11 @@ struct _SugarMenuClass {
GtkMenuClass base_class; GtkMenuClass base_class;
}; };
GType sugar_menu_get_type (void); GType sugar_menu_get_type (void);
void sugar_menu_popup (SugarMenu *menu, void sugar_menu_set_active (SugarMenu *menu,
int x, gboolean active);
int y); void sugar_menu_embed (SugarMenu *menu,
void sugar_menu_set_min_width (SugarMenu *menu, GtkContainer *parent);
int min_width);
void sugar_menu_popdown (SugarMenu *menu);
G_END_DECLS G_END_DECLS

View File

@ -1 +1 @@
SUBDIRS = clipboard console SUBDIRS = shell console

View File

@ -28,29 +28,29 @@ import pango
from sugar import env from sugar import env
class MultiLogView(gtk.VBox): class MultiLogView(gtk.VBox):
def __init__(self, path): def __init__(self, path, extra_files):
self._active_log = None self._active_log = None
self._iters = [] self._extra_files = extra_files
# Creating Main treeview with Actitivities list # Creating Main treeview with Actitivities list
tv_menu = gtk.TreeView() self._tv_menu = gtk.TreeView()
tv_menu.connect('cursor-changed', self._load_log) self._tv_menu.connect('cursor-changed', self._load_log)
tv_menu.set_rules_hint(True) self._tv_menu.set_rules_hint(True)
# Set width # Set width
box_width = gtk.gdk.screen_width() * 80 / 100 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) self._store_menu = gtk.TreeStore(str)
tv_menu.set_model(self.store_menu) 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._logs_path = os.path.join(env.get_profile_path(), 'logs')
self._activity = {} self._activity = {}
# Activities menu # Activities menu
self.hbox = gtk.HBox(False, 3) 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 # Activity log, set width
self._view = LogView() self._view = LogView()
@ -59,52 +59,62 @@ class MultiLogView(gtk.VBox):
self.hbox.pack_start(self._view, True, True, 0) self.hbox.pack_start(self._view, True, True, 0)
self.hbox.show_all() 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) # Load the log information in View (textview)
def _load_log(self, treeview): def _load_log(self, treeview):
treeselection = treeview.get_selection() treeselection = treeview.get_selection()
treestore, iter = treeselection.get_selected() treestore, iter = treeselection.get_selected()
# Get current selection # 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 # Set buffer and scroll down
self._view.textview.set_buffer(self._activity[act_log]) self._view.textview.set_buffer(self._activity[act_log])
self._view.textview.scroll_to_mark(self._activity[act_log].get_insert(), 0); self._view.textview.scroll_to_mark(self._activity[act_log].get_insert(), 0);
self._active_log = act_log self._active_log = act_log
def _update(self, tv_menu): def _update(self):
# Searching log files # Searching log files
for logfile in os.listdir(self._logs_path): for logfile in os.listdir(self._logs_path):
full_log_path = os.path.join(self._logs_path, logfile) full_log_path = os.path.join(self._logs_path, logfile)
self._add_log_file(full_log_path)
if os.path.isdir(full_log_path): for ext in self._extra_files:
continue self._add_log_file(ext)
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);
return True 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): 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) # Add a new column to the main treeview, (code from Memphis)
def _add_column(self, treeview, column_name, index): def _add_column(self, treeview, column_name, index):
@ -171,9 +181,20 @@ class LogView(gtk.ScrolledWindow):
self.textview.show() self.textview.show()
class Interface: class Interface:
def __init__(self): def __init__(self):
path = None 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 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

View File

@ -130,15 +130,14 @@ class Data:
treeview.set_model(self.store) treeview.set_model(self.store)
def _start_memphis(self, button): def _start_memphis(self, button):
# Update information every 1.5 second # Update information every 1.5 second
button.hide() button.hide()
self.interface.button_stop.show() self.interface.button_stop.show()
self._running_status = True 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): def _stop_memphis(self, button):
gobject.source_remove(self._gid)
self._running_status = False self._running_status = False
button.hide() button.hide()
self.interface.button_start.show() self.interface.button_start.show()

View File

@ -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 sugardir = $(pkgdatadir)/services/console/interface/memphis/plugins
sugar_PYTHON = sugar_PYTHON =

View File

@ -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)"]
}

View File

@ -11,3 +11,4 @@ def plg_on_top_data_refresh(self, ppinfo):
data = [ppinfo['pid'], ppinfo['name'], ppinfo['state_name']] data = [ppinfo['pid'], ppinfo['name'], ppinfo['state_name']]
return data return data

View File

@ -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)"]
}

View File

@ -8,13 +8,12 @@
def plg_on_top_data_refresh(self, ppinfo): def plg_on_top_data_refresh(self, ppinfo):
smaps = get_data(self, ppinfo['pid'])
dirty_sizes = get_dirty(self, ppinfo['pid']) # memphis need an array
return [smaps['private_dirty'], smaps['referenced']]
# memhis need an array def get_data(pself, pid):
return [dirty_sizes['private']]
def get_dirty(pself, pid):
ProcAnalysis = pself.INTERNALS['Plg'].proc_analysis(pid) ProcAnalysis = pself.INTERNALS['Plg'].proc_analysis(pid)
return ProcAnalysis.DirtyRSS() return ProcAnalysis.SMaps()

View File

@ -32,7 +32,7 @@ class Terminal(gtk.HBox):
self._vte = vte.Terminal() self._vte = vte.Terminal()
self._configure_vte() self._configure_vte()
self._vte.set_size(30, 5) self._vte.set_size(100, 5)
self._vte.set_size_request(200, 450) self._vte.set_size_request(200, 450)
self._vte.show() self._vte.show()
self.pack_start(self._vte) self.pack_start(self._vte)

View File

@ -7,20 +7,22 @@ class Analysis:
def __init__(self, pid): def __init__(self, pid):
self.pid = pid self.pid = pid
def DirtyRSS(self): def SMaps(self):
smaps = proc_smaps.ProcSmaps(self.pid) smaps = proc_smaps.ProcSmaps(self.pid)
dirty = [] private_dirty = 0
shared_dirty = 0
private = 0 referenced = 0
shared = 0
for map in smaps.mappings: for map in smaps.mappings:
private += map.private_dirty private_dirty += map.private_dirty
shared += map.shared_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): def ApproxRealMemoryUsage(self):
maps = proc_smaps.ProcMaps(self.pid) maps = proc_smaps.ProcMaps(self.pid)

View File

@ -1,4 +1,6 @@
import sys, os import os
import re
import sys
import string import string
class ProcInfo: class ProcInfo:
@ -36,10 +38,12 @@ class ProcInfo:
return None return None
# Parsing data , check 'man 5 proc' for details # Parsing data , check 'man 5 proc' for details
data = infile.read().split() stat_data = infile.read()
infile.close() infile.close()
process_name = self._get_process_name(stat_data)
data = self._get_safe_split(stat_data)
state_dic = { state_dic = {
'R': 'Running', 'R': 'Running',
'S': 'Sleeping', 'S': 'Sleeping',
@ -51,24 +55,31 @@ class ProcInfo:
# user and group owners # user and group owners
pidstat = os.stat(pidfile) pidstat = os.stat(pidfile)
info = { info = {
'pid': int(data[0]), # Process ID 'pid': int(data[0]), # Process ID
'name': data[1].strip('()'), # Process name 'name': process_name,
'state': data[2], # Process State, ex: R|S|D|Z|T|W '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 'state_name': state_dic[data[2]], # Process State name, ex: Running, sleeping, Zombie, etc
'ppid': int(data[3]), # Parent process ID 'ppid': int(data[3]), # Parent process ID
'utime': int(data[13]), # Used jiffies in user mode 'utime': int(data[13]), # Used jiffies in user mode
'stime': int(data[14]), # Used jiffies in kernel mode 'stime': int(data[14]), # Used jiffies in kernel mode
'start_time': int(data[21]), # Process time from system boot (jiffies) 'start_time': int(data[21]), # Process time from system boot (jiffies)
'vsize': int(data[22]), # Virtual memory size used (bytes) 'vsize': int(data[22]), # Virtual memory size used (bytes)
'rss': int(data[23])*4, # Resident Set Size (bytes) 'rss': int(data[23])*4, # Resident Set Size (bytes)
'user_id': pidstat.st_uid, # process owner 'user_id': pidstat.st_uid, # process owner
'group_id': pidstat.st_gid # owner group 'group_id': pidstat.st_gid # owner group
} }
return info 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 # Returns the CPU usage expressed in Jiffies
def get_CPU_usage(self, cpu_hz, used_jiffies, start_time): def get_CPU_usage(self, cpu_hz, used_jiffies, start_time):

View File

@ -36,6 +36,7 @@ class ProcSmaps:
# Shared_Dirty: 0 kB # Shared_Dirty: 0 kB
# Private_Clean: 8 kB # Private_Clean: 8 kB
# Private_Dirty: 0 kB # Private_Dirty: 0 kB
# Referenced: 4 kb -> Introduced in kernel 2.6.22
while num_lines > 0: while num_lines > 0:
fields = lines[line_idx].split (" ", 5) fields = lines[line_idx].split (" ", 5)
@ -51,13 +52,20 @@ class ProcSmaps:
shared_dirty = self.parse_smaps_size_line (lines[line_idx + 4]) shared_dirty = self.parse_smaps_size_line (lines[line_idx + 4])
private_clean = self.parse_smaps_size_line (lines[line_idx + 5]) private_clean = self.parse_smaps_size_line (lines[line_idx + 5])
private_dirty = self.parse_smaps_size_line (lines[line_idx + 6]) private_dirty = self.parse_smaps_size_line (lines[line_idx + 6])
referenced = self.parse_smaps_size_line (lines[line_idx + 7])
name = name.strip () 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) self.mappings.append (mapping)
num_lines -= 7 num_lines -= 8
line_idx += 7 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 # Parses a line of the form "foo: 42 kB" and returns an integer for the "42" field
def parse_smaps_size_line (self, line): def parse_smaps_size_line (self, line):
@ -66,13 +74,15 @@ class ProcSmaps:
return int(fields[1]) return int(fields[1])
class Mapping: 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.size = size
self.rss = rss self.rss = rss
self.shared_clean = shared_clean self.shared_clean = shared_clean
self.shared_dirty = shared_dirty self.shared_dirty = shared_dirty
self.private_clean = private_clean self.private_clean = private_clean
self.private_dirty = private_dirty self.private_dirty = private_dirty
self.referenced = referenced
self.permissions = permissions self.permissions = permissions
self.name = name self.name = name

View File

@ -6,7 +6,15 @@ pygtk.require('2.0')
import os import os
import sys import sys
from sugar import env from sugar import env
from sugar import util
sys.path.append(env.get_service_path('console')) 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 import console

View File

@ -1,29 +1,37 @@
servicedir = $(datadir)/dbus-1/services servicedir = $(datadir)/dbus-1/services
service_in_files = \ service_in_files = \
org.laptop.ActivityRegistry.service.in \
org.laptop.Clipboard.service.in \ org.laptop.Clipboard.service.in \
org.laptop.ObjectTypeRegistry.service.in org.laptop.ObjectTypeRegistry.service.in
service_DATA = \ service_DATA = \
org.laptop.ActivityRegistry.service \
org.laptop.Clipboard.service \ org.laptop.Clipboard.service \
org.laptop.ObjectTypeRegistry.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 org.laptop.Clipboard.service: org.laptop.Clipboard.service.in Makefile
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@ @sed -e "s|\@bindir\@|$(bindir)|" $< > $@
org.laptop.ObjectTypeRegistry.service: org.laptop.ObjectTypeRegistry.service.in Makefile org.laptop.ObjectTypeRegistry.service: org.laptop.ObjectTypeRegistry.service.in Makefile
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@ @sed -e "s|\@bindir\@|$(bindir)|" $< > $@
sugardir = $(pkgdatadir)/services/clipboard sugardir = $(pkgdatadir)/services/shell
sugar_PYTHON = \ sugar_PYTHON = \
__init__.py \ __init__.py \
clipboardobject.py \ activityregistryservice.py \
clipboardservice.py \ bundleregistry.py \
clipboardobject.py \
clipboardservice.py \
objecttypeservice.py objecttypeservice.py
bin_SCRIPTS = sugar-clipboard bin_SCRIPTS = sugar-shell-service
DISTCLEANFILES = $(service_DATA) DISTCLEANFILES = $(service_DATA)
EXTRA_DIST = $(service_in_files) $(bin_SCRIPTS) EXTRA_DIST = $(service_in_files) $(bin_SCRIPTS)

View File

@ -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

View File

@ -106,6 +106,13 @@ class BundleRegistry(gobject.GObject):
else: else:
return False 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(): def get_registry():
return _bundle_registry return _bundle_registry

View File

@ -19,9 +19,9 @@ import logging
import urlparse import urlparse
from sugar.objects import mime from sugar.objects import mime
from sugar import activity
import objecttypeservice import objecttypeservice
import bundleregistry
class ClipboardObject: class ClipboardObject:
@ -66,30 +66,15 @@ class ClipboardObject:
return '' return ''
def get_activity(self): 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() mime = self.get_mime_type()
if not mime: if not mime:
return '' return ''
"""
registry = activity.get_registry() registry = bundleregistry.get_registry()
activities = registry.get_activities_for_type(self.get_mime_type()) activities = registry.get_activities_for_type(self.get_mime_type())
# TODO: should we return several activities? # TODO: should we return several activities?
if activities: if activities:
return activities[0] return activities[0].get_service_name()
else:
return ''
"""
if mapping.has_key(mime):
return mapping[mime]
else: else:
return '' return ''
@ -101,8 +86,6 @@ class ClipboardObject:
def add_format(self, format): def add_format(self, format):
self._formats[format.get_type()] = 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): def get_formats(self):
return self._formats return self._formats

View File

@ -0,0 +1,4 @@
[D-BUS Service]
Name = org.laptop.ActivityRegistry
Exec = @bindir@/sugar-shell-service

View File

@ -1,4 +1,4 @@
[D-BUS Service] [D-BUS Service]
Name = org.laptop.Clipboard Name = org.laptop.Clipboard
Exec = @bindir@/sugar-clipboard Exec = @bindir@/sugar-shell-service

View File

@ -1,4 +1,4 @@
[D-BUS Service] [D-BUS Service]
Name = org.laptop.ObjectTypeRegistry Name = org.laptop.ObjectTypeRegistry
Exec = @bindir@/sugar-clipboard Exec = @bindir@/sugar-shell-service

View File

@ -23,28 +23,31 @@ import os
import logging import logging
from sugar import logger from sugar import logger
logger.start('clipboard') logger.start('shellservice')
import gobject import gobject
import dbus.glib import dbus.glib
from sugar import env from sugar import env
sys.path.append(env.get_service_path('clipboard')) sys.path.append(env.get_service_path('shell'))
import clipboardservice import clipboardservice
import objecttypeservice import objecttypeservice
import activityregistryservice
logging.info('Starting clipboard service.') logging.info('Starting shell service.')
gobject.threads_init() gobject.threads_init()
dbus.glib.threads_init() dbus.glib.threads_init()
clipboard_service = clipboardservice.get_instance() clipboard_service = clipboardservice.get_instance()
object_type_registry = objecttypeservice.get_instance() object_type_registry = objecttypeservice.get_instance()
activity_registry = activityregistryservice.get_instance()
loop = gobject.MainLoop() loop = gobject.MainLoop()
try: try:
loop.run() loop.run()
except KeyboardInterrupt: except KeyboardInterrupt:
print 'Ctrl+C pressed, exiting...' print 'Ctrl+C pressed, exiting...'

View File

@ -4,7 +4,6 @@ sugardir = $(pkgdatadir)/shell/model
sugar_PYTHON = \ sugar_PYTHON = \
__init__.py \ __init__.py \
accesspointmodel.py \ accesspointmodel.py \
bundleregistry.py \
BuddyModel.py \ BuddyModel.py \
Friends.py \ Friends.py \
Invites.py \ Invites.py \

View File

@ -18,29 +18,29 @@ import gobject
from sugar.graphics.xocolor import XoColor from sugar.graphics.xocolor import XoColor
from sugar.presence import presenceservice from sugar.presence import presenceservice
from sugar import activity
from model import bundleregistry
from model.BuddyModel import BuddyModel from model.BuddyModel import BuddyModel
from model.accesspointmodel import AccessPointModel from model.accesspointmodel import AccessPointModel
from hardware import hardwaremanager from hardware import hardwaremanager
from hardware import nmclient from hardware import nmclient
class ActivityModel: class ActivityModel:
def __init__(self, activity, bundle): def __init__(self, activity, activity_info):
self._activity = activity self._activity = activity
self._bundle = bundle self._activity_info = activity_info
def get_id(self): def get_id(self):
return self._activity.props.id return self._activity.props.id
def get_icon_name(self): def get_icon_name(self):
return self._bundle.get_icon() return self._activity_info.icon
def get_color(self): def get_color(self):
return XoColor(self._activity.props.color) return XoColor(self._activity.props.color)
def get_service_name(self): def get_service_name(self):
return self._bundle.get_service_name() return self._activity_info.service_name
def get_title(self): def get_title(self):
return self._activity.props.name return self._activity.props.name
@ -75,7 +75,6 @@ class MeshModel(gobject.GObject):
self._buddies = {} self._buddies = {}
self._access_points = {} self._access_points = {}
self._mesh = None self._mesh = None
self._bundle_registry = bundleregistry.get_registry()
self._pservice = presenceservice.get_instance() self._pservice = presenceservice.get_instance()
self._pservice.connect("activity-appeared", self._pservice.connect("activity-appeared",
@ -196,13 +195,14 @@ class MeshModel(gobject.GObject):
def _activity_appeared_cb(self, pservice, activity): def _activity_appeared_cb(self, pservice, activity):
self._check_activity(activity) self._check_activity(activity)
def _check_activity(self, activity): def _check_activity(self, presence_activity):
bundle = self._bundle_registry.get_bundle(activity.props.type) registry = activity.get_registry()
if not bundle: activity_info = registry.get_activity(presence_activity.props.type)
if not activity_info:
return return
if self.has_activity(activity.props.id): if self.has_activity(presence_activity.props.id):
return return
self.add_activity(bundle, activity) self.add_activity(activity_info, presence_activity)
def has_activity(self, activity_id): def has_activity(self, activity_id):
return self._activities.has_key(activity_id) return self._activities.has_key(activity_id)
@ -213,8 +213,8 @@ class MeshModel(gobject.GObject):
else: else:
return None return None
def add_activity(self, bundle, activity): def add_activity(self, activity_info, activity):
model = ActivityModel(activity, bundle) model = ActivityModel(activity, activity_info)
self._activities[model.get_id()] = model self._activities[model.get_id()] = model
self.emit('activity-added', model) self.emit('activity-added', model)

View File

@ -44,10 +44,10 @@ class HomeActivity(gobject.GObject):
gobject.PARAM_READWRITE), gobject.PARAM_READWRITE),
} }
def __init__(self, bundle, activity_id): def __init__(self, activity_info, activity_id):
"""Initialise the HomeActivity """Initialise the HomeActivity
bundle -- sugar.activity.bundle.Bundle instance, activity_info -- sugar.activity.registry.ActivityInfo instance,
provides the information required to actually provides the information required to actually
create the new instance. This is, in effect, create the new instance. This is, in effect,
the "type" of activity being created. the "type" of activity being created.
@ -61,7 +61,7 @@ class HomeActivity(gobject.GObject):
self._pid = None self._pid = None
self._service = None self._service = None
self._activity_id = activity_id self._activity_id = activity_id
self._bundle = bundle self._activity_info = activity_info
self._launch_time = time.time() self._launch_time = time.time()
self._launching = False self._launching = False
@ -99,9 +99,9 @@ class HomeActivity(gobject.GObject):
return self._window.get_name() return self._window.get_name()
def get_icon_name(self): def get_icon_name(self):
"""Retrieve the bundle's icon (file) name""" """Retrieve the activity's icon (file) name"""
if self._bundle: if self._activity_info:
return self._bundle.get_icon() return self._activity_info.icon
else: else:
return 'theme:stock-missing' return 'theme:stock-missing'
@ -156,9 +156,9 @@ class HomeActivity(gobject.GObject):
return self._window return self._window
def get_type(self): def get_type(self):
"""Retrieve bundle's "service_name" for future reference""" """Retrieve activity_info's "service_name" for future reference"""
if self._bundle: if self._activity_info:
return self._bundle.get_service_name() return self._activity_info.service_name
else: else:
return None return None

View File

@ -21,9 +21,9 @@ import wnck
import dbus import dbus
from sugar import wm from sugar import wm
from sugar import activity
from model.homeactivity import HomeActivity from model.homeactivity import HomeActivity
from model import bundleregistry
class HomeModel(gobject.GObject): class HomeModel(gobject.GObject):
"""Model of the "Home" view (activity management) """Model of the "Home" view (activity management)
@ -51,15 +51,18 @@ class HomeModel(gobject.GObject):
([gobject.TYPE_PYOBJECT])), ([gobject.TYPE_PYOBJECT])),
'active-activity-changed': (gobject.SIGNAL_RUN_FIRST, 'active-activity-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, 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): def __init__(self):
gobject.GObject.__init__(self) gobject.GObject.__init__(self)
self._activities = [] self._activities = []
self._bundle_registry = bundleregistry.get_registry() self._active_activity = None
self._current_activity = None self._pending_activity = None
screen = wnck.screen_get_default() screen = wnck.screen_get_default()
screen.connect('window-opened', self._window_opened_cb) screen.connect('window-opened', self._window_opened_cb)
@ -67,8 +70,55 @@ class HomeModel(gobject.GObject):
screen.connect('active-window-changed', screen.connect('active-window-changed',
self._active_window_changed_cb) self._active_window_changed_cb)
def get_current_activity(self): def get_pending_activity(self):
return self._current_activity """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): def __iter__(self):
return iter(self._activities) return iter(self._activities)
@ -84,65 +134,48 @@ class HomeModel(gobject.GObject):
def _window_opened_cb(self, screen, window): def _window_opened_cb(self, screen, window):
if window.get_window_type() == wnck.WINDOW_NORMAL: if window.get_window_type() == wnck.WINDOW_NORMAL:
activity = None home_activity = None
activity_id = wm.get_activity_id(window) activity_id = wm.get_activity_id(window)
bundle_id = wm.get_bundle_id(window) service_name = wm.get_bundle_id(window)
if bundle_id: if service_name:
bundle = self._bundle_registry.get_bundle(bundle_id) registry = activity.get_registry()
activity_info = registry.get_activity(service_name)
else: else:
bundle = None activity_info = None
if activity_id: if activity_id:
activity = self._get_activity_by_id(activity_id) home_activity = self._get_activity_by_id(activity_id)
if not activity: if not home_activity:
activity = HomeActivity(bundle, activity_id) home_activity = HomeActivity(activity_info, activity_id)
self._add_activity(activity) self._add_activity(home_activity)
activity.set_window(window) home_activity.set_window(window)
activity.props.launching = False home_activity.props.launching = False
self.emit('activity-started', activity) 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): def _window_closed_cb(self, screen, window):
if window.get_window_type() == wnck.WINDOW_NORMAL: if window.get_window_type() == wnck.WINDOW_NORMAL:
self._remove_activity_by_xid(window.get_xid()) 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): def _get_activity_by_xid(self, xid):
for activity in self._activities: for home_activity in self._activities:
if activity.get_xid() == xid: if home_activity.get_xid() == xid:
return activity return home_activity
return None return None
def _get_activity_by_id(self, activity_id): def _get_activity_by_id(self, activity_id):
for activity in self._activities: for home_activity in self._activities:
if activity.get_activity_id() == activity_id: if home_activity.get_activity_id() == activity_id:
return activity return home_activity
return None 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): def _set_active_success(self):
pass pass
@ -151,55 +184,58 @@ class HomeModel(gobject.GObject):
def _active_window_changed_cb(self, screen): def _active_window_changed_cb(self, screen):
window = screen.get_active_window() window = screen.get_active_window()
if window == None: if window is None or window.get_window_type() != wnck.WINDOW_NORMAL:
self.emit('active-activity-changed', None)
self._notify_activity_activation(self._current_activity, None)
return
if window.get_window_type() != wnck.WINDOW_NORMAL:
return return
xid = window.get_xid() xid = window.get_xid()
act = self._get_activity_by_xid(window.get_xid()) act = self._get_activity_by_xid(xid)
if act: if act is None:
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
logging.error('Model for window %d does not exist.' % xid) logging.error('Model for window %d does not exist.' % xid)
self._set_pending_activity(act)
self._set_active_activity(act)
self.emit('active-activity-changed', self._current_activity) def _add_activity(self, home_activity):
self._activities.append(home_activity)
self.emit('activity-added', home_activity)
def _add_activity(self, activity): def _remove_activity(self, home_activity):
self._activities.append(activity) if home_activity == self._active_activity:
self.emit('activity-added', 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)
def _remove_activity(self, activity): self.emit('activity-removed', home_activity)
if activity == self._current_activity: self._activities.remove(home_activity)
self._current_activity = None
self.emit('activity-removed', activity)
self._activities.remove(activity)
def _remove_activity_by_xid(self, xid): def _remove_activity_by_xid(self, xid):
activity = self._get_activity_by_xid(xid) home_activity = self._get_activity_by_xid(xid)
if activity: if home_activity:
self._remove_activity(activity) self._remove_activity(home_activity)
else: else:
logging.error('Model for window %d does not exist.' % xid) logging.error('Model for window %d does not exist.' % xid)
def notify_activity_launch(self, activity_id, service_name): def notify_activity_launch(self, activity_id, service_name):
bundle = self._bundle_registry.get_bundle(service_name) registry = activity.get_registry()
if not bundle: 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) raise ValueError("Activity service name '%s' was not found in the bundle registry." % service_name)
activity = HomeActivity(bundle, activity_id) home_activity = HomeActivity(activity_info, activity_id)
activity.props.launching = True home_activity.props.launching = True
self._add_activity(activity) self._add_activity(home_activity)
def notify_activity_launch_failed(self, activity_id): def notify_activity_launch_failed(self, activity_id):
activity = self._get_activity_by_id(activity_id) home_activity = self._get_activity_by_id(activity_id)
if activity: if home_activity:
logging.debug("Activity %s (%s) launch failed" % (activity_id, activity.get_type())) logging.debug("Activity %s (%s) launch failed" % (activity_id, home_activity.get_type()))
self._remove_activity(activity) self._remove_activity(home_activity)
else: else:
logging.error('Model for activity id %s does not exist.' % activity_id) logging.error('Model for activity id %s does not exist.' % activity_id)

View File

@ -17,10 +17,7 @@
"""D-bus service providing access to the shell's functionality""" """D-bus service providing access to the shell's functionality"""
import dbus import dbus
from model import bundleregistry
_DBUS_SERVICE = "org.laptop.Shell" _DBUS_SERVICE = "org.laptop.Shell"
_DBUS_ACTIVITY_REGISTRY_IFACE = "org.laptop.Shell.ActivityRegistry"
_DBUS_SHELL_IFACE = "org.laptop.Shell" _DBUS_SHELL_IFACE = "org.laptop.Shell"
_DBUS_OWNER_IFACE = "org.laptop.Shell.Owner" _DBUS_OWNER_IFACE = "org.laptop.Shell.Owner"
_DBUS_PATH = "/org/laptop/Shell" _DBUS_PATH = "/org/laptop/Shell"
@ -56,9 +53,6 @@ class ShellService(dbus.service.Object):
self._home_model.connect('active-activity-changed', self._home_model.connect('active-activity-changed',
self._cur_activity_changed_cb) self._cur_activity_changed_cb)
bundle_registry = bundleregistry.get_registry()
bundle_registry.connect('bundle-added', self._bundle_added_cb)
bus = dbus.SessionBus() bus = dbus.SessionBus()
bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus) bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus)
dbus.service.Object.__init__(self, bus_name, _DBUS_PATH) dbus.service.Object.__init__(self, bus_name, _DBUS_PATH)
@ -83,60 +77,6 @@ class ShellService(dbus.service.Object):
def NotifyLaunchFailure(self, activity_id): def NotifyLaunchFailure(self, activity_id):
self._shell.notify_launch_failure(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") @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s")
def ColorChanged(self, color): def ColorChanged(self, color):
pass pass
@ -169,12 +109,3 @@ class ShellService(dbus.service.Object):
if new_id: if new_id:
self.CurrentActivityChanged(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))

View File

@ -85,18 +85,19 @@ class BuddyMenu(Palette):
else: else:
menu_item = MenuItem(_('Make friend'), 'stock-add') menu_item = MenuItem(_('Make friend'), 'stock-add')
menu_item.connect('activate', self._make_friend_cb) menu_item.connect('activate', self._make_friend_cb)
self.append_menu_item(menu_item)
self.menu.append(menu_item)
menu_item.show() menu_item.show()
activity = shell_model.get_home().get_current_activity() activity = self._shell.get_current_activity()
if activity != None: 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 # FIXME check that the buddy is not in the activity already
menu_item = MenuItem(_('Invite'), 'stock-invite') menu_item = MenuItem(_('Invite'), 'stock-invite')
menu_item.connect('activate', self._invite_friend_cb) menu_item.connect('activate', self._invite_friend_cb)
self.append_menu_item(menu_item) self.menu.append(menu_item)
menu_item.show() menu_item.show()
def _buddy_icon_changed_cb(self, buddy): def _buddy_icon_changed_cb(self, buddy):

View File

@ -26,6 +26,7 @@ import gtk
import wnck import wnck
from sugar.activity.activityhandle import ActivityHandle from sugar.activity.activityhandle import ActivityHandle
from sugar import activity
from sugar.activity import activityfactory from sugar.activity import activityfactory
from sugar.datastore import datastore from sugar.datastore import datastore
from sugar import profile from sugar import profile
@ -34,7 +35,6 @@ from view.ActivityHost import ActivityHost
from view.frame.frame import Frame from view.frame.frame import Frame
from view.keyhandler import KeyHandler from view.keyhandler import KeyHandler
from view.home.HomeWindow import HomeWindow from view.home.HomeWindow import HomeWindow
from model import bundleregistry
from model.shellmodel import ShellModel from model.shellmodel import ShellModel
from hardware import hardwaremanager from hardware import hardwaremanager
@ -47,6 +47,7 @@ class Shell(gobject.GObject):
self._hosts = {} self._hosts = {}
self._screen = wnck.screen_get_default() self._screen = wnck.screen_get_default()
self._current_host = None self._current_host = None
self._pending_host = None
self._screen_rotation = 0 self._screen_rotation = 0
self._key_handler = KeyHandler(self) 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('activity-removed', self._activity_removed_cb)
home_model.connect('active-activity-changed', home_model.connect('active-activity-changed',
self._active_activity_changed_cb) self._active_activity_changed_cb)
home_model.connect('pending-activity-changed',
self._pending_activity_changed_cb)
# Unfreeze the display when it's stable # Unfreeze the display when it's stable
hw_manager = hardwaremanager.get_manager() hw_manager = hardwaremanager.get_manager()
@ -100,6 +103,12 @@ class Shell(gobject.GObject):
self._current_host = host 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): def get_model(self):
return self._model return self._model
@ -107,16 +116,16 @@ class Shell(gobject.GObject):
return self._frame return self._frame
def join_activity(self, bundle_id, activity_id): def join_activity(self, bundle_id, activity_id):
activity = self.get_activity(activity_id) activity_host = self.get_activity(activity_id)
if activity: if activity_host:
activity.present() activity_host.present()
return return
# Get the service name for this activity, if # Get the service name for this activity, if
# we have a bundle on the system capable of handling # we have a bundle on the system capable of handling
# this activity type # this activity type
breg = bundleregistry.get_registry() registry = activity.get_registry()
bundle = breg.get_bundle(bundle_id) bundle = registry.get_activity(bundle_id)
if not bundle: if not bundle:
logging.error("Couldn't find activity for type %s" % bundle_id) logging.error("Couldn't find activity for type %s" % bundle_id)
return return
@ -156,6 +165,8 @@ class Shell(gobject.GObject):
return return
if level == ShellModel.ZOOM_ACTIVITY: if level == ShellModel.ZOOM_ACTIVITY:
if self._pending_host is not None:
self._pending_host.present()
self._screen.toggle_showing_desktop(False) self._screen.toggle_showing_desktop(False)
else: else:
self._model.set_zoom_level(level) self._model.set_zoom_level(level)

View File

@ -73,9 +73,9 @@ class ClipboardIcon(CanvasIcon):
self._selected = selected self._selected = selected
if selected: if selected:
if not self._hover: 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: 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): def set_state(self, name, percent, icon_name, preview, activity):
cb_service = clipboardservice.get_instance() cb_service = clipboardservice.get_instance()
@ -107,11 +107,11 @@ class ClipboardIcon(CanvasIcon):
def prelight(self, enter): def prelight(self, enter):
if enter: if enter:
self._hover = True self._hover = True
self.props.background_color = color.BLACK.get_int() self.props.background_color = style.COLOR_BLACK.get_int()
else: else:
self._hover = False self._hover = False
if self._selected: if self._selected:
self.props.background_color = color.DESKTOP_BACKGROUND.get_int() self.props.background_color = style.COLOR_SELECTION_GREY.get_int()
else: else:
self.props.background_color = color.TOOLBAR_BACKGROUND.get_int() self.props.background_color = style.COLOR_PANEL_GREY.get_int()

View File

@ -64,11 +64,13 @@ class ClipboardMenu(Palette):
self._remove_item = MenuItem(_('Remove'), 'stock-remove') self._remove_item = MenuItem(_('Remove'), 'stock-remove')
self._remove_item.connect('activate', self._remove_item_activate_cb) 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 = MenuItem(_('Open'), 'stock-keep')
self._open_item.connect('activate', self._open_item_activate_cb) 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') #self._stop_item = MenuItem(_('Stop download'), 'stock-close')
# TODO: Implement stopping downloads # TODO: Implement stopping downloads
@ -77,7 +79,8 @@ class ClipboardMenu(Palette):
self._journal_item = MenuItem(_('Add to journal'), 'document-save') self._journal_item = MenuItem(_('Add to journal'), 'document-save')
self._journal_item.connect('activate', self._journal_item_activate_cb) 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) self._update_items_visibility(installable)
@ -120,32 +123,8 @@ class ClipboardMenu(Palette):
def _open_item_activate_cb(self, menu_item): def _open_item_activate_cb(self, menu_item):
if self._percent < 100: if self._percent < 100:
return return
jobject = self._copy_to_journal() jobject = self._copy_to_journal()
# TODO: we cannot simply call resume() right now because we would lock jobject.resume()
# 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)
def _remove_item_activate_cb(self, menu_item): def _remove_item_activate_cb(self, menu_item):
cb_service = clipboardservice.get_instance() cb_service = clipboardservice.get_instance()

View File

@ -20,30 +20,33 @@ import logging
from sugar.graphics.palette import Palette from sugar.graphics.palette import Palette
from sugar.graphics.xocolor import XoColor from sugar.graphics.xocolor import XoColor
from sugar.graphics.iconbutton import IconButton from sugar.graphics.iconbutton import IconButton
from sugar.graphics import style
from sugar import profile from sugar import profile
from sugar import activity
from model import bundleregistry
from frameinvoker import FrameCanvasInvoker from frameinvoker import FrameCanvasInvoker
class ActivityButton(IconButton): class ActivityButton(IconButton):
def __init__(self, activity): def __init__(self, activity_info):
IconButton.__init__(self, icon_name=activity.get_icon()) 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.props.invoker = FrameCanvasInvoker(self)
palette.set_group_id('frame') palette.set_group_id('frame')
self.set_palette(palette) self.set_palette(palette)
self._activity = activity self._activity_info = activity_info
def get_bundle_id(self): def get_bundle_id(self):
return self._activity.get_service_name() return self._activity_info.service_name
class InviteButton(IconButton): class InviteButton(IconButton):
def __init__(self, activity, invite): def __init__(self, activity_model, invite):
IconButton.__init__(self, icon_name=activity.get_icon()) 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 self._invite = invite
def get_activity_id(self): def get_activity_id(self):
@ -64,12 +67,12 @@ class ActivitiesBox(hippo.CanvasBox):
self._invite_to_item = {} self._invite_to_item = {}
self._invites = self._shell_model.get_invites() self._invites = self._shell_model.get_invites()
bundle_registry = bundleregistry.get_registry() registry = activity.get_registry()
for bundle in bundle_registry: for activity_info in registry.get_activities():
if bundle.get_show_launcher(): if activity_info.show_launcher:
self.add_activity(bundle) 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: for invite in self._invites:
self.add_invite(invite) self.add_invite(invite)
@ -90,19 +93,19 @@ class ActivitiesBox(hippo.CanvasBox):
def _invite_removed_cb(self, invites, invite): def _invite_removed_cb(self, invites, invite):
self.remove_invite(invite) self.remove_invite(invite)
def _bundle_added_cb(self, bundle_registry, bundle): def _activity_added_cb(self, activity_registry, activity_info):
self.add_activity(bundle) self.add_activity(activity_info)
def add_activity(self, activity): def add_activity(self, activity_info):
item = ActivityButton(activity) item = ActivityButton(activity_info)
item.connect('activated', self._activity_clicked_cb) item.connect('activated', self._activity_clicked_cb)
self.append(item, 0) self.append(item, 0)
def add_invite(self, invite): def add_invite(self, invite):
mesh = self._shell_model.get_mesh() 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: if activity:
item = InviteButton(activity, invite) item = InviteButton(activity_model, invite)
item.connect('activated', self._invite_clicked_cb) item.connect('activated', self._invite_clicked_cb)
self.append(item, 0) self.append(item, 0)

View File

@ -20,8 +20,8 @@ import gobject
from sugar.graphics.canvasicon import CanvasIcon from sugar.graphics.canvasicon import CanvasIcon
from sugar.graphics import style from sugar.graphics import style
from sugar.presence import presenceservice from sugar.presence import presenceservice
from sugar import activity
from model import bundleregistry
from view.BuddyIcon import BuddyIcon from view.BuddyIcon import BuddyIcon
class FriendView(hippo.CanvasBox): class FriendView(hippo.CanvasBox):
@ -46,9 +46,9 @@ class FriendView(hippo.CanvasBox):
self._buddy.connect('disappeared', self._buddy_disappeared_cb) self._buddy.connect('disappeared', self._buddy_disappeared_cb)
self._buddy.connect('color-changed', self._buddy_color_changed_cb) self._buddy.connect('color-changed', self._buddy_color_changed_cb)
def _get_new_icon_name(self, activity): def _get_new_icon_name(self, home_activity):
registry = bundleregistry.get_registry() registry = activity.get_registry()
bundle = registry.get_bundle(activity.get_type()) bundle = registry.get_bundle(home_activity.get_type())
if bundle: if bundle:
return bundle.get_icon() return bundle.get_icon()
return None return None
@ -58,14 +58,14 @@ class FriendView(hippo.CanvasBox):
self.remove(self._activity_icon) self.remove(self._activity_icon)
self._activity_icon_visible = False self._activity_icon_visible = False
def _buddy_activity_changed_cb(self, buddy, activity=None): def _buddy_activity_changed_cb(self, buddy, home_activity=None):
if not activity: if not home_activity:
self._remove_activity_icon() self._remove_activity_icon()
return return
# FIXME: use some sort of "unknown activity" icon rather # FIXME: use some sort of "unknown activity" icon rather
# than hiding the icon? # than hiding the icon?
name = self._get_new_icon_name(activity) name = self._get_new_icon_name(home_activity)
if name: if name:
self._activity_icon.props.icon_name = name self._activity_icon.props.icon_name = name
self._activity_icon.props.xo_color = buddy.get_color() self._activity_icon.props.xo_color = buddy.get_color()
@ -76,8 +76,8 @@ class FriendView(hippo.CanvasBox):
self._remove_activity_icon() self._remove_activity_icon()
def _buddy_appeared_cb(self, buddy): def _buddy_appeared_cb(self, buddy):
activity = self._buddy.get_current_activity() home_activity = self._buddy.get_current_activity()
self._buddy_activity_changed_cb(buddy, activity) self._buddy_activity_changed_cb(buddy, home_activity)
def _buddy_disappeared_cb(self, buddy): def _buddy_disappeared_cb(self, buddy):
self._buddy_activity_changed_cb(buddy, None) self._buddy_activity_changed_cb(buddy, None)

View File

@ -130,7 +130,7 @@ class HomeMyIcon(MyIcon):
shutdown_menu_item = gtk.MenuItem(_('Shutdown')) shutdown_menu_item = gtk.MenuItem(_('Shutdown'))
shutdown_menu_item.connect('activate', self._shutdown_activate_cb) 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() shutdown_menu_item.show()
self.set_palette(palette) self.set_palette(palette)

View File

@ -88,14 +88,14 @@ class ActivityIcon(CanvasIcon):
resume_menu_item = gtk.MenuItem(_('Resume')) resume_menu_item = gtk.MenuItem(_('Resume'))
resume_menu_item.connect('activate', self._resume_activate_cb) 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() resume_menu_item.show()
# FIXME: kludge # FIXME: kludge
if self._activity.get_type() != "org.laptop.JournalActivity": if self._activity.get_type() != "org.laptop.JournalActivity":
stop_menu_item = gtk.MenuItem(_('Stop')) stop_menu_item = gtk.MenuItem(_('Stop'))
stop_menu_item.connect('activate', self._stop_activate_cb) 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() stop_menu_item.show()
def _launching_changed_cb(self, activity, pspec): 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 = shell.get_model().get_home()
self._model.connect('activity-added', self._activity_added_cb) self._model.connect('activity-added', self._activity_added_cb)
self._model.connect('activity-removed', self._activity_removed_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) self.connect('button-release-event', self._button_release_event_cb)
@ -385,7 +385,7 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem):
cr.fill() cr.fill()
# Selected Wedge # Selected Wedge
current_activity = self._model.get_current_activity() current_activity = self._model.get_pending_activity()
if current_activity is not None: if current_activity is not None:
selected_index = self._model.index(current_activity) selected_index = self._model.index(current_activity)
[angle_start, angle_end] = self._get_angles(selected_index) [angle_start, angle_end] = self._get_angles(selected_index)

View File

@ -12,21 +12,19 @@ sugar_PYTHON = \
util.py \ util.py \
wm.py wm.py
INCLUDES = \
$(LIB_CFLAGS) \
$(LIB_BINDINGS_CFLAGS) \
$(PYTHON_INCLUDES) \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/lib/ui
pkgpyexecdir = $(pythondir)/sugar pkgpyexecdir = $(pythondir)/sugar
pkgpyexec_LTLIBRARIES = _sugarext.la _sugaruiext.la 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_LDFLAGS = -module -avoid-version
_sugarext_la_LIBADD = \ _sugarext_la_LIBADD = \
$(LIB_BINDINGS_LIBS) \ $(LIB_BINDINGS_LIBS) \
$(LIB_LIBS) \
$(top_builddir)/lib/libsugar.la $(top_builddir)/lib/libsugar.la
_sugarext_la_SOURCES = \ _sugarext_la_SOURCES = \
@ -36,10 +34,16 @@ nodist__sugarext_la_SOURCES = _sugarext.c
_sugarext.c: _sugarext.defs _sugarext.override _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_LDFLAGS = -module -avoid-version
_sugaruiext_la_LIBADD = \ _sugaruiext_la_LIBADD = \
$(LIB_BINDINGS_LIBS) \ $(LIBUI_BINDINGS_LIBS) \
$(LIB_LIBS) \ $(LIBUI_LIBS) \
$(top_builddir)/lib/ui/libsugarui.la $(top_builddir)/lib/ui/libsugarui.la
_sugaruiext_la_SOURCES = \ _sugaruiext_la_SOURCES = \

View File

@ -26,19 +26,27 @@
;; From sugar-menu.h ;; From sugar-menu.h
(define-method popup (define-method set_active
(of-object "SugarMenu") (of-object "SugarMenu")
(c-name "sugar_menu_popup") (c-name "sugar_menu_set_active")
(return-type "none") (return-type "none")
(parameters (parameters
'("gint" "x") '("gboolean" "active")
'("gint" "y")
) )
) )
(define-method popdown (define-method embed
(of-object "SugarMenu") (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") (return-type "none")
) )

View File

@ -18,6 +18,7 @@ modulename _sugarext
import gobject.GObject as PyGObject_Type import gobject.GObject as PyGObject_Type
import gtk.Entry as PyGtkEntry_Type import gtk.Entry as PyGtkEntry_Type
import gtk.Menu as PyGtkMenu_Type import gtk.Menu as PyGtkMenu_Type
import gtk.Container as PyGtkContainer_Type
import gtk.gdk.Window as PyGdkWindow_Type import gtk.gdk.Window as PyGdkWindow_Type
%% %%
ignore-glob ignore-glob

View File

@ -114,7 +114,7 @@ class ActivityCreationHandler(gobject.GObject):
self._factory.create(self._activity_handle.get_dict(), self._factory.create(self._activity_handle.get_dict(),
timeout=120 * 1000, timeout=120 * 1000,
reply_handler=self._no_reply_handler, reply_handler=self._create_reply_handler,
error_handler=self._create_error_handler) error_handler=self._create_error_handler)
def get_activity_id(self): def get_activity_id(self):
@ -137,7 +137,10 @@ class ActivityCreationHandler(gobject.GObject):
def _activate_error_handler(self, err): def _activate_error_handler(self, err):
logging.debug("Activity activation request failed %s" % 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)." % logging.debug("Activity created %s (%s)." %
(self._activity_handle.activity_id, self._service_name)) (self._activity_handle.activity_id, self._service_name))

View File

@ -41,6 +41,7 @@ class NotInstalledException(Exception): pass
class InvalidPathException(Exception): pass class InvalidPathException(Exception): pass
class ZipExtractException(Exception): pass class ZipExtractException(Exception): pass
class RegistrationException(Exception): pass class RegistrationException(Exception): pass
class MalformedBundleException(Exception): pass
class Bundle: class Bundle:
"""Metadata description of a given application/activity """Metadata description of a given application/activity
@ -265,10 +266,12 @@ class Bundle:
if not bundle_root_dir: if not bundle_root_dir:
bundle_root_dir = file_name.split('/')[0] bundle_root_dir = file_name.split('/')[0]
if not bundle_root_dir.endswith('.activity'): if not bundle_root_dir.endswith('.activity'):
raise 'Incorrect bundle.' raise MalformedBundleException(
'The activity directory name must end with .activity')
else: else:
if not file_name.startswith(bundle_root_dir): 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 return bundle_root_dir
@ -294,10 +297,7 @@ class Bundle:
self._init_with_path(bundle_path) self._init_with_path(bundle_path)
bus = dbus.SessionBus() if not activity.get_registry().add_bundle(bundle_path):
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):
raise RegistrationException raise RegistrationException
def deinstall(self): def deinstall(self):

View File

@ -162,8 +162,7 @@ def _get_mo_list(manifest):
for lang in _get_po_list(manifest).keys(): for lang in _get_po_list(manifest).keys():
filename = _get_service_name() + '.mo' filename = _get_service_name() + '.mo'
mo_list.append(os.path.join(_get_source_path(), 'locale', mo_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename))
lang, 'LC_MESSAGES', filename))
return mo_list return mo_list

View File

@ -1,4 +1,5 @@
# Copyright (C) 2006-2007 Red Hat, Inc. # 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 # This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public # modify it under the terms of the GNU Lesser General Public
@ -18,29 +19,39 @@
import logging import logging
import dbus import dbus
import gobject
_SHELL_SERVICE = "org.laptop.Shell" _ACTIVITY_REGISTRY_SERVICE_NAME = 'org.laptop.ActivityRegistry'
_SHELL_PATH = "/org/laptop/Shell" _ACTIVITY_REGISTRY_IFACE = 'org.laptop.ActivityRegistry'
_REGISTRY_IFACE = "org.laptop.Shell.ActivityRegistry" _ACTIVITY_REGISTRY_PATH = '/org/laptop/ActivityRegistry'
def _activity_info_from_dict(info_dict): def _activity_info_from_dict(info_dict):
if not info_dict: if not info_dict:
return None return None
return ActivityInfo(info_dict['name'], info_dict['icon'], 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): 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.name = name
self.icon = icon self.icon = icon
self.service_name = service_name self.service_name = service_name
self.path = path 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): def __init__(self):
gobject.GObject.__init__(self)
bus = dbus.SessionBus() bus = dbus.SessionBus()
bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH) bus_object = bus.get_object(_ACTIVITY_REGISTRY_SERVICE_NAME,
self._registry = dbus.Interface(bus_object, _REGISTRY_IFACE) _ACTIVITY_REGISTRY_PATH)
self._registry = dbus.Interface(bus_object, _ACTIVITY_REGISTRY_IFACE)
self._registry.connect_to_signal('ActivityAdded', self._activity_added_cb) self._registry.connect_to_signal('ActivityAdded', self._activity_added_cb)
# Two caches fo saving some travel across dbus. # Two caches fo saving some travel across dbus.
@ -55,6 +66,10 @@ class ActivityRegistry(object):
return result return result
def get_activities(self):
info_list = self._registry.GetActivities()
return self._convert_info_list(info_list)
def get_activity(self, service_name): def get_activity(self, service_name):
if self._service_name_to_activity_info.has_key(service_name): if self._service_name_to_activity_info.has_key(service_name):
return self._service_name_to_activity_info[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 self._mime_type_to_activities[mime_type] = activities
return 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') logging.debug('ActivityRegistry._activity_added_cb: flushing caches')
self._service_name_to_activity_info.clear() self._service_name_to_activity_info.clear()
self._mime_type_to_activities.clear() self._mime_type_to_activities.clear()
self.emit('activity-added', _activity_info_from_dict(info_dict))
_registry = None _registry = None

View File

@ -231,13 +231,13 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
stroke_color = None stroke_color = None
if self._active: if self._active:
if self._fill_color: if self._fill_color:
fill_color = self._fill_color.get_html() fill_color = self._fill_color.get_svg()
if self._stroke_color: if self._stroke_color:
stroke_color = self._stroke_color.get_html() stroke_color = self._stroke_color.get_svg()
else: else:
stroke_color = color.ICON_STROKE_INACTIVE.get_html() stroke_color = color.ICON_STROKE_INACTIVE.get_svg()
if self._fill_color: if self._fill_color:
fill_color = self._fill_color.get_html() fill_color = self._fill_color.get_svg()
return [fill_color, stroke_color] return [fill_color, stroke_color]
def _get_handle(self, name, handle): def _get_handle(self, name, handle):

View File

@ -53,7 +53,7 @@ class IconButton(CanvasIcon, hippo.CanvasItem):
if self.props.active: if self.props.active:
self.props.background_color = 0x000000FF self.props.background_color = 0x000000FF
else: else:
self.props.background_color = 0x404040FF self.props.background_color = 0x00000000
def _icon_clicked_cb(self, button): def _icon_clicked_cb(self, button):
if self._palette: if self._palette:

View File

@ -21,12 +21,12 @@ import time
import gtk import gtk
import hippo import hippo
from sugar.graphics.frame import Frame
from sugar.activity.bundle import Bundle from sugar.activity.bundle import Bundle
from sugar.date import Date from sugar.date import Date
from sugar.graphics import style from sugar.graphics import style
from sugar.graphics.canvasicon import CanvasIcon from sugar.graphics.canvasicon import CanvasIcon
from sugar.graphics.xocolor import XoColor from sugar.graphics.xocolor import XoColor
from sugar.graphics.canvasroundbox import CanvasRoundBox
from sugar.datastore import datastore from sugar.datastore import datastore
from sugar import activity from sugar import activity
from sugar.objects import objecttype from sugar.objects import objecttype
@ -95,12 +95,12 @@ class ObjectChooser(gtk.Dialog):
else: else:
return None return None
class CollapsedEntry(Frame): class CollapsedEntry(CanvasRoundBox):
_DATE_COL_WIDTH = style.zoom(100) _DATE_COL_WIDTH = style.zoom(100)
_BUDDIES_COL_WIDTH = style.zoom(50) _BUDDIES_COL_WIDTH = style.zoom(50)
def __init__(self, jobject): def __init__(self, jobject):
Frame.__init__(self) CanvasRoundBox.__init__(self)
self.props.box_height = style.zoom(75) self.props.box_height = style.zoom(75)
self.props.spacing = style.DEFAULT_SPACING self.props.spacing = style.DEFAULT_SPACING
self.props.border_color = style.COLOR_BLACK.get_int() self.props.border_color = style.COLOR_BLACK.get_int()

View File

@ -36,7 +36,40 @@ _RIGHT_TOP = 5
_TOP_LEFT = 6 _TOP_LEFT = 6
_TOP_RIGHT = 7 _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 DEFAULT = 0
AT_CURSOR = 1 AT_CURSOR = 1
AROUND = 2 AROUND = 2
@ -54,7 +87,9 @@ class Palette(gobject.GObject):
'invoker' : (object, None, None, 'invoker' : (object, None, None,
gobject.PARAM_READWRITE), gobject.PARAM_READWRITE),
'position' : (gobject.TYPE_INT, None, None, 0, 6, '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__ = { __gsignals__ = {
@ -65,16 +100,21 @@ class Palette(gobject.GObject):
} }
def __init__(self, label, accel_path=None): 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._full_request = [0, 0]
self._cursor_x = 0 self._cursor_x = 0
self._cursor_y = 0 self._cursor_y = 0
self._state = self._SECONDARY self._state = self._PRIMARY
self._invoker = None self._invoker = None
self._group_id = None self._group_id = None
self._up = False self._up = False
self._position = self.DEFAULT self._position = self.DEFAULT
self._draw_gap = False
self._palette_popup_sid = None self._palette_popup_sid = None
self._popup_anim = animator.Animator(0.3, 10) 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 = animator.Animator(0.6, 10)
self._popdown_anim.add(_PopdownAnimation(self)) 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._label = gtk.Label()
self._menu.append(self._primary) vbox.pack_start(self._label, False)
self._primary.show()
self._separator = gtk.SeparatorMenuItem() self._secondary_box = gtk.VBox()
self._menu.append(self._separator) vbox.pack_start(self._secondary_box)
self._content = _ContentMenuItem() self._separator = gtk.HSeparator()
self._menu.append(self._content) self._secondary_box.pack_start(self._separator)
self._button_bar = _ButtonBarMenuItem() self._menu_box = gtk.VBox()
self._menu.append(self._button_bar) self._secondary_box.pack_start(self._menu_box)
self._menu_box.show()
self._menu.connect('enter-notify-event', self._content = gtk.VBox()
self._enter_notify_event_cb) self._secondary_box.pack_start(self._content)
self._menu.connect('leave-notify-event', self._content.show()
self._leave_notify_event_cb)
self.action_bar = PaletteActionBar()
self._secondary_box.pack_start(self.action_bar)
self.action_bar.show()
self.add(vbox)
vbox.show()
self.menu = _Menu(self)
self.menu.show()
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 is_up(self): def is_up(self):
return self._up return self._up
def get_rect(self):
win_x, win_y = self.window.get_origin()
rectangle = self.get_allocation()
x = win_x + rectangle.x
y = win_y + rectangle.y
width = rectangle.width
height = rectangle.height
return gtk.gdk.Rectangle(x, y, width, height)
def set_primary_text(self, label, accel_path=None): def set_primary_text(self, label, accel_path=None):
self._primary.set_label(label, accel_path) self._label.set_text(label)
self._label.show()
def append_menu_item(self, item):
self._separator.show()
self._menu.insert(item, len(self._menu.get_children()) - 2)
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)
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 menu_item_count(self):
return len(self._menu.get_children()) - 4
def set_content(self, widget): def set_content(self, widget):
self._content.set_widget(widget) if len(self._content.get_children()) > 0:
self._content.show() self.remove(self._content.get_children()[0])
def append_button(self, button): if widget is not None:
self._button_bar.append_button(button) self._content.add(widget)
self._button_bar.show()
self._update_accept_focus()
self._update_separator()
def set_group_id(self, group_id): def set_group_id(self, group_id):
if self._group_id: if self._group_id:
@ -154,9 +204,11 @@ class Palette(gobject.GObject):
self._invoker = value self._invoker = value
self._invoker.connect('mouse-enter', self._invoker_mouse_enter_cb) self._invoker.connect('mouse-enter', self._invoker_mouse_enter_cb)
self._invoker.connect('mouse-leave', self._invoker_mouse_leave_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': elif pspec.name == 'position':
self._position = value self._position = value
elif pspec.name == 'draw-gap':
self._draw_gap = value
self.queue_draw()
else: else:
raise AssertionError raise AssertionError
@ -165,9 +217,57 @@ class Palette(gobject.GObject):
return self._invoker return self._invoker
elif pspec.name == 'position': elif pspec.name == 'position':
return self._position return self._position
elif pspec.name == 'draw-gap':
return self._draw_gap
else: else:
raise AssertionError 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): def _in_screen(self, x, y):
[width, height] = self._full_request [width, height] = self._full_request
screen_area = self._invoker.get_screen_area() screen_area = self._invoker.get_screen_area()
@ -182,7 +282,7 @@ class Palette(gobject.GObject):
if inv_rect == None: if inv_rect == None:
inv_rect = self._invoker.get_rect() 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 + \ x = inv_rect.x + inv_rect.width * invoker_halign + \
palette_width * palette_halign palette_width * palette_halign
@ -241,12 +341,12 @@ class Palette(gobject.GObject):
def _update_full_request(self): def _update_full_request(self):
state = self._state state = self._state
self._menu.set_size_request(-1, -1) self.set_size_request(-1, -1)
self._set_state(self._SECONDARY) 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) self._set_state(state)
@ -282,7 +382,7 @@ class Palette(gobject.GObject):
elif position == self.TOP: elif position == self.TOP:
x, y = self._get_top_position() x, y = self._get_top_position()
self._menu.popup(x, y) self.move(x, y)
def _show(self): def _show(self):
if self._up: if self._up:
@ -291,11 +391,12 @@ class Palette(gobject.GObject):
self._update_cursor_position() self._update_cursor_position()
self._update_full_request() self._update_full_request()
self._invoker.connect_to_parent() self._palette_popup_sid = _palette_observer.connect(
self._palette_popup_sid = _palette_observer.connect('popup', 'popup', self._palette_observer_popup_cb)
self._palette_observer_popup_cb)
self._update_position() self._update_position()
self.menu.set_active(True)
self.show()
self._up = True self._up = True
_palette_observer.emit('popup', self) _palette_observer.emit('popup', self)
@ -305,7 +406,8 @@ class Palette(gobject.GObject):
if not self._palette_popup_sid is None: if not self._palette_popup_sid is None:
_palette_observer.disconnect(self._palette_popup_sid) _palette_observer.disconnect(self._palette_popup_sid)
self._palette_popup_sid = None self._palette_popup_sid = None
self._menu.popdown() self.menu.set_active(False)
self.hide()
self._up = False self._up = False
self.emit('popdown') self.emit('popdown')
@ -329,25 +431,11 @@ class Palette(gobject.GObject):
return return
if state == self._PRIMARY: if state == self._PRIMARY:
self._primary.show() self.menu.unembed()
for menu_item in self._menu.get_children()[1:]: self._secondary_box.hide()
menu_item.hide()
elif state == self._SECONDARY: elif state == self._SECONDARY:
middle_menu_items = self._menu.get_children() self.menu.embed(self._menu_box)
middle_menu_items = middle_menu_items[2:len(middle_menu_items) - 2] self._secondary_box.show()
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._state = state self._state = state
@ -357,68 +445,54 @@ class Palette(gobject.GObject):
def _invoker_mouse_leave_cb(self, invoker): def _invoker_mouse_leave_cb(self, invoker):
self.popdown() self.popdown()
def _invoker_focus_out_cb(self, invoker):
self._hide()
def _enter_notify_event_cb(self, widget, event): 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._popdown_anim.stop()
self._secondary_anim.start() self._secondary_anim.start()
def _leave_notify_event_cb(self, widget, event): def _leave_notify_event_cb(self, widget, event):
if event.detail == gtk.gdk.NOTIFY_NONLINEAR: if event.detail != gtk.gdk.NOTIFY_INFERIOR:
self.popdown() self.popdown()
def _palette_observer_popup_cb(self, observer, palette): def _palette_observer_popup_cb(self, observer, palette):
if self != palette: if self != palette:
self._hide() self._hide()
class _PrimaryMenuItem(gtk.MenuItem): class PaletteActionBar(gtk.HButtonBox):
def __init__(self, label, accel_path): def add_action(label, icon_name=None):
gtk.MenuItem.__init__(self) button = Button(label)
self.set_border_width(style.DEFAULT_PADDING)
self._set_label(label, accel_path)
def set_label(self, label, accel_path): if icon_name:
self.remove(self._label) icon = Icon(icon_name)
self._set_label(label, accel_path) button.set_image(icon)
icon.show()
def _set_label(self, label, accel_path): self.pack_start(button)
self._label = gtk.AccelLabel(label) button.show()
self._label.set_accel_widget(self)
if accel_path: class _Menu(_sugaruiext.Menu):
self.set_accel_path(accel_path) __gtype_name__ = 'SugarPaletteMenu'
self._label.set_alignment(0.0, 0.5)
self.add(self._label) def __init__(self, palette):
self._label.show() _sugaruiext.Menu.__init__(self)
self._palette = palette
class _ContentMenuItem(gtk.MenuItem): def do_insert(self, item, position):
def __init__(self): _sugaruiext.Menu.do_insert(self, item, position)
gtk.MenuItem.__init__(self) self._palette._update_separator()
def set_widget(self, widget): def do_expose_event(self, event):
if self.child: # Ignore the Menu expose, just do the MenuShell expose to prevent any
self.remove(self.child) # border from being drawn here. A border is drawn by the palette object
self.add(widget) # around everything.
gtk.MenuShell.do_expose_event(self, event)
def is_empty(self): def do_grab_notify(self, was_grabbed):
return self.child is None or not self.child.props.visible # Ignore grab_notify as the menu would close otherwise
pass
class _ButtonBarMenuItem(gtk.MenuItem): def do_deactivate(self):
def __init__(self): self._palette._hide()
gtk.MenuItem.__init__(self)
self._hbar = gtk.HButtonBox()
self.add(self._hbar)
self._hbar.show()
def append_button(self, button):
self._hbar.pack_start(button)
def is_empty(self):
return len(self._hbar.get_children()) == 0
class _PopupAnimation(animator.Animation): class _PopupAnimation(animator.Animation):
def __init__(self, palette): def __init__(self, palette):
@ -469,13 +543,6 @@ class Invoker(gobject.GObject):
height = gtk.gdk.screen_height() height = gtk.gdk.screen_height()
return gtk.gdk.Rectangle(0, 0, width, 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): class WidgetInvoker(Invoker):
def __init__(self, widget): def __init__(self, widget):
Invoker.__init__(self) Invoker.__init__(self)
@ -495,6 +562,37 @@ class WidgetInvoker(Invoker):
return gtk.gdk.Rectangle(x, y, width, height) 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): def _enter_notify_event_cb(self, widget, event):
self.emit('mouse-enter') self.emit('mouse-enter')

View File

@ -22,6 +22,8 @@ from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, WidgetInvoker from sugar.graphics.palette import Palette, WidgetInvoker
class RadioToolButton(gtk.RadioToolButton): class RadioToolButton(gtk.RadioToolButton):
__gtype_name__ = "SugarRadioToolButton"
def __init__(self, named_icon=None, group=None): def __init__(self, named_icon=None, group=None):
gtk.RadioToolButton.__init__(self, group=group) gtk.RadioToolButton.__init__(self, group=group)
self._palette = None self._palette = None
@ -38,9 +40,25 @@ class RadioToolButton(gtk.RadioToolButton):
def set_palette(self, palette): def set_palette(self, palette):
self._palette = palette self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child) 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): def set_tooltip(self, text):
self._palette = Palette(text) self._palette = Palette(text)
self._palette.props.invoker = WidgetInvoker(self.child) 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) palette = property(get_palette, set_palette)

View File

@ -81,6 +81,12 @@ class Color(object):
return (r, g, b) return (r, g, b)
def get_svg(self):
if self._a == 0.0:
return 'none'
else:
return self.get_html()
_XO_DPI = 200.0 _XO_DPI = 200.0
_FOCUS_LINE_WIDTH = 2 _FOCUS_LINE_WIDTH = 2
@ -113,6 +119,7 @@ TOOLBOX_TAB_LABEL_WIDTH = zoom(150 - 15 * 2)
COLOR_BLACK = Color('#000000') COLOR_BLACK = Color('#000000')
COLOR_WHITE = Color('#FFFFFF') COLOR_WHITE = Color('#FFFFFF')
COLOR_TRANSPARENT = Color('#FFFFFF', alpha=0.0)
COLOR_PANEL_GREY = Color('#C0C0C0') COLOR_PANEL_GREY = Color('#C0C0C0')
COLOR_SELECTION_GREY = Color('#A6A6A6') COLOR_SELECTION_GREY = Color('#A6A6A6')
COLOR_INACTIVE_FILL = Color('#9D9FA1') COLOR_INACTIVE_FILL = Color('#9D9FA1')

View File

@ -21,6 +21,8 @@ from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, WidgetInvoker from sugar.graphics.palette import Palette, WidgetInvoker
class ToggleToolButton(gtk.ToggleToolButton): class ToggleToolButton(gtk.ToggleToolButton):
__gtype_name__ = "SugarToggleToolButton"
def __init__(self, named_icon=None): def __init__(self, named_icon=None):
gtk.ToggleToolButton.__init__(self) gtk.ToggleToolButton.__init__(self)
self._palette = None self._palette = None
@ -37,9 +39,25 @@ class ToggleToolButton(gtk.ToggleToolButton):
def set_palette(self, palette): def set_palette(self, palette):
self._palette = palette self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child) 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): def set_tooltip(self, text):
self._palette = Palette(text) self._palette = Palette(text)
self._palette.props.invoker = WidgetInvoker(self.child) 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) palette = property(get_palette, set_palette)

View File

@ -23,6 +23,7 @@ from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, WidgetInvoker from sugar.graphics.palette import Palette, WidgetInvoker
class ToolButton(gtk.ToolButton): class ToolButton(gtk.ToolButton):
__gtype_name__ = "SugarToolButton"
def __init__(self, icon_name=None): def __init__(self, icon_name=None):
gtk.ToolButton.__init__(self) gtk.ToolButton.__init__(self)
@ -41,12 +42,28 @@ class ToolButton(gtk.ToolButton):
def set_palette(self, palette): def set_palette(self, palette):
self._palette = palette self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child) 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): def set_tooltip(self, text):
self.set_palette(Palette(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): def _button_clicked_cb(self, widget):
if self._palette: if self._palette:
self._palette.popdown(True) 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) palette = property(get_palette, set_palette)