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/POTFILES
po/*.gmo
po/.intltool-merge-cache
sugar/__installed__.py
tools/sugar-setup-activity
threadframe
@ -53,4 +54,5 @@ browser/sugar-marshal.h
bin/sugar
shell/extensions/_extensions.c
data/sugar.gtkrc
data/sugar.xml
data/sugar-xo.gtkrc

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)
* #2475 Retrieve correctly the file path for files in removable devices. (tomeu)
* #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(LIB, gtk+-2.0)
PKG_CHECK_MODULES(LIB_BINDINGS, pygtk-2.0)
PKG_CHECK_MODULES(LIBUI, gtk+-2.0)
PKG_CHECK_MODULES(LIBUI_BINDINGS, pygtk-2.0)
PKG_CHECK_MODULES(LIB_BINDINGS, pygobject-2.0)
PYGTK_DEFSDIR=`$PKG_CONFIG --variable=defsdir pygtk-2.0`
AC_SUBST(PYGTK_DEFSDIR)
@ -45,7 +47,7 @@ data/Makefile
lib/Makefile
lib/ui/Makefile
services/Makefile
services/clipboard/Makefile
services/shell/Makefile
shell/Makefile
shell/intro/Makefile
shell/hardware/Makefile
@ -64,7 +66,7 @@ services/console/Makefile
services/console/interface/Makefile
services/console/interface/xo/Makefile
services/console/interface/memphis/plugins/clean_size/Makefile
services/console/interface/memphis/plugins/dirty_size/Makefile
services/console/interface/memphis/plugins/smaps/Makefile
services/console/interface/memphis/plugins/Makefile
services/console/interface/memphis/plugins/memphis_init/Makefile
services/console/interface/memphis/plugins/cpu/Makefile

View File

@ -14,5 +14,23 @@ GTKRC_FILES = \
sugar.gtkrc \
sugar-xo.gtkrc
EXTRA_DIST = $(sugar_DATA) em.py gtkrc.em
mime_xml_in_files = sugar.xml.in
mime_xml_files = $(mime_xml_in_files:.xml.in=.xml)
@INTLTOOL_XML_RULE@
mimedir = $(datadir)/mime/packages
mime_DATA = $(mime_xml_files)
install-data-hook:
if [ -z "$$DESTDIR" ]; then \
update-mime-database "$(datadir)/mime"; \
fi
uninstall-hook:
if [ -z "$$DESTDIR" ]; then \
update-mime-database "$(datadir)/mime"; \
fi
EXTRA_DIST = $(sugar_DATA) $(mime_xml_in_files) em.py gtkrc.em
CLEANFILES = $(GTKRC_FILES)

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 = \
$(LIB_CFLAGS)
$(LIBUI_CFLAGS)
noinst_LTLIBRARIES = libsugarui.la
libsugarui_la_LIBADD = \
$(LIB_LIBS)
$(LIBUI_LIBS)
libsugarui_la_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)
void
sugar_menu_popup(SugarMenu *menu,
int x,
int y)
sugar_menu_set_active(SugarMenu *menu, gboolean active)
{
GtkWidget *window;
window = GTK_MENU(menu)->toplevel;
g_return_if_fail(window != NULL);
GTK_MENU_SHELL(menu)->active = TRUE;
gtk_widget_show(GTK_WIDGET(menu));
gtk_window_move(GTK_WINDOW(window), x, y);
gtk_widget_show(window);
GTK_MENU_SHELL(menu)->active = active;
}
void
sugar_menu_popdown(SugarMenu *menu)
sugar_menu_embed(SugarMenu *menu, GtkContainer *parent)
{
GtkWidget *window;
menu->orig_toplevel = GTK_MENU(menu)->toplevel;
window = GTK_MENU(menu)->toplevel;
g_return_if_fail(window != NULL);
GTK_MENU_SHELL(menu)->active = FALSE;
gtk_widget_hide(GTK_WIDGET(menu));
gtk_widget_hide(window);
GTK_MENU(menu)->toplevel = gtk_widget_get_toplevel(GTK_WIDGET(parent));
gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(parent));
}
static void
sugar_menu_size_request (GtkWidget *widget,
GtkRequisition *requisition)
void
sugar_menu_unembed(SugarMenu *menu)
{
SugarMenu *menu = SUGAR_MENU(widget);
(* GTK_WIDGET_CLASS (sugar_menu_parent_class)->size_request) (widget, requisition);
requisition->width = MAX(requisition->width, menu->min_width);
if (menu->orig_toplevel) {
GTK_MENU(menu)->toplevel = menu->orig_toplevel;
gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(menu->orig_toplevel));
}
}
static void
sugar_menu_class_init(SugarMenuClass *menu_class)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(menu_class);
widget_class->size_request = sugar_menu_size_request;
}
static void
sugar_menu_init(SugarMenu *menu)
{
menu->orig_toplevel = NULL;
}

View File

@ -37,6 +37,7 @@ typedef struct _SugarMenuClass SugarMenuClass;
struct _SugarMenu {
GtkMenu base_instance;
GtkWidget *orig_toplevel;
int min_width;
};
@ -45,12 +46,10 @@ struct _SugarMenuClass {
};
GType sugar_menu_get_type (void);
void sugar_menu_popup (SugarMenu *menu,
int x,
int y);
void sugar_menu_set_min_width (SugarMenu *menu,
int min_width);
void sugar_menu_popdown (SugarMenu *menu);
void sugar_menu_set_active (SugarMenu *menu,
gboolean active);
void sugar_menu_embed (SugarMenu *menu,
GtkContainer *parent);
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
class MultiLogView(gtk.VBox):
def __init__(self, path):
def __init__(self, path, extra_files):
self._active_log = None
self._iters = []
self._extra_files = extra_files
# Creating Main treeview with Actitivities list
tv_menu = gtk.TreeView()
tv_menu.connect('cursor-changed', self._load_log)
tv_menu.set_rules_hint(True)
self._tv_menu = gtk.TreeView()
self._tv_menu.connect('cursor-changed', self._load_log)
self._tv_menu.set_rules_hint(True)
# Set width
box_width = gtk.gdk.screen_width() * 80 / 100
tv_menu.set_size_request(box_width*25/100, 0)
self._tv_menu.set_size_request(box_width*25/100, 0)
self.store_menu = gtk.TreeStore(str)
tv_menu.set_model(self.store_menu)
self._store_menu = gtk.TreeStore(str)
self._tv_menu.set_model(self._store_menu)
self._add_column(tv_menu, 'Sugar logs', 0)
self._add_column(self._tv_menu, 'Sugar logs', 0)
self._logs_path = os.path.join(env.get_profile_path(), 'logs')
self._activity = {}
# Activities menu
self.hbox = gtk.HBox(False, 3)
self.hbox.pack_start(tv_menu, True, True, 0)
self.hbox.pack_start(self._tv_menu, True, True, 0)
# Activity log, set width
self._view = LogView()
@ -59,33 +59,44 @@ class MultiLogView(gtk.VBox):
self.hbox.pack_start(self._view, True, True, 0)
self.hbox.show_all()
gobject.timeout_add(1000, self._update, tv_menu)
gobject.timeout_add(1000, self._update)
# Load the log information in View (textview)
def _load_log(self, treeview):
treeselection = treeview.get_selection()
treestore, iter = treeselection.get_selected()
# Get current selection
act_log = self.store_menu.get_value(iter, 0)
act_log = self._store_menu.get_value(iter, 0)
# Set buffer and scroll down
self._view.textview.set_buffer(self._activity[act_log])
self._view.textview.scroll_to_mark(self._activity[act_log].get_insert(), 0);
self._active_log = act_log
def _update(self, tv_menu):
def _update(self):
# Searching log files
for logfile in os.listdir(self._logs_path):
full_log_path = os.path.join(self._logs_path, logfile)
self._add_log_file(full_log_path)
if os.path.isdir(full_log_path):
continue
for ext in self._extra_files:
self._add_log_file(ext)
return True
def _get_filename_from_path(self, path):
return path.split('/')[-1]
def _add_log_file(self, path):
if os.path.isdir(path):
return False
logfile = self._get_filename_from_path(path)
if not self._activity.has_key(logfile):
self._add_activity(logfile)
model = LogBuffer(full_log_path)
model = LogBuffer(path)
self._activity[logfile] = model
self._activity[logfile].update()
@ -94,17 +105,16 @@ class MultiLogView(gtk.VBox):
# 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)
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);
self._view.textview.scroll_to_mark(self._activity[logfile].get_insert(), 0)
return True
def _add_activity(self, name):
self._insert_row(self.store_menu, None, name)
self._insert_row(self._store_menu, None, name)
# Add a new column to the main treeview, (code from Memphis)
def _add_column(self, treeview, column_name, index):
@ -171,9 +181,20 @@ class LogView(gtk.ScrolledWindow):
self.textview.show()
class Interface:
def __init__(self):
path = None
viewer = MultiLogView(path)
xserver_logfile = self._get_xserver_logfile_path()
# Aditional files to watch in logviewer
ext_files = []
ext_files.append(xserver_logfile)
viewer = MultiLogView(path, ext_files)
self.widget = viewer.hbox
# Get the Xorg log file path, we have two ways to get the path: do a system
# call and exec a 'xset -q' or just read directly the file that we know
# always be the right one for a XO machine...
def _get_xserver_logfile_path(self):
path = "/var/log/Xorg.0.log"
return path

View File

@ -130,15 +130,14 @@ class Data:
treeview.set_model(self.store)
def _start_memphis(self, button):
# Update information every 1.5 second
button.hide()
self.interface.button_stop.show()
self._running_status = True
gobject.timeout_add(1500, self.load_data, self.treeview)
self._gid = gobject.timeout_add(1500, self.load_data, self.treeview)
def _stop_memphis(self, button):
gobject.source_remove(self._gid)
self._running_status = False
button.hide()
self.interface.button_start.show()

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
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']]
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):
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
return [dirty_sizes['private']]
def get_dirty(pself, pid):
def get_data(pself, 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._configure_vte()
self._vte.set_size(30, 5)
self._vte.set_size(100, 5)
self._vte.set_size_request(200, 450)
self._vte.show()
self.pack_start(self._vte)

View File

@ -7,20 +7,22 @@ class Analysis:
def __init__(self, pid):
self.pid = pid
def DirtyRSS(self):
def SMaps(self):
smaps = proc_smaps.ProcSmaps(self.pid)
dirty = []
private = 0
shared = 0
private_dirty = 0
shared_dirty = 0
referenced = 0
for map in smaps.mappings:
private += map.private_dirty
shared += map.shared_dirty
private_dirty += map.private_dirty
shared_dirty += map.shared_dirty
referenced += map.referenced
dirty = {"private": int(private), "shared": int(shared)}
smaps = {"private_dirty": int(private_dirty), \
"shared_dirty": int(shared_dirty),\
"referenced": int(referenced)}
return dirty
return smaps
def ApproxRealMemoryUsage(self):
maps = proc_smaps.ProcMaps(self.pid)

View File

@ -1,4 +1,6 @@
import sys, os
import os
import re
import sys
import string
class ProcInfo:
@ -36,10 +38,12 @@ class ProcInfo:
return None
# Parsing data , check 'man 5 proc' for details
data = infile.read().split()
stat_data = infile.read()
infile.close()
process_name = self._get_process_name(stat_data)
data = self._get_safe_split(stat_data)
state_dic = {
'R': 'Running',
'S': 'Sleeping',
@ -51,10 +55,9 @@ class ProcInfo:
# user and group owners
pidstat = os.stat(pidfile)
info = {
'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_name': state_dic[data[2]], # Process State name, ex: Running, sleeping, Zombie, etc
'ppid': int(data[3]), # Parent process ID
@ -69,6 +72,14 @@ class ProcInfo:
return info
# Return the process name
def _get_process_name(self, data):
m = re.search("\(.*\)", data)
return m.string[m.start()+1:m.end()-1]
def _get_safe_split(self, data):
new_data = re.sub("\(.*\)", '()', data)
return new_data.split()
# Returns the CPU usage expressed in Jiffies
def get_CPU_usage(self, cpu_hz, used_jiffies, start_time):

View File

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

View File

@ -6,7 +6,15 @@ pygtk.require('2.0')
import os
import sys
from sugar import env
from sugar import util
sys.path.append(env.get_service_path('console'))
# change to the user's home directory if it is set
# root if not
os.chdir(os.environ.get('HOME', '/'))
#set the process title so it shows up as sugar-console not python
util.set_proc_title('sugar-console')
import console

View File

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

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:
return False
def get_activities_for_type(self, mime_type):
result = []
for bundle in self._bundles.values():
if bundle.get_mime_types() and mime_type in bundle.get_mime_types():
result.append(bundle)
return result
def get_registry():
return _bundle_registry

View File

@ -19,9 +19,9 @@ import logging
import urlparse
from sugar.objects import mime
from sugar import activity
import objecttypeservice
import bundleregistry
class ClipboardObject:
@ -66,30 +66,15 @@ class ClipboardObject:
return ''
def get_activity(self):
logging.debug('get_activity')
mapping = {'text/html' : 'org.laptop.WebActivity',
'image/jpeg' : 'org.laptop.WebActivity',
'image/gif' : 'org.laptop.WebActivity',
'image/png' : 'org.laptop.WebActivity',
'text/plain' : 'org.laptop.AbiWordActivity',
'text/rtf' : 'org.laptop.AbiWordActivity',
'text/richtext' : 'org.laptop.AbiWordActivity',
'application/pdf' : 'org.laptop.sugar.ReadActivity',
'application/x-squeak-project' : 'org.vpri.EtoysActivity'}
mime = self.get_mime_type()
if not mime:
return ''
"""
registry = activity.get_registry()
registry = bundleregistry.get_registry()
activities = registry.get_activities_for_type(self.get_mime_type())
# TODO: should we return several activities?
if activities:
return activities[0]
else:
return ''
"""
if mapping.has_key(mime):
return mapping[mime]
return activities[0].get_service_name()
else:
return ''
@ -101,8 +86,6 @@ class ClipboardObject:
def add_format(self, format):
self._formats[format.get_type()] = format
# We want to get the activity early in order to prevent a DBus lockup.
activity = self.get_activity()
def get_formats(self):
return self._formats

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]
Name = org.laptop.Clipboard
Exec = @bindir@/sugar-clipboard
Exec = @bindir@/sugar-shell-service

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,9 +21,9 @@ import wnck
import dbus
from sugar import wm
from sugar import activity
from model.homeactivity import HomeActivity
from model import bundleregistry
class HomeModel(gobject.GObject):
"""Model of the "Home" view (activity management)
@ -51,6 +51,9 @@ class HomeModel(gobject.GObject):
([gobject.TYPE_PYOBJECT])),
'active-activity-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])),
'pending-activity-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
}
@ -58,8 +61,8 @@ class HomeModel(gobject.GObject):
gobject.GObject.__init__(self)
self._activities = []
self._bundle_registry = bundleregistry.get_registry()
self._current_activity = None
self._active_activity = None
self._pending_activity = None
screen = wnck.screen_get_default()
screen.connect('window-opened', self._window_opened_cb)
@ -67,8 +70,55 @@ class HomeModel(gobject.GObject):
screen.connect('active-window-changed',
self._active_window_changed_cb)
def get_current_activity(self):
return self._current_activity
def get_pending_activity(self):
"""Returns the activity that would be seen in the Activity zoom level
In the Home (or Neighborhood or Groups) zoom level, this
indicates the activity that would become active if the user
switched to the Activity zoom level. (In the Activity zoom
level, this just returns the currently-active activity.)
Unlike get_active_activity(), this never returns None as long
as there is any activity running.
"""
return self._pending_activity
def _set_pending_activity(self, home_activity):
if self._pending_activity == home_activity:
return
self._pending_activity = home_activity
self.emit('pending-activity-changed', self._pending_activity)
def get_active_activity(self):
"""Returns the activity that the user is currently working in
In the Activity zoom level, this returns the currently-active
activity. In the other zoom levels, it returns the activity
that was most-recently active in the Activity zoom level, or
None if the most-recently-active activity is no longer
running.
"""
return self._active_activity
def _set_active_activity(self, home_activity):
if self._active_activity == home_activity:
return
if self._active_activity:
service = self._active_activity.get_service()
if service:
service.set_active(False,
reply_handler=self._set_active_success,
error_handler=self._set_active_error)
if home_activity:
service = home_activity.get_service()
if service:
service.set_active(True,
reply_handler=self._set_active_success,
error_handler=self._set_active_error)
self._active_activity = home_activity
self.emit('active-activity-changed', self._active_activity)
def __iter__(self):
return iter(self._activities)
@ -84,65 +134,48 @@ class HomeModel(gobject.GObject):
def _window_opened_cb(self, screen, window):
if window.get_window_type() == wnck.WINDOW_NORMAL:
activity = None
home_activity = None
activity_id = wm.get_activity_id(window)
bundle_id = wm.get_bundle_id(window)
if bundle_id:
bundle = self._bundle_registry.get_bundle(bundle_id)
service_name = wm.get_bundle_id(window)
if service_name:
registry = activity.get_registry()
activity_info = registry.get_activity(service_name)
else:
bundle = None
activity_info = None
if activity_id:
activity = self._get_activity_by_id(activity_id)
home_activity = self._get_activity_by_id(activity_id)
if not activity:
activity = HomeActivity(bundle, activity_id)
self._add_activity(activity)
if not home_activity:
home_activity = HomeActivity(activity_info, activity_id)
self._add_activity(home_activity)
activity.set_window(window)
home_activity.set_window(window)
activity.props.launching = False
self.emit('activity-started', activity)
home_activity.props.launching = False
self.emit('activity-started', home_activity)
if self._pending_activity is None:
self._set_pending_activity(home_activity)
def _window_closed_cb(self, screen, window):
if window.get_window_type() == wnck.WINDOW_NORMAL:
self._remove_activity_by_xid(window.get_xid())
if not self._activities:
self.emit('active-activity-changed', None)
self._notify_activity_activation(self._current_activity, None)
def _get_activity_by_xid(self, xid):
for activity in self._activities:
if activity.get_xid() == xid:
return activity
for home_activity in self._activities:
if home_activity.get_xid() == xid:
return home_activity
return None
def _get_activity_by_id(self, activity_id):
for activity in self._activities:
if activity.get_activity_id() == activity_id:
return activity
for home_activity in self._activities:
if home_activity.get_activity_id() == activity_id:
return home_activity
return None
def _notify_activity_activation(self, old_activity, new_activity):
if old_activity == new_activity:
return
if old_activity:
service = old_activity.get_service()
if service:
service.set_active(False,
reply_handler=self._set_active_success,
error_handler=self._set_active_error)
if new_activity:
service = new_activity.get_service()
if service:
service.set_active(True,
reply_handler=self._set_active_success,
error_handler=self._set_active_error)
def _set_active_success(self):
pass
@ -151,55 +184,58 @@ class HomeModel(gobject.GObject):
def _active_window_changed_cb(self, screen):
window = screen.get_active_window()
if window == None:
self.emit('active-activity-changed', None)
self._notify_activity_activation(self._current_activity, None)
return
if window.get_window_type() != wnck.WINDOW_NORMAL:
if window is None or window.get_window_type() != wnck.WINDOW_NORMAL:
return
xid = window.get_xid()
act = self._get_activity_by_xid(window.get_xid())
if act:
self._notify_activity_activation(self._current_activity, act)
self._current_activity = act
else:
self._notify_activity_activation(self._current_activity, None)
self._current_activity = None
act = self._get_activity_by_xid(xid)
if act is None:
logging.error('Model for window %d does not exist.' % xid)
self._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):
self._activities.append(activity)
self.emit('activity-added', activity)
def _remove_activity(self, home_activity):
if home_activity == self._active_activity:
self._set_active_activity(None)
# Figure out the new _pending_activity.
windows = wnck.screen_get_default().get_windows_stacked()
windows.reverse()
for window in windows:
new_activity = self._get_activity_by_xid(window.get_xid())
if new_activity is not None:
self._set_pending_activity(new_activity)
break
else:
logging.error('No activities are running')
self._set_pending_activity(None)
def _remove_activity(self, activity):
if activity == self._current_activity:
self._current_activity = None
self.emit('activity-removed', activity)
self._activities.remove(activity)
self.emit('activity-removed', home_activity)
self._activities.remove(home_activity)
def _remove_activity_by_xid(self, xid):
activity = self._get_activity_by_xid(xid)
if activity:
self._remove_activity(activity)
home_activity = self._get_activity_by_xid(xid)
if home_activity:
self._remove_activity(home_activity)
else:
logging.error('Model for window %d does not exist.' % xid)
def notify_activity_launch(self, activity_id, service_name):
bundle = self._bundle_registry.get_bundle(service_name)
if not bundle:
registry = activity.get_registry()
activity_info = registry.get_activity(service_name)
if not activity_info:
raise ValueError("Activity service name '%s' was not found in the bundle registry." % service_name)
activity = HomeActivity(bundle, activity_id)
activity.props.launching = True
self._add_activity(activity)
home_activity = HomeActivity(activity_info, activity_id)
home_activity.props.launching = True
self._add_activity(home_activity)
def notify_activity_launch_failed(self, activity_id):
activity = self._get_activity_by_id(activity_id)
if activity:
logging.debug("Activity %s (%s) launch failed" % (activity_id, activity.get_type()))
self._remove_activity(activity)
home_activity = self._get_activity_by_id(activity_id)
if home_activity:
logging.debug("Activity %s (%s) launch failed" % (activity_id, home_activity.get_type()))
self._remove_activity(home_activity)
else:
logging.error('Model for activity id %s does not exist.' % activity_id)

View File

@ -17,10 +17,7 @@
"""D-bus service providing access to the shell's functionality"""
import dbus
from model import bundleregistry
_DBUS_SERVICE = "org.laptop.Shell"
_DBUS_ACTIVITY_REGISTRY_IFACE = "org.laptop.Shell.ActivityRegistry"
_DBUS_SHELL_IFACE = "org.laptop.Shell"
_DBUS_OWNER_IFACE = "org.laptop.Shell.Owner"
_DBUS_PATH = "/org/laptop/Shell"
@ -56,9 +53,6 @@ class ShellService(dbus.service.Object):
self._home_model.connect('active-activity-changed',
self._cur_activity_changed_cb)
bundle_registry = bundleregistry.get_registry()
bundle_registry.connect('bundle-added', self._bundle_added_cb)
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus)
dbus.service.Object.__init__(self, bus_name, _DBUS_PATH)
@ -83,60 +77,6 @@ class ShellService(dbus.service.Object):
def NotifyLaunchFailure(self, activity_id):
self._shell.notify_launch_failure(activity_id)
@dbus.service.method(_DBUS_ACTIVITY_REGISTRY_IFACE,
in_signature="s", out_signature="b")
def AddBundle(self, bundle_path):
"""Register the activity bundle with the global registry
bundle_path -- path to the activity bundle's root directory,
that is, the directory with activity/activity.info as a
child of the directory.
The bundleregistry.BundleRegistry is responsible for setting
up a set of d-bus service mappings for each available activity.
"""
registry = bundleregistry.get_registry()
return registry.add_bundle(bundle_path)
@dbus.service.method(_DBUS_ACTIVITY_REGISTRY_IFACE,
in_signature="s", out_signature="a{sv}")
def GetActivity(self, service_name):
registry = bundleregistry.get_registry()
bundle = registry.get_bundle(service_name)
if not bundle:
return {}
return self._bundle_to_dict(bundle)
@dbus.service.method(_DBUS_ACTIVITY_REGISTRY_IFACE,
in_signature="s", out_signature="aa{sv}")
def FindActivity(self, name):
result = []
key = name.lower()
for bundle in bundleregistry.get_registry():
name = bundle.get_name().lower()
service_name = bundle.get_service_name().lower()
if name.find(key) != -1 or service_name.find(key) != -1:
result.append(self._bundle_to_dict(bundle))
return result
@dbus.service.method(_DBUS_ACTIVITY_REGISTRY_IFACE,
in_signature="s", out_signature="aa{sv}")
def GetActivitiesForType(self, mime_type):
result = []
for bundle in bundleregistry.get_registry():
if bundle.get_mime_types() and mime_type in bundle.get_mime_types():
result.append(self._bundle_to_dict(bundle))
return result
@dbus.service.signal(_DBUS_ACTIVITY_REGISTRY_IFACE, signature="a{sv}")
def ActivityAdded(self, activity_info):
pass
@dbus.service.signal(_DBUS_OWNER_IFACE, signature="s")
def ColorChanged(self, color):
pass
@ -169,12 +109,3 @@ class ShellService(dbus.service.Object):
if new_id:
self.CurrentActivityChanged(new_id)
def _bundle_to_dict(self, bundle):
return {'name': bundle.get_name(),
'icon': bundle.get_icon(),
'service_name': bundle.get_service_name(),
'path': bundle.get_path()}
def _bundle_added_cb(self, bundle_registry, bundle):
self.ActivityAdded(self._bundle_to_dict(bundle))

View File

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

View File

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

View File

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

View File

@ -64,11 +64,13 @@ class ClipboardMenu(Palette):
self._remove_item = MenuItem(_('Remove'), 'stock-remove')
self._remove_item.connect('activate', self._remove_item_activate_cb)
self.append_menu_item(self._remove_item)
self.menu.append(self._remove_item)
self._remove_item.show()
self._open_item = MenuItem(_('Open'), 'stock-keep')
self._open_item.connect('activate', self._open_item_activate_cb)
self.append_menu_item(self._open_item)
self.menu.append(self._open_item)
self._open_item.show()
#self._stop_item = MenuItem(_('Stop download'), 'stock-close')
# TODO: Implement stopping downloads
@ -77,7 +79,8 @@ class ClipboardMenu(Palette):
self._journal_item = MenuItem(_('Add to journal'), 'document-save')
self._journal_item.connect('activate', self._journal_item_activate_cb)
self.append_menu_item(self._journal_item)
self.menu.append(self._journal_item)
self._journal_item.show()
self._update_items_visibility(installable)
@ -120,32 +123,8 @@ class ClipboardMenu(Palette):
def _open_item_activate_cb(self, menu_item):
if self._percent < 100:
return
jobject = self._copy_to_journal()
# TODO: we cannot simply call resume() right now because we would lock
# the shell as we are sharing the same loop as the shell service.
#jobject.resume()
# TODO: take this out when we fix the mess that is the shell/shellservice.
from model import bundleregistry
from sugar.activity.bundle import Bundle
from sugar.activity import activityfactory
if jobject.is_bundle():
bundle = Bundle(jobject.file_path)
if not bundle.is_installed():
bundle.install()
activityfactory.create(bundle.get_service_name())
else:
service_name = None
mime_type = jobject.metadata['mime_type']
for bundle in bundleregistry.get_registry():
if bundle.get_mime_types() and mime_type in bundle.get_mime_types():
service_name = bundle.get_service_name()
break
if service_name:
activityfactory.create_with_object_id(service_name,
jobject.object_id)
jobject.resume()
def _remove_item_activate_cb(self, menu_item):
cb_service = clipboardservice.get_instance()

View File

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

View File

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

View File

@ -130,7 +130,7 @@ class HomeMyIcon(MyIcon):
shutdown_menu_item = gtk.MenuItem(_('Shutdown'))
shutdown_menu_item.connect('activate', self._shutdown_activate_cb)
palette.append_menu_item(shutdown_menu_item)
palette.menu.append(shutdown_menu_item)
shutdown_menu_item.show()
self.set_palette(palette)

View File

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

View File

@ -12,21 +12,19 @@ sugar_PYTHON = \
util.py \
wm.py
INCLUDES = \
$(LIB_CFLAGS) \
$(LIB_BINDINGS_CFLAGS) \
$(PYTHON_INCLUDES) \
-I$(top_srcdir)/lib \
-I$(top_srcdir)/lib/ui
pkgpyexecdir = $(pythondir)/sugar
pkgpyexec_LTLIBRARIES = _sugarext.la _sugaruiext.la
_sugarext_la_CFLAGS = \
$(LIB_CFLAGS) \
$(LIB_BINDINGS_CFLAGS) \
$(PYTHON_INCLUDES) \
-I$(top_srcdir)/lib
_sugarext_la_LDFLAGS = -module -avoid-version
_sugarext_la_LIBADD = \
$(LIB_BINDINGS_LIBS) \
$(LIB_LIBS) \
$(top_builddir)/lib/libsugar.la
_sugarext_la_SOURCES = \
@ -36,10 +34,16 @@ nodist__sugarext_la_SOURCES = _sugarext.c
_sugarext.c: _sugarext.defs _sugarext.override
_sugaruiext_la_CFLAGS = \
$(LIBUI_CFLAGS) \
$(LIBUI_BINDINGS_CFLAGS) \
$(PYTHON_INCLUDES) \
-I$(top_srcdir)/lib/ui
_sugaruiext_la_LDFLAGS = -module -avoid-version
_sugaruiext_la_LIBADD = \
$(LIB_BINDINGS_LIBS) \
$(LIB_LIBS) \
$(LIBUI_BINDINGS_LIBS) \
$(LIBUI_LIBS) \
$(top_builddir)/lib/ui/libsugarui.la
_sugaruiext_la_SOURCES = \

View File

@ -26,19 +26,27 @@
;; From sugar-menu.h
(define-method popup
(define-method set_active
(of-object "SugarMenu")
(c-name "sugar_menu_popup")
(c-name "sugar_menu_set_active")
(return-type "none")
(parameters
'("gint" "x")
'("gint" "y")
'("gboolean" "active")
)
)
(define-method popdown
(define-method embed
(of-object "SugarMenu")
(c-name "sugar_menu_popdown")
(c-name "sugar_menu_embed")
(return-type "none")
(parameters
'("GtkContainer" "container")
)
)
(define-method unembed
(of-object "SugarMenu")
(c-name "sugar_menu_unembed")
(return-type "none")
)

View File

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

View File

@ -114,7 +114,7 @@ class ActivityCreationHandler(gobject.GObject):
self._factory.create(self._activity_handle.get_dict(),
timeout=120 * 1000,
reply_handler=self._no_reply_handler,
reply_handler=self._create_reply_handler,
error_handler=self._create_error_handler)
def get_activity_id(self):
@ -137,7 +137,10 @@ class ActivityCreationHandler(gobject.GObject):
def _activate_error_handler(self, err):
logging.debug("Activity activation request failed %s" % err)
def _create_reply_handler(self, xid):
def _create_reply_handler(self, xid=None):
if xid is None:
self._create_error_handler('D-Bus error')
return
logging.debug("Activity created %s (%s)." %
(self._activity_handle.activity_id, self._service_name))

View File

@ -41,6 +41,7 @@ class NotInstalledException(Exception): pass
class InvalidPathException(Exception): pass
class ZipExtractException(Exception): pass
class RegistrationException(Exception): pass
class MalformedBundleException(Exception): pass
class Bundle:
"""Metadata description of a given application/activity
@ -265,10 +266,12 @@ class Bundle:
if not bundle_root_dir:
bundle_root_dir = file_name.split('/')[0]
if not bundle_root_dir.endswith('.activity'):
raise 'Incorrect bundle.'
raise MalformedBundleException(
'The activity directory name must end with .activity')
else:
if not file_name.startswith(bundle_root_dir):
raise 'Incorrect bundle.'
raise MalformedBundleException(
'All files in the bundle must be inside the activity directory')
return bundle_root_dir
@ -294,10 +297,7 @@ class Bundle:
self._init_with_path(bundle_path)
bus = dbus.SessionBus()
proxy_obj = bus.get_object(_DBUS_SHELL_SERVICE, _DBUS_SHELL_PATH)
dbus_service = dbus.Interface(proxy_obj, _DBUS_ACTIVITY_REGISTRY_IFACE)
if not dbus_service.AddBundle(bundle_path):
if not activity.get_registry().add_bundle(bundle_path):
raise RegistrationException
def deinstall(self):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,7 +36,40 @@ _RIGHT_TOP = 5
_TOP_LEFT = 6
_TOP_RIGHT = 7
class Palette(gobject.GObject):
# Helper function to find the gap position and size of widget a
def _calculate_gap(a, b):
# Test for each side if the palette and invoker are
# adjacent to each other.
gap = True
if a.y + a.height == b.y:
gap_side = gtk.POS_BOTTOM
elif a.x + a.width == b.x:
gap_side = gtk.POS_RIGHT
elif a.x == b.x + b.width:
gap_side = gtk.POS_LEFT
elif a.y == b.y + b.height:
gap_side = gtk.POS_TOP
else:
gap = False
if gap:
if gap_side == gtk.POS_BOTTOM or gap_side == gtk.POS_TOP:
gap_start = min(a.width, max(0, b.x - a.x))
gap_size = max(0, min(a.width,
(b.x + b.width) - a.x) - gap_start)
elif gap_side == gtk.POS_RIGHT or gap_side == gtk.POS_LEFT:
gap_start = min(a.height, max(0, b.y - a.y))
gap_size = max(0, min(a.height,
(b.y + b.height) - a.y) - gap_start)
if gap and gap_size > 0:
return (gap_side, gap_start, gap_size)
else:
return False
class Palette(gtk.Window):
DEFAULT = 0
AT_CURSOR = 1
AROUND = 2
@ -54,7 +87,9 @@ class Palette(gobject.GObject):
'invoker' : (object, None, None,
gobject.PARAM_READWRITE),
'position' : (gobject.TYPE_INT, None, None, 0, 6,
0, gobject.PARAM_READWRITE)
0, gobject.PARAM_READWRITE),
'draw-gap' : (bool, None, None, False,
gobject.PARAM_READWRITE)
}
__gsignals__ = {
@ -65,16 +100,21 @@ class Palette(gobject.GObject):
}
def __init__(self, label, accel_path=None):
gobject.GObject.__init__(self)
gtk.Window.__init__(self)
self.set_decorated(False)
self.set_resizable(False)
self.connect('realize', self._realize_cb)
self._full_request = [0, 0]
self._cursor_x = 0
self._cursor_y = 0
self._state = self._SECONDARY
self._state = self._PRIMARY
self._invoker = None
self._group_id = None
self._up = False
self._position = self.DEFAULT
self._draw_gap = False
self._palette_popup_sid = None
self._popup_anim = animator.Animator(0.3, 10)
@ -86,60 +126,70 @@ class Palette(gobject.GObject):
self._popdown_anim = animator.Animator(0.6, 10)
self._popdown_anim.add(_PopdownAnimation(self))
self._menu = _sugaruiext.Menu()
vbox = gtk.VBox()
vbox.set_border_width(style.DEFAULT_PADDING)
self._primary = _PrimaryMenuItem(label, accel_path)
self._menu.append(self._primary)
self._primary.show()
self._label = gtk.Label()
vbox.pack_start(self._label, False)
self._separator = gtk.SeparatorMenuItem()
self._menu.append(self._separator)
self._secondary_box = gtk.VBox()
vbox.pack_start(self._secondary_box)
self._content = _ContentMenuItem()
self._menu.append(self._content)
self._separator = gtk.HSeparator()
self._secondary_box.pack_start(self._separator)
self._button_bar = _ButtonBarMenuItem()
self._menu.append(self._button_bar)
self._menu_box = gtk.VBox()
self._secondary_box.pack_start(self._menu_box)
self._menu_box.show()
self._menu.connect('enter-notify-event',
self._content = gtk.VBox()
self._secondary_box.pack_start(self._content)
self._content.show()
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._menu.connect('leave-notify-event',
self.connect('leave-notify-event',
self._leave_notify_event_cb)
self.set_primary_text(label, accel_path)
def is_up(self):
return self._up
def get_rect(self):
win_x, win_y = self.window.get_origin()
rectangle = self.get_allocation()
x = win_x + rectangle.x
y = win_y + rectangle.y
width = rectangle.width
height = rectangle.height
return gtk.gdk.Rectangle(x, y, width, height)
def set_primary_text(self, label, accel_path=None):
self._primary.set_label(label, accel_path)
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
self._label.set_text(label)
self._label.show()
def set_content(self, widget):
self._content.set_widget(widget)
self._content.show()
if len(self._content.get_children()) > 0:
self.remove(self._content.get_children()[0])
def append_button(self, button):
self._button_bar.append_button(button)
self._button_bar.show()
if widget is not None:
self._content.add(widget)
self._update_accept_focus()
self._update_separator()
def set_group_id(self, group_id):
if self._group_id:
@ -154,9 +204,11 @@ class Palette(gobject.GObject):
self._invoker = value
self._invoker.connect('mouse-enter', self._invoker_mouse_enter_cb)
self._invoker.connect('mouse-leave', self._invoker_mouse_leave_cb)
self._invoker.connect('focus-out', self._invoker_focus_out_cb)
elif pspec.name == 'position':
self._position = value
elif pspec.name == 'draw-gap':
self._draw_gap = value
self.queue_draw()
else:
raise AssertionError
@ -165,9 +217,57 @@ class Palette(gobject.GObject):
return self._invoker
elif pspec.name == 'position':
return self._position
elif pspec.name == 'draw-gap':
return self._draw_gap
else:
raise AssertionError
def do_size_allocate(self, allocation):
gtk.Window.do_size_allocate(self, allocation)
self.queue_draw()
def do_expose_event(self, event):
# We want to draw a border with a beautiful gap
if self._draw_gap:
invoker = self._invoker.get_rect()
palette = self.get_rect()
gap = _calculate_gap(palette, invoker)
else:
gap = False
if gap:
self.style.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self, "palette",
0, 0,
self.allocation.width,
self.allocation.height,
gap[0], gap[1], gap[2])
else:
self.style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self, "palette",
0, 0,
self.allocation.width,
self.allocation.height)
# Fall trough to the container expose handler.
# (Leaving out the window expose handler which redraws everything)
gtk.Bin.do_expose_event(self, event)
def _update_separator(self):
visible = len(self.menu.get_children()) > 0 or \
len(self._content.get_children()) > 0
self._separator.props.visible = visible
def _update_accept_focus(self):
accept_focus = len(self._content.get_children())
if self.window:
self.window.set_accept_focus(accept_focus)
def _realize_cb(self, widget):
self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
self._update_accept_focus()
def _in_screen(self, x, y):
[width, height] = self._full_request
screen_area = self._invoker.get_screen_area()
@ -182,7 +282,7 @@ class Palette(gobject.GObject):
if inv_rect == None:
inv_rect = self._invoker.get_rect()
palette_width, palette_height = self._menu.size_request()
palette_width, palette_height = self.size_request()
x = inv_rect.x + inv_rect.width * invoker_halign + \
palette_width * palette_halign
@ -241,12 +341,12 @@ class Palette(gobject.GObject):
def _update_full_request(self):
state = self._state
self._menu.set_size_request(-1, -1)
self.set_size_request(-1, -1)
self._set_state(self._SECONDARY)
self._full_request = self._menu.size_request()
self._full_request = self.size_request()
self._menu.set_size_request(self._full_request[0], -1)
self.set_size_request(self._full_request[0], -1)
self._set_state(state)
@ -282,7 +382,7 @@ class Palette(gobject.GObject):
elif position == self.TOP:
x, y = self._get_top_position()
self._menu.popup(x, y)
self.move(x, y)
def _show(self):
if self._up:
@ -291,11 +391,12 @@ class Palette(gobject.GObject):
self._update_cursor_position()
self._update_full_request()
self._invoker.connect_to_parent()
self._palette_popup_sid = _palette_observer.connect('popup',
self._palette_observer_popup_cb)
self._palette_popup_sid = _palette_observer.connect(
'popup', self._palette_observer_popup_cb)
self._update_position()
self.menu.set_active(True)
self.show()
self._up = True
_palette_observer.emit('popup', self)
@ -305,7 +406,8 @@ class Palette(gobject.GObject):
if not self._palette_popup_sid is None:
_palette_observer.disconnect(self._palette_popup_sid)
self._palette_popup_sid = None
self._menu.popdown()
self.menu.set_active(False)
self.hide()
self._up = False
self.emit('popdown')
@ -329,25 +431,11 @@ class Palette(gobject.GObject):
return
if state == self._PRIMARY:
self._primary.show()
for menu_item in self._menu.get_children()[1:]:
menu_item.hide()
self.menu.unembed()
self._secondary_box.hide()
elif state == self._SECONDARY:
middle_menu_items = self._menu.get_children()
middle_menu_items = middle_menu_items[2:len(middle_menu_items) - 2]
if middle_menu_items or \
not self._content.is_empty() or \
not self._button_bar.is_empty():
self._separator.show()
for menu_item in middle_menu_items:
menu_item.show()
if not self._content.is_empty():
self._content.show()
if not self._button_bar.is_empty():
self._button_bar.show()
self.menu.embed(self._menu_box)
self._secondary_box.show()
self._state = state
@ -357,68 +445,54 @@ class Palette(gobject.GObject):
def _invoker_mouse_leave_cb(self, invoker):
self.popdown()
def _invoker_focus_out_cb(self, invoker):
self._hide()
def _enter_notify_event_cb(self, widget, event):
if event.detail == gtk.gdk.NOTIFY_NONLINEAR:
if event.detail != gtk.gdk.NOTIFY_INFERIOR:
self._popdown_anim.stop()
self._secondary_anim.start()
def _leave_notify_event_cb(self, widget, event):
if event.detail == gtk.gdk.NOTIFY_NONLINEAR:
if event.detail != gtk.gdk.NOTIFY_INFERIOR:
self.popdown()
def _palette_observer_popup_cb(self, observer, palette):
if self != palette:
self._hide()
class _PrimaryMenuItem(gtk.MenuItem):
def __init__(self, label, accel_path):
gtk.MenuItem.__init__(self)
self.set_border_width(style.DEFAULT_PADDING)
self._set_label(label, accel_path)
class PaletteActionBar(gtk.HButtonBox):
def add_action(label, icon_name=None):
button = Button(label)
def set_label(self, label, accel_path):
self.remove(self._label)
self._set_label(label, accel_path)
if icon_name:
icon = Icon(icon_name)
button.set_image(icon)
icon.show()
def _set_label(self, label, accel_path):
self._label = gtk.AccelLabel(label)
self._label.set_accel_widget(self)
self.pack_start(button)
button.show()
if accel_path:
self.set_accel_path(accel_path)
self._label.set_alignment(0.0, 0.5)
class _Menu(_sugaruiext.Menu):
__gtype_name__ = 'SugarPaletteMenu'
self.add(self._label)
self._label.show()
def __init__(self, palette):
_sugaruiext.Menu.__init__(self)
self._palette = palette
class _ContentMenuItem(gtk.MenuItem):
def __init__(self):
gtk.MenuItem.__init__(self)
def do_insert(self, item, position):
_sugaruiext.Menu.do_insert(self, item, position)
self._palette._update_separator()
def set_widget(self, widget):
if self.child:
self.remove(self.child)
self.add(widget)
def do_expose_event(self, event):
# Ignore the Menu expose, just do the MenuShell expose to prevent any
# border from being drawn here. A border is drawn by the palette object
# around everything.
gtk.MenuShell.do_expose_event(self, event)
def is_empty(self):
return self.child is None or not self.child.props.visible
def do_grab_notify(self, was_grabbed):
# Ignore grab_notify as the menu would close otherwise
pass
class _ButtonBarMenuItem(gtk.MenuItem):
def __init__(self):
gtk.MenuItem.__init__(self)
self._hbar = gtk.HButtonBox()
self.add(self._hbar)
self._hbar.show()
def append_button(self, button):
self._hbar.pack_start(button)
def is_empty(self):
return len(self._hbar.get_children()) == 0
def do_deactivate(self):
self._palette._hide()
class _PopupAnimation(animator.Animation):
def __init__(self, palette):
@ -469,13 +543,6 @@ class Invoker(gobject.GObject):
height = gtk.gdk.screen_height()
return gtk.gdk.Rectangle(0, 0, width, height)
def connect_to_parent(self):
window = self.get_toplevel()
window.connect('focus-out-event', self._window_focus_out_event_cb)
def _window_focus_out_event_cb(self, widget, event):
self.emit('focus-out')
class WidgetInvoker(Invoker):
def __init__(self, widget):
Invoker.__init__(self)
@ -495,6 +562,37 @@ class WidgetInvoker(Invoker):
return gtk.gdk.Rectangle(x, y, width, height)
def draw_invoker_rect(self, event, palette):
style = self._widget.style
if palette.is_up():
gap = _calculate_gap(self.get_rect(), palette.get_rect())
if gap:
style.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
self._widget.allocation.width,
self._widget.allocation.height,
gap[0], gap[1], gap[2])
else:
style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_IN, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
self._widget.allocation.width,
self._widget.allocation.height)
else:
style.paint_box(event.window, gtk.STATE_PRELIGHT,
gtk.SHADOW_NONE, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
self._widget.allocation.width,
self._widget.allocation.height)
def _enter_notify_event_cb(self, widget, event):
self.emit('mouse-enter')

View File

@ -22,6 +22,8 @@ from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, WidgetInvoker
class RadioToolButton(gtk.RadioToolButton):
__gtype_name__ = "SugarRadioToolButton"
def __init__(self, named_icon=None, group=None):
gtk.RadioToolButton.__init__(self, group=group)
self._palette = None
@ -38,9 +40,25 @@ class RadioToolButton(gtk.RadioToolButton):
def set_palette(self, palette):
self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child)
self._palette.props.draw_gap = True
self._palette.connect("popup", self._palette_changed)
self._palette.connect("popdown", self._palette_changed)
def set_tooltip(self, text):
self._palette = Palette(text)
self._palette.props.invoker = WidgetInvoker(self.child)
def do_expose_event(self, event):
if self._palette and self._palette.props.draw_gap:
if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT:
invoker = self._palette.props.invoker
invoker.draw_invoker_rect(event, self._palette)
gtk.RadioToolButton.do_expose_event(self, event)
def _palette_changed(self, palette):
# Force a redraw to update the invoker rectangle
self.queue_draw()
palette = property(get_palette, set_palette)

View File

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

View File

@ -21,6 +21,8 @@ from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, WidgetInvoker
class ToggleToolButton(gtk.ToggleToolButton):
__gtype_name__ = "SugarToggleToolButton"
def __init__(self, named_icon=None):
gtk.ToggleToolButton.__init__(self)
self._palette = None
@ -37,9 +39,25 @@ class ToggleToolButton(gtk.ToggleToolButton):
def set_palette(self, palette):
self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child)
self._palette.props.draw_gap = True
self._palette.connect("popup", self._palette_changed)
self._palette.connect("popdown", self._palette_changed)
def set_tooltip(self, text):
self._palette = Palette(text)
self._palette.props.invoker = WidgetInvoker(self.child)
def do_expose_event(self, event):
if self._palette and self._palette.props.draw_gap:
if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT:
invoker = self._palette.props.invoker
invoker.draw_invoker_rect(event, self._palette)
gtk.ToggleToolButton.do_expose_event(self, event)
def _palette_changed(self, palette):
# Force a redraw to update the invoker rectangle
self.queue_draw()
palette = property(get_palette, set_palette)

View File

@ -23,6 +23,7 @@ from sugar.graphics.icon import Icon
from sugar.graphics.palette import Palette, WidgetInvoker
class ToolButton(gtk.ToolButton):
__gtype_name__ = "SugarToolButton"
def __init__(self, icon_name=None):
gtk.ToolButton.__init__(self)
@ -41,12 +42,28 @@ class ToolButton(gtk.ToolButton):
def set_palette(self, palette):
self._palette = palette
self._palette.props.invoker = WidgetInvoker(self.child)
self._palette.props.draw_gap = True
self._palette.connect("popup", self._palette_changed)
self._palette.connect("popdown", self._palette_changed)
def set_tooltip(self, text):
self.set_palette(Palette(text))
def do_expose_event(self, event):
if self._palette and self._palette.props.draw_gap:
if self._palette.is_up() or self.child.state == gtk.STATE_PRELIGHT:
invoker = self._palette.props.invoker
invoker.draw_invoker_rect(event, self._palette)
gtk.ToolButton.do_expose_event(self, event)
def _button_clicked_cb(self, widget):
if self._palette:
self._palette.popdown(True)
def _palette_changed(self, palette):
# Force a redraw to update the invoker rectangle
self.queue_draw()
palette = property(get_palette, set_palette)