Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/sugar
diff options
context:
space:
mode:
authorJohn (J5) Palmieri <quinticent@localhost.localdomain>2007-08-20 18:41:03 (GMT)
committer John (J5) Palmieri <quinticent@localhost.localdomain>2007-08-20 18:41:03 (GMT)
commit655d7f41741d90617aa341db7542153261c9f6e7 (patch)
tree88c0df40a8bb109500672136fb7916b34348275b /sugar
parent735b0cf2c8a5c85b89a0b82d4fc1f40ec6ab5d12 (diff)
parent1a65f81d10186d2f82aafb17533e6b5946761ec8 (diff)
Merge branch 'master' of git+ssh://j5@dev.laptop.org/git/sugar
Diffstat (limited to 'sugar')
-rw-r--r--sugar/_sugaruiext.defs75
-rw-r--r--sugar/_sugaruiext.override2
-rw-r--r--sugar/_sugaruiextmodule.c2
-rw-r--r--sugar/activity/activity.py22
-rw-r--r--sugar/activity/activityservice.py2
-rw-r--r--sugar/activity/bundlebuilder.py56
-rw-r--r--sugar/clipboard/clipboardservice.py10
-rw-r--r--sugar/datastore/datastore.py3
-rw-r--r--sugar/datastore/dbus_helpers.py38
-rw-r--r--sugar/date.py14
-rw-r--r--sugar/graphics/Makefile.am2
-rw-r--r--sugar/graphics/canvasbutton.py2
-rw-r--r--sugar/graphics/canvasicon.py18
-rw-r--r--sugar/graphics/icon.py27
-rw-r--r--sugar/graphics/iconentry.py45
-rw-r--r--sugar/graphics/menuitem.py2
-rw-r--r--sugar/graphics/objectchooser.py2
-rw-r--r--sugar/graphics/palette.py94
-rw-r--r--sugar/graphics/palettegroup.py17
-rw-r--r--sugar/graphics/radiotoolbutton.py18
-rw-r--r--sugar/graphics/style.py2
-rw-r--r--sugar/graphics/toggletoolbutton.py18
-rw-r--r--sugar/graphics/toolbutton.py18
-rw-r--r--sugar/logger.py6
-rw-r--r--sugar/objects/mime.py38
-rw-r--r--sugar/presence/Makefile.am1
-rw-r--r--sugar/presence/tubeconn.py107
27 files changed, 493 insertions, 148 deletions
diff --git a/sugar/_sugaruiext.defs b/sugar/_sugaruiext.defs
index 3c011e1..6a9129d 100644
--- a/sugar/_sugaruiext.defs
+++ b/sugar/_sugaruiext.defs
@@ -22,8 +22,25 @@
(gtype-id "SUGAR_TYPE_MENU")
)
+(define-object IconEntry
+ (in-module "Sexy")
+ (parent "GtkEntry")
+ (c-name "SexyIconEntry")
+ (gtype-id "SEXY_TYPE_ICON_ENTRY")
+)
+
;; Enumerations and flags ...
+(define-enum IconEntryPosition
+ (in-module "Sexy")
+ (c-name "SexyIconEntryPosition")
+ (gtype-id "SEXY_TYPE_ICON_ENTRY_POSITION")
+ (values
+ '("primary" "SEXY_ICON_ENTRY_PRIMARY")
+ '("secondary" "SEXY_ICON_ENTRY_SECONDARY")
+ )
+)
+
;; From sugar-menu.h
(define-method set_active
@@ -94,3 +111,61 @@
'("const-char*" "property")
)
)
+
+;; From sexy-icon-entry.h
+
+(define-function sexy_icon_entry_get_type
+ (c-name "sexy_icon_entry_get_type")
+ (return-type "GType")
+)
+
+(define-function sexy_icon_entry_new
+ (c-name "sexy_icon_entry_new")
+ (is-constructor-of "SexyIconEntry")
+ (return-type "GtkWidget*")
+)
+
+(define-method set_icon
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_set_icon")
+ (return-type "none")
+ (parameters
+ '("SexyIconEntryPosition" "position")
+ '("GtkImage*" "icon")
+ )
+)
+
+(define-method set_icon_highlight
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_set_icon_highlight")
+ (return-type "none")
+ (parameters
+ '("SexyIconEntryPosition" "position")
+ '("gboolean" "highlight")
+ )
+)
+
+(define-method get_icon
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_get_icon")
+ (return-type "GtkImage*")
+ (parameters
+ '("SexyIconEntryPosition" "position")
+ )
+)
+
+(define-method get_icon_highlight
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_get_icon_highlight")
+ (return-type "gboolean")
+ (parameters
+ '("SexyIconEntryPosition" "position")
+ )
+)
+
+(define-method add_clear_button
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_add_clear_button")
+ (return-type "none")
+)
+
diff --git a/sugar/_sugaruiext.override b/sugar/_sugaruiext.override
index 6daafc3..beeaad0 100644
--- a/sugar/_sugaruiext.override
+++ b/sugar/_sugaruiext.override
@@ -8,6 +8,7 @@ headers
#include "sugar-key-grabber.h"
#include "sugar-menu.h"
#include "sugar-x11-util.h"
+#include "sexy-icon-entry.h"
#include <pygtk/pygtk.h>
#include <glib.h>
@@ -20,6 +21,7 @@ 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
+import gtk.Image as PyGtkImage_Type
%%
ignore-glob
*_get_type
diff --git a/sugar/_sugaruiextmodule.c b/sugar/_sugaruiextmodule.c
index 8c74010..719b153 100644
--- a/sugar/_sugaruiextmodule.c
+++ b/sugar/_sugaruiextmodule.c
@@ -27,6 +27,7 @@
extern PyMethodDef py_sugaruiext_functions[];
void py_sugaruiext_register_classes (PyObject *d);
+void py_sugaruiext_add_constants (PyObject *module, const gchar *strip_prefix);
DL_EXPORT(void)
init_sugaruiext(void)
@@ -39,6 +40,7 @@ init_sugaruiext(void)
d = PyModule_GetDict (m);
py_sugaruiext_register_classes (d);
+ py_sugaruiext_add_constants(m, "SEXY_");
if (PyErr_Occurred ()) {
Py_FatalError ("can't initialise module _sugaruiext");
diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py
index 262d89b..553b1a1 100644
--- a/sugar/activity/activity.py
+++ b/sugar/activity/activity.py
@@ -52,6 +52,8 @@ class ActivityToolbar(gtk.Toolbar):
self._activity = activity
activity.connect('shared', self._activity_shared_cb)
activity.connect('joined', self._activity_shared_cb)
+ activity.connect('notify::max_participants',
+ self._max_participants_changed_cb)
if activity.metadata:
self.title = gtk.Entry()
@@ -74,11 +76,11 @@ class ActivityToolbar(gtk.Toolbar):
'theme:zoom-home-mini')
self.share.combo.append_item(None, _('My Neighborhood'),
'theme:zoom-neighborhood-mini')
- self._update_share()
-
self.insert(self.share, -1)
self.share.show()
+ self._update_share()
+
self.keep = ToolButton('document-save')
self.keep.set_tooltip(_('Keep'))
self.keep.connect('clicked', self._keep_clicked_cb)
@@ -94,6 +96,9 @@ class ActivityToolbar(gtk.Toolbar):
self._update_title_sid = None
def _update_share(self):
+ if self._activity.props.max_participants == 1:
+ self.share.hide()
+
if self._activity.get_shared():
self.share.set_sensitive(False)
self.share.combo.set_active(self.SHARE_NEIGHBORHOOD)
@@ -139,6 +144,9 @@ class ActivityToolbar(gtk.Toolbar):
def _activity_shared_cb(self, activity):
self._update_share()
+ def _max_participants_changed_cb(self, activity, pspec):
+ self._update_share()
+
class EditToolbar(gtk.Toolbar):
def __init__(self):
gtk.Toolbar.__init__(self)
@@ -185,7 +193,10 @@ class Activity(Window, gtk.Container):
}
__gproperties__ = {
- 'active': (bool, None, None, False, gobject.PARAM_READWRITE)
+ 'active' : (bool, None, None, False,
+ gobject.PARAM_READWRITE),
+ 'max-participants': (int, None, None, 0, 1000, 0,
+ gobject.PARAM_READWRITE)
}
def __init__(self, handle, create_jobject=True):
@@ -235,6 +246,7 @@ class Activity(Window, gtk.Container):
self._preview = None
self._updating_jobject = False
self._closing = False
+ self._max_participants = 0
shared_activity = handle.get_shared_activity()
if shared_activity:
@@ -280,10 +292,14 @@ class Activity(Window, gtk.Container):
self._active = value
if not self._active and self._jobject:
self.save()
+ elif pspec.name == 'max-participants':
+ self._max_participants = value
def do_get_property(self, pspec):
if pspec.name == 'active':
return self._active
+ elif pspec.name == 'max-participants':
+ return self._max_participants
def get_id(self):
return self._activity_id
diff --git a/sugar/activity/activityservice.py b/sugar/activity/activityservice.py
index c9ee482..f2b3394 100644
--- a/sugar/activity/activityservice.py
+++ b/sugar/activity/activityservice.py
@@ -56,6 +56,6 @@ class ActivityService(dbus.service.Object):
self._activity = activity
@dbus.service.method(_ACTIVITY_INTERFACE)
- def set_active(self, active):
+ def SetActive(self, active):
logging.debug('ActivityService.set_active: %s.' % active)
self._activity.props.active = active
diff --git a/sugar/activity/bundlebuilder.py b/sugar/activity/bundlebuilder.py
index 3bbe454..b0e46ab 100644
--- a/sugar/activity/bundlebuilder.py
+++ b/sugar/activity/bundlebuilder.py
@@ -21,6 +21,7 @@ import zipfile
import shutil
import subprocess
import re
+import gettext
from sugar import env
from sugar.activity.bundle import Bundle
@@ -117,7 +118,7 @@ setup.py dist - create a bundle package \n\
setup.py install [dirname] - install the bundle \n\
setup.py uninstall [dirname] - uninstall the bundle \n\
setup.py genpot - generate the gettext pot file \n\
-setup.py genmo - compile gettext po files in mo \n\
+setup.py genl10n - generate localization files \n\
setup.py clean - clean the directory \n\
setup.py release - do a new release of the bundle \n\
setup.py help - print this message \n\
@@ -157,17 +158,26 @@ def _get_po_list(manifest):
return file_list
-def _get_mo_list(manifest):
- mo_list = []
+def _get_l10n_list(manifest):
+ l10n_list = []
for lang in _get_po_list(manifest).keys():
filename = _get_service_name() + '.mo'
- mo_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename))
+ l10n_list.append(os.path.join('locale', lang, 'LC_MESSAGES', filename))
+ l10n_list.append(os.path.join('locale', lang, 'activity.linfo'))
- return mo_list
+ return l10n_list
+
+def _get_activity_name():
+ info_path = os.path.join(_get_source_path(), 'activity', 'activity.info')
+ f = open(info_path,'r')
+ info = f.read()
+ f.close()
+ match = re.search('^name\s*=\s*(.*)$', info, flags = re.MULTILINE)
+ return match.group(1)
def cmd_dist(bundle_name, manifest):
- cmd_genmo(bundle_name, manifest)
+ cmd_genl10n(bundle_name, manifest)
file_list = _get_file_list(manifest)
zipname = _get_package_name(bundle_name)
@@ -177,7 +187,7 @@ def cmd_dist(bundle_name, manifest):
for filename in file_list:
bundle_zip.write(filename, os.path.join(base_dir, filename))
- for filename in _get_mo_list(manifest):
+ for filename in _get_l10n_list(manifest):
bundle_zip.write(filename, os.path.join(base_dir, filename))
bundle_zip.close()
@@ -205,8 +215,21 @@ def cmd_genpot(bundle_name, manifest):
if file_name.endswith('.py'):
python_files.append(file_name)
+ # First write out a stub .pot file containing just the translated
+ # activity name, then have xgettext merge the rest of the
+ # translations into that. (We can't just append the activity name
+ # to the end of the .pot file afterwards, because that might
+ # create a duplicate msgid.)
pot_file = os.path.join('po', '%s.pot' % bundle_name)
- args = [ 'xgettext', '--language=Python',
+ activity_name = _get_activity_name()
+ escaped_name = re.sub('([\\\\"])', '\\\\\\1', activity_name)
+ f = open(pot_file, 'w')
+ f.write('#: activity/activity.info:2\n')
+ f.write('msgid "%s"\n' % escaped_name)
+ f.write('msgstr ""\n')
+ f.close()
+
+ args = [ 'xgettext', '--join-existing', '--language=Python',
'--keyword=_', '--output=%s' % pot_file ]
args += python_files
@@ -220,14 +243,16 @@ def cmd_genpot(bundle_name, manifest):
if retcode:
print 'ERROR - msgmerge failed with return code %i.' % retcode
-def cmd_genmo(bundle_name, manifest):
+def cmd_genl10n(bundle_name, manifest):
source_path = _get_source_path()
+ activity_name = _get_activity_name()
po_list = _get_po_list(manifest)
for lang in po_list.keys():
file_name = po_list[lang]
- mo_path = os.path.join(source_path, 'locale', lang, 'LC_MESSAGES')
+ localedir = os.path.join(source_path, 'locale', lang)
+ mo_path = os.path.join(localedir, 'LC_MESSAGES')
if not os.path.isdir(mo_path):
os.makedirs(mo_path)
@@ -237,6 +262,13 @@ def cmd_genmo(bundle_name, manifest):
if retcode:
print 'ERROR - msgfmt failed with return code %i.' % retcode
+ cat = gettext.GNUTranslations(open(mo_file, 'r'))
+ translated_name = cat.gettext(activity_name)
+ linfo_file = os.path.join(localedir, 'activity.linfo')
+ f = open(linfo_file, 'w')
+ f.write('[Activity]\nname = %s\n' % translated_name)
+ f.close()
+
def cmd_release(bundle_name, manifest):
if not os.path.isdir('.git'):
print 'ERROR - this command works only for git repositories'
@@ -358,8 +390,8 @@ def start(bundle_name, manifest='MANIFEST'):
cmd_uninstall(sys.argv[2])
elif sys.argv[1] == 'genpot':
cmd_genpot(bundle_name, manifest)
- elif sys.argv[1] == 'genmo':
- cmd_genmo(bundle_name, manifest)
+ elif sys.argv[1] == 'genl10n':
+ cmd_genl10n(bundle_name, manifest)
elif sys.argv[1] == 'clean':
cmd_clean()
elif sys.argv[1] == 'release':
diff --git a/sugar/clipboard/clipboardservice.py b/sugar/clipboard/clipboardservice.py
index dbdf41d..0e357fe 100644
--- a/sugar/clipboard/clipboardservice.py
+++ b/sugar/clipboard/clipboardservice.py
@@ -23,7 +23,7 @@ NAME_KEY = 'NAME'
PERCENT_KEY = 'PERCENT'
ICON_KEY = 'ICON'
PREVIEW_KEY = 'PREVIEW'
-ACTIVITY_KEY = 'ACTIVITY'
+ACTIVITIES_KEY = 'ACTIVITIES'
FORMATS_KEY = 'FORMATS'
TYPE_KEY = 'TYPE'
@@ -51,7 +51,7 @@ class ClipboardService(gobject.GObject):
'object-deleted': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str])),
'object-state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
- ([str, str, int, str, str, str])),
+ ([str, str, int, str, str, object])),
}
def __init__(self):
@@ -118,13 +118,13 @@ class ClipboardService(gobject.GObject):
percent
icon
preview
- activity
+ activities
From the ClipboardObject instance which is being described.
"""
self.emit('object-state-changed', str(object_id), values[NAME_KEY],
values[PERCENT_KEY], values[ICON_KEY], values[PREVIEW_KEY],
- values[ACTIVITY_KEY])
+ values[ACTIVITIES_KEY])
def add_object(self, name):
"""Add a new object to the path
@@ -193,7 +193,7 @@ class ClipboardService(gobject.GObject):
PERCENT_KEY: number,
ICON_KEY: str,
PREVIEW_KEY: XXX what is it?,
- ACTIVITY_KEY: source activity id,
+ ACTIVITIES_KEY: activities that can open this object,
FORMATS_KEY: list of XXX what is it?
"""
return self._dbus_service.get_object(dbus.ObjectPath(object_id),)
diff --git a/sugar/datastore/datastore.py b/sugar/datastore/datastore.py
index 5ba994d..0dbe35b 100644
--- a/sugar/datastore/datastore.py
+++ b/sugar/datastore/datastore.py
@@ -120,6 +120,9 @@ class DSObject(object):
def resume(self, service_name=None):
if self.is_bundle():
+ if service_name is not None:
+ raise ValueError('Object is a bundle, cannot be resumed as an activity.')
+
bundle = Bundle(self.file_path)
if not bundle.is_installed():
bundle.install()
diff --git a/sugar/datastore/dbus_helpers.py b/sugar/datastore/dbus_helpers.py
index f0cfa3b..442a35d 100644
--- a/sugar/datastore/dbus_helpers.py
+++ b/sugar/datastore/dbus_helpers.py
@@ -28,55 +28,63 @@ DS_DBUS_SERVICE = "org.laptop.sugar.DataStore"
DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore"
DS_DBUS_PATH = "/org/laptop/sugar/DataStore"
-_bus = dbus.SessionBus()
-_data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH),
- DS_DBUS_INTERFACE)
+_data_store = None
+
+def _get_data_store():
+ global _data_store
+
+ if not _data_store:
+ _bus = dbus.SessionBus()
+ _data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE,
+ DS_DBUS_PATH),
+ DS_DBUS_INTERFACE)
+ return _data_store
def create(properties, filename):
- object_id = _data_store.create(dbus.Dictionary(properties), filename)
+ object_id = _get_data_store().create(dbus.Dictionary(properties), filename)
logging.debug('dbus_helpers.create: ' + object_id)
return object_id
def update(uid, properties, filename, reply_handler=None, error_handler=None, timeout=-1):
logging.debug('dbus_helpers.update: %s, %s, %s' % (uid, filename, properties))
if reply_handler and error_handler:
- _data_store.update(uid, dbus.Dictionary(properties), filename,
+ _get_data_store().update(uid, dbus.Dictionary(properties), filename,
reply_handler=reply_handler,
error_handler=error_handler,
timeout=timeout)
else:
- _data_store.update(uid, dbus.Dictionary(properties), filename)
+ _get_data_store().update(uid, dbus.Dictionary(properties), filename)
def delete(uid):
logging.debug('dbus_helpers.delete: %r' % uid)
- _data_store.delete(uid)
+ _get_data_store().delete(uid)
def get_properties(uid):
logging.debug('dbus_helpers.get_properties: %s' % uid)
- return _data_store.get_properties(uid)
+ return _get_data_store().get_properties(uid)
def get_filename(uid):
- filename = _data_store.get_filename(uid)
+ filename = _get_data_store().get_filename(uid)
logging.debug('dbus_helpers.get_filename: %s, %s' % (uid, filename))
return filename
def find(query, reply_handler, error_handler):
logging.debug('dbus_helpers.find: %r' % query)
if reply_handler and error_handler:
- return _data_store.find(query, reply_handler=reply_handler,
+ return _get_data_store().find(query, reply_handler=reply_handler,
error_handler=error_handler)
else:
- return _data_store.find(query)
+ return _get_data_store().find(query)
def mount(uri, options):
- return _data_store.mount(uri, options)
+ return _get_data_store().mount(uri, options)
def unmount(mount_point_id):
- _data_store.unmount(mount_point_id)
+ _get_data_store().unmount(mount_point_id)
def mounts():
- return _data_store.mounts()
+ return _get_data_store().mounts()
def get_unique_values(key):
- return _data_store.get_uniquevaluesfor(key, dbus.Dictionary({}, signature='ss'))
+ return _get_data_store().get_uniquevaluesfor(key, dbus.Dictionary({}, signature='ss'))
diff --git a/sugar/date.py b/sugar/date.py
index 3b68818..3f4dcc2 100644
--- a/sugar/date.py
+++ b/sugar/date.py
@@ -21,12 +21,13 @@ import datetime
class Date(object):
"""Date-object storing a simple time.time() float
-
- XXX not sure about the rationale for this class,
- possibly it makes transfer over dbus easier?
+
+ Useful to display dates in the UI in an
+ abbreviated and easy to read format.
"""
def __init__(self, timestamp):
"""Initialise via a timestamp (floating point value)"""
+ self._today = datetime.date.today()
self._timestamp = timestamp
def __str__(self):
@@ -39,14 +40,13 @@ class Date(object):
the year in the date.
"""
date = datetime.date.fromtimestamp(self._timestamp)
- today = datetime.date.today()
# FIXME localization
- if date == today:
+ if date == self._today:
result = 'Today'
- elif date == today - datetime.timedelta(1):
+ elif date == self._today - datetime.timedelta(1):
result = 'Yesterday'
- elif date.year == today.year:
+ elif date.year == self._today.year:
result = date.strftime('%B %d')
else:
result = date.strftime('%B %d, %Y')
diff --git a/sugar/graphics/Makefile.am b/sugar/graphics/Makefile.am
index af66cfb..2d0dc17 100644
--- a/sugar/graphics/Makefile.am
+++ b/sugar/graphics/Makefile.am
@@ -9,7 +9,7 @@ sugar_PYTHON = \
combobox.py \
icon.py \
iconbutton.py \
- menuitem.py \
+ iconentry.py \
notebook.py \
objectchooser.py \
radiotoolbutton.py \
diff --git a/sugar/graphics/canvasbutton.py b/sugar/graphics/canvasbutton.py
index 31bc833..fc869f9 100644
--- a/sugar/graphics/canvasbutton.py
+++ b/sugar/graphics/canvasbutton.py
@@ -25,7 +25,7 @@ class CanvasButton(hippo.CanvasButton):
hippo.CanvasButton.__init__(self, text=label)
if icon_name:
- icon = Icon(icon_name, gtk.ICON_SIZE_BUTTON)
+ icon = Icon(icon_name,icon_size=gtk.ICON_SIZE_BUTTON)
self.props.widget.set_image(icon)
icon.show()
diff --git a/sugar/graphics/canvasicon.py b/sugar/graphics/canvasicon.py
index 39f1358..a77eecd 100644
--- a/sugar/graphics/canvasicon.py
+++ b/sugar/graphics/canvasicon.py
@@ -142,6 +142,8 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
gobject.PARAM_READWRITE),
'size' : (int, None, None, 0, 1024, 0,
gobject.PARAM_READWRITE),
+ 'scale' : (int, None, None, 0, 1024, 0,
+ gobject.PARAM_READWRITE),
'cache' : (bool, None, None, False,
gobject.PARAM_READWRITE),
'active' : (bool, None, None, True,
@@ -156,6 +158,7 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
self._buffers = {}
self._cur_buffer = None
self._size = 0
+ self._scale = 0
self._fill_color = None
self._stroke_color = None
self._icon_name = None
@@ -210,6 +213,11 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
self._clear_buffers()
self._size = value
self.emit_request_changed()
+ elif pspec.name == 'scale':
+ if self._scale != value and not self._cache:
+ self._clear_buffers()
+ self._scale = value
+ self.emit_request_changed()
elif pspec.name == 'cache':
self._cache = value
elif pspec.name == 'active':
@@ -277,6 +285,8 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
return self._active
elif pspec.name == 'badge-name':
return self._badge_name
+ elif pspec.name == 'scale':
+ return self._scale
def _get_icon_size(self, handle):
if handle:
@@ -286,9 +296,11 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
return [0, 0]
def _get_size(self, handle):
- if self._size == 0:
- width, height = self._get_icon_size(handle)
- else:
+ width, height = self._get_icon_size(handle)
+ if self._scale != 0:
+ width = int(width * self._scale)
+ height = int(height * self._scale)
+ elif self._size != 0:
width = height = self._size
return [width, height]
diff --git a/sugar/graphics/icon.py b/sugar/graphics/icon.py
index 731412f..e324d45 100644
--- a/sugar/graphics/icon.py
+++ b/sugar/graphics/icon.py
@@ -34,17 +34,18 @@ class Icon(gtk.Image):
gobject.PARAM_READWRITE)
}
- def __init__(self, name, size=gtk.ICON_SIZE_LARGE_TOOLBAR, **kwargs):
+ def __init__(self, name, **kwargs):
+ self._constructed = False
self._fill_color = None
self._stroke_color = None
self._icon_name = name
- self._size = size
self._theme = gtk.icon_theme_get_default()
+ self._data = None
+
gobject.GObject.__init__(self, **kwargs)
- # If we have a non-styled-icon
- if not self._fill_color and not self._stroke_color:
- self._update_normal_icon()
+ self._constructed = True
+ self._update_icon()
def _get_pixbuf(self, data, width, height):
loader = gtk.gdk.PixbufLoader('svg')
@@ -77,9 +78,12 @@ class Icon(gtk.Image):
source.set_state(gtk.STATE_INSENSITIVE)
icon_set.add_source(source)
- self.set_from_icon_set(icon_set, self._size)
+ self.props.icon_set = icon_set
def _update_icon(self):
+ if not self._constructed:
+ return
+
if not self._fill_color and not self._stroke_color:
self._update_normal_icon()
return
@@ -100,12 +104,12 @@ class Icon(gtk.Image):
self._data = data
# Redraw pixbuf
- [w, h] = gtk.icon_size_lookup(self._size)
+ [w, h] = gtk.icon_size_lookup(self.props.icon_size)
pixbuf = self._get_pixbuf(self._data, w, h)
self.set_from_pixbuf(pixbuf)
def _get_real_name(self, name):
- info = self._theme.lookup_icon(name, self._size, 0)
+ info = self._theme.lookup_icon(name, self.props.icon_size, 0)
if not info:
raise ValueError("Icon '" + name + "' not found.")
fname = info.get_filename()
@@ -122,9 +126,16 @@ class Icon(gtk.Image):
elif pspec.name == 'stroke-color':
self._stroke_color = value
self._update_icon()
+ else:
+ gtk.Image.do_set_property(self, pspec, value)
+
+ if pspec.name == 'icon-size':
+ self._update_icon()
def do_get_property(self, pspec):
if pspec.name == 'fill-color':
return self._fill_color
elif pspec.name == 'stroke-color':
return self._stroke_color
+ else:
+ return gtk.Image.do_get_property(self, pspec)
diff --git a/sugar/graphics/iconentry.py b/sugar/graphics/iconentry.py
new file mode 100644
index 0000000..2f7584f
--- /dev/null
+++ b/sugar/graphics/iconentry.py
@@ -0,0 +1,45 @@
+# 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
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+
+from sugar import _sugaruiext
+
+ICON_ENTRY_PRIMARY = _sugaruiext.ICON_ENTRY_PRIMARY
+ICON_ENTRY_SECONDARY = _sugaruiext.ICON_ENTRY_SECONDARY
+
+class IconEntry(_sugaruiext.IconEntry):
+ def set_icon_from_name(self, position, name):
+ icon_theme = gtk.icon_theme_get_default()
+ icon_info = icon_theme.lookup_icon(name,
+ gtk.ICON_SIZE_SMALL_TOOLBAR,
+ 0)
+
+ pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.get_filename())
+
+ image = gtk.Image()
+ image.set_from_pixbuf(pixbuf)
+ image.show()
+
+ self.set_icon(position, image)
+
+ def set_icon(self, position, image):
+ if image.get_storage_type() not in [gtk.IMAGE_PIXBUF, gtk.IMAGE_STOCK]:
+ raise ValueError('Image must have a storage type of pixbuf or ' +
+ 'stock, not %r.' % image.get_storage_type())
+ _sugaruiext.IconEntry.set_icon(self, position, image)
+
diff --git a/sugar/graphics/menuitem.py b/sugar/graphics/menuitem.py
index 492f5f1..db4a293 100644
--- a/sugar/graphics/menuitem.py
+++ b/sugar/graphics/menuitem.py
@@ -22,7 +22,7 @@ class MenuItem(gtk.ImageMenuItem):
def __init__(self, text_label, icon_name=None):
gtk.ImageMenuItem.__init__(self, text_label)
if icon_name:
- icon = Icon(icon_name, gtk.ICON_SIZE_MENU)
+ icon = Icon(icon_name, icon_size=gtk.ICON_SIZE_MENU)
self.set_image(icon)
icon.show()
diff --git a/sugar/graphics/objectchooser.py b/sugar/graphics/objectchooser.py
index 5b09e13..46739f6 100644
--- a/sugar/graphics/objectchooser.py
+++ b/sugar/graphics/objectchooser.py
@@ -150,7 +150,7 @@ class CollapsedEntry(CanvasRoundBox):
self._icon_name = type.icon
if not self._icon_name:
- self._icon_name = 'theme:stock-missing'
+ self._icon_name = 'theme:image-missing'
return self._icon_name
diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py
index 368a0f6..6b1209a 100644
--- a/sugar/graphics/palette.py
+++ b/sugar/graphics/palette.py
@@ -87,9 +87,7 @@ class Palette(gtk.Window):
'invoker' : (object, None, None,
gobject.PARAM_READWRITE),
'position' : (gobject.TYPE_INT, None, None, 0, 6,
- 0, gobject.PARAM_READWRITE),
- 'draw-gap' : (bool, None, None, False,
- gobject.PARAM_READWRITE)
+ 0, gobject.PARAM_READWRITE)
}
__gsignals__ = {
@@ -114,7 +112,6 @@ class Palette(gtk.Window):
self._group_id = None
self._up = False
self._position = self.DEFAULT
- self._draw_gap = False
self._palette_popup_sid = None
self._popup_anim = animator.Animator(0.3, 10)
@@ -162,6 +159,7 @@ class Palette(gtk.Window):
self._leave_notify_event_cb)
self.set_primary_text(label, accel_path)
+ self.set_group_id('default')
def is_up(self):
return self._up
@@ -178,8 +176,9 @@ class Palette(gtk.Window):
return gtk.gdk.Rectangle(x, y, width, height)
def set_primary_text(self, label, accel_path=None):
- self._label.set_text(label)
- self._label.show()
+ if label is not None:
+ self._label.set_text(label)
+ self._label.show()
def set_content(self, widget):
if len(self._content.get_children()) > 0:
@@ -196,6 +195,7 @@ class Palette(gtk.Window):
group = palettegroup.get_group(self._group_id)
group.remove(self)
if group_id:
+ self._group_id = group_id
group = palettegroup.get_group(group_id)
group.add(self)
@@ -206,9 +206,6 @@ class Palette(gtk.Window):
self._invoker.connect('mouse-leave', self._invoker_mouse_leave_cb)
elif pspec.name == 'position':
self._position = value
- elif pspec.name == 'draw-gap':
- self._draw_gap = value
- self.queue_draw()
else:
raise AssertionError
@@ -217,8 +214,6 @@ class Palette(gtk.Window):
return self._invoker
elif pspec.name == 'position':
return self._position
- elif pspec.name == 'draw-gap':
- return self._draw_gap
else:
raise AssertionError
@@ -228,7 +223,7 @@ class Palette(gtk.Window):
def do_expose_event(self, event):
# We want to draw a border with a beautiful gap
- if self._draw_gap:
+ if self._invoker.has_rectangle_gap():
invoker = self._invoker.get_rect()
palette = self.get_rect()
@@ -398,6 +393,9 @@ class Palette(gtk.Window):
self.menu.set_active(True)
self.show()
+ if self._invoker:
+ self._invoker.notify_popup()
+
self._up = True
_palette_observer.emit('popup', self)
self.emit('popup')
@@ -406,22 +404,31 @@ class Palette(gtk.Window):
if not self._palette_popup_sid is None:
_palette_observer.disconnect(self._palette_popup_sid)
self._palette_popup_sid = None
+
self.menu.set_active(False)
self.hide()
+ if self._invoker:
+ self._invoker.notify_popdown()
+
self._up = False
self.emit('popdown')
- def popup(self):
+ def popup(self, immediate=False):
self._popdown_anim.stop()
- self._popup_anim.start()
+
+ if not immediate:
+ self._popup_anim.start()
+ else:
+ self._show()
+
self._secondary_anim.start()
- def popdown(self, inmediate=False):
+ def popdown(self, immediate=False):
self._secondary_anim.stop()
self._popup_anim.stop()
- if not inmediate:
+ if not immediate:
self._popdown_anim.start()
else:
self._hide()
@@ -440,7 +447,15 @@ class Palette(gtk.Window):
self._state = state
def _invoker_mouse_enter_cb(self, invoker):
- self.popup()
+ immediate = False
+ if self._group_id:
+ group = palettegroup.get_group(self._group_id)
+ if group and group.is_up():
+ immediate = True
+ group.popdown()
+
+ print immediate
+ self.popup(immediate=immediate)
def _invoker_mouse_leave_cb(self, invoker):
self.popdown()
@@ -535,6 +550,12 @@ class Invoker(gobject.GObject):
def __init__(self):
gobject.GObject.__init__(self)
+ def has_rectangle_gap(self):
+ return False
+
+ def draw_rectangle(self, event, palette):
+ pass
+
def get_default_position(self):
return Palette.AROUND
@@ -543,6 +564,12 @@ class Invoker(gobject.GObject):
height = gtk.gdk.screen_height()
return gtk.gdk.Rectangle(0, 0, width, height)
+ def notify_popup(self):
+ pass
+
+ def notify_popdown(self):
+ pass
+
class WidgetInvoker(Invoker):
def __init__(self, widget):
Invoker.__init__(self)
@@ -562,31 +589,24 @@ class WidgetInvoker(Invoker):
return gtk.gdk.Rectangle(x, y, width, height)
- def draw_invoker_rect(self, event, palette):
+ def has_rectangle_gap(self):
+ return True
+
+ def draw_rectangle(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,
+ 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)
+ self._widget.allocation.height,
+ gap[0], gap[1], gap[2])
else:
style.paint_box(event.window, gtk.STATE_PRELIGHT,
- gtk.SHADOW_NONE, event.area, self._widget,
+ gtk.SHADOW_IN, event.area, self._widget,
"palette-invoker",
self._widget.allocation.x,
self._widget.allocation.y,
@@ -602,6 +622,12 @@ class WidgetInvoker(Invoker):
def get_toplevel(self):
return self._widget.get_toplevel()
+ def notify_popup(self):
+ self._widget.queue_draw()
+
+ def notify_popdown(self):
+ self._widget.queue_draw()
+
class CanvasInvoker(Invoker):
def __init__(self, item):
Invoker.__init__(self)
diff --git a/sugar/graphics/palettegroup.py b/sugar/graphics/palettegroup.py
index d5e1125..d6ae122 100644
--- a/sugar/graphics/palettegroup.py
+++ b/sugar/graphics/palettegroup.py
@@ -39,6 +39,7 @@ class Group(gobject.GObject):
gobject.GObject.__init__(self)
self._up = False
self._palettes = []
+ self._sig_ids = {}
def is_up(self):
return self._up
@@ -46,18 +47,26 @@ class Group(gobject.GObject):
def add(self, palette):
self._palettes.append(palette)
+ self._sig_ids[palette] = []
+
sid = palette.connect('popup', self._palette_popup_cb)
- palette.popup_sid = sid
+ self._sig_ids[palette].append(sid)
sid = palette.connect('popdown', self._palette_popdown_cb)
- palette.podown_sid = sid
+ self._sig_ids[palette].append(sid)
def remove(self, palette):
- self.disconnect(palette.popup_sid)
- self.disconnect(palette.popdown_sid)
+ sig_ids = self._sig_ids[palette]
+ for sid in sig_ids:
+ palette.disconnect(sid)
self._palettes.remove(palette)
+ def popdown(self):
+ for palette in self._palettes:
+ if palette.is_up():
+ palette.popdown(immediate=True)
+
def _palette_popup_cb(self, palette):
if not self._up:
self.emit('popup')
diff --git a/sugar/graphics/radiotoolbutton.py b/sugar/graphics/radiotoolbutton.py
index fb584ee..4ffc7d1 100644
--- a/sugar/graphics/radiotoolbutton.py
+++ b/sugar/graphics/radiotoolbutton.py
@@ -40,25 +40,15 @@ 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)
+ 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)
+ if self._palette and self._palette.is_up():
+ invoker = self._palette.props.invoker
+ invoker.draw_rectangle(event, self._palette)
gtk.RadioToolButton.do_expose_event(self, event)
- def _palette_changed(self, palette):
- # Force a redraw to update the invoker rectangle
- self.queue_draw()
-
palette = property(get_palette, set_palette)
diff --git a/sugar/graphics/style.py b/sugar/graphics/style.py
index c0094b6..9d561eb 100644
--- a/sugar/graphics/style.py
+++ b/sugar/graphics/style.py
@@ -128,6 +128,8 @@ COLOR_WHITE = Color('#FFFFFF')
COLOR_TRANSPARENT = Color('#FFFFFF', alpha=0.0)
COLOR_PANEL_GREY = Color('#C0C0C0')
COLOR_SELECTION_GREY = Color('#A6A6A6')
+COLOR_TOOLBAR_GREY = Color('#404040')
+COLOR_BUTTON_GREY = Color('#808080')
COLOR_INACTIVE_FILL = Color('#9D9FA1')
COLOR_INACTIVE_STROKE = Color('#757575')
diff --git a/sugar/graphics/toggletoolbutton.py b/sugar/graphics/toggletoolbutton.py
index 41050e2..74e78a6 100644
--- a/sugar/graphics/toggletoolbutton.py
+++ b/sugar/graphics/toggletoolbutton.py
@@ -39,25 +39,15 @@ 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)
+ 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)
+ if self._palette and self._palette.is_up():
+ invoker = self._palette.props.invoker
+ invoker.draw_rectangle(event, self._palette)
gtk.ToggleToolButton.do_expose_event(self, event)
- def _palette_changed(self, palette):
- # Force a redraw to update the invoker rectangle
- self.queue_draw()
-
palette = property(get_palette, set_palette)
diff --git a/sugar/graphics/toolbutton.py b/sugar/graphics/toolbutton.py
index 52a5d62..b42f63e 100644
--- a/sugar/graphics/toolbutton.py
+++ b/sugar/graphics/toolbutton.py
@@ -28,7 +28,8 @@ class ToolButton(gtk.ToolButton):
def __init__(self, icon_name=None):
gtk.ToolButton.__init__(self)
self._palette = None
- self.set_icon(icon_name)
+ if icon_name:
+ self.set_icon(icon_name)
self.connect('clicked', self._button_clicked_cb)
def set_icon(self, icon_name):
@@ -42,19 +43,14 @@ 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)
+ if self._palette and self._palette.is_up():
+ invoker = self._palette.props.invoker
+ invoker.draw_rectangle(event, self._palette)
gtk.ToolButton.do_expose_event(self, event)
@@ -62,8 +58,4 @@ class ToolButton(gtk.ToolButton):
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)
diff --git a/sugar/logger.py b/sugar/logger.py
index e857044..72c4a99 100644
--- a/sugar/logger.py
+++ b/sugar/logger.py
@@ -105,11 +105,7 @@ def _get_logs_dir():
def start(module_id):
# Only log if logging is set up for the activity
module_key = module_id.upper() + "_DEBUG"
- emulator = False
- if os.environ.has_key("SUGAR_EMULATOR"):
- if os.environ["SUGAR_EMULATOR"] == "yes":
- emulator = True
- if not os.environ.has_key(module_key) and not emulator:
+ if not os.environ.has_key(module_key) and not env.is_emulator():
return
log_writer = LogWriter(module_id)
diff --git a/sugar/objects/mime.py b/sugar/objects/mime.py
index 80eac9b..b933143 100644
--- a/sugar/objects/mime.py
+++ b/sugar/objects/mime.py
@@ -1,4 +1,5 @@
# Copyright (C) 2006-2007, Red Hat, Inc.
+# Copyright (C) 2007, One Laptop Per Child
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -20,7 +21,13 @@ import logging
from sugar import _sugarext
def get_for_file(file_name):
- return _sugarext.get_mime_type_for_file(file_name)
+ mime_type = _sugarext.get_mime_type_for_file(file_name)
+ if mime_type == 'application/octet-stream':
+ if _file_looks_like_text(file_name):
+ return 'text/plain'
+ else:
+ return 'application/octet-stream'
+ return mime_type
def get_from_file_name(file_name):
return _sugarext.get_mime_type_from_file_name(file_name)
@@ -51,13 +58,9 @@ def choose_most_significant(mime_types):
if 'text/uri-list' in mime_types:
return 'text/uri-list'
- for mime_category in ['image/', 'text/', 'application/']:
+ for mime_category in ['image/', 'application/']:
for mime_type in mime_types:
- # skip text/plain and text/html, these have lower priority.
- if mime_type in ['text/plain', 'text/html']:
- continue
-
if mime_type.startswith(mime_category):
# skip mozilla private types (second component starts with '_'
# or ends with '-priv')
@@ -70,6 +73,10 @@ def choose_most_significant(mime_types):
logging.debug('Choosed %r!' % mime_type)
return mime_type
+ if 'text/x-moz-url' in mime_types:
+ logging.debug('Choosed text/x-moz-url!')
+ return 'text/x-moz-url'
+
if 'text/html' in mime_types:
logging.debug('Choosed text/html!')
return 'text/html'
@@ -80,3 +87,22 @@ def choose_most_significant(mime_types):
logging.debug('Returning first: %r.' % mime_types[0])
return mime_types[0]
+
+def _file_looks_like_text(file_name):
+ f = open(file_name, 'r')
+ try:
+ sample = f.read(256)
+ finally:
+ f.close()
+
+ if '\000' in sample:
+ return False
+
+ for encoding in ('ascii', 'latin_1', 'utf_8', 'utf_16'):
+ try:
+ string = unicode(sample, encoding)
+ return True
+ except Exception, e:
+ pass
+
+ return False
diff --git a/sugar/presence/Makefile.am b/sugar/presence/Makefile.am
index 6314c1a..cb52a41 100644
--- a/sugar/presence/Makefile.am
+++ b/sugar/presence/Makefile.am
@@ -3,5 +3,6 @@ sugar_PYTHON = \
__init__.py \
activity.py \
buddy.py \
+ tubeconn.py \
presenceservice.py
diff --git a/sugar/presence/tubeconn.py b/sugar/presence/tubeconn.py
new file mode 100644
index 0000000..d1c1403
--- /dev/null
+++ b/sugar/presence/tubeconn.py
@@ -0,0 +1,107 @@
+# This should eventually land in telepathy-python, so has the same license:
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+__all__ = ('TubeConnection',)
+__docformat__ = 'reStructuredText'
+
+
+import logging
+
+from dbus.connection import Connection
+
+
+logger = logging.getLogger('telepathy.tubeconn')
+
+
+class TubeConnection(Connection):
+
+ def __new__(cls, conn, tubes_iface, tube_id, address=None,
+ group_iface=None, mainloop=None):
+ if address is None:
+ address = tubes_iface.GetDBusServerAddress(tube_id)
+ self = super(TubeConnection, cls).__new__(cls, address,
+ mainloop=mainloop)
+
+ self._tubes_iface = tubes_iface
+ self.tube_id = tube_id
+ self.participants = {}
+ self.bus_name_to_handle = {}
+ self._mapping_watches = []
+
+ if group_iface is None:
+ method = conn.GetSelfHandle
+ else:
+ method = group_iface.GetSelfHandle
+ method(reply_handler=self._on_get_self_handle_reply,
+ error_handler=self._on_get_self_handle_error)
+
+ return self
+
+ def _on_get_self_handle_reply(self, handle):
+ self.self_handle = handle
+ match = self._tubes_iface.connect_to_signal('DBusNamesChanged',
+ self._on_dbus_names_changed)
+ self._tubes_iface.GetDBusNames(self.tube_id,
+ reply_handler=self._on_get_dbus_names_reply,
+ error_handler=self._on_get_dbus_names_error)
+ self._dbus_names_changed_match = match
+
+ def _on_get_self_handle_error(self, e):
+ logging.basicConfig()
+ logger.error('GetSelfHandle failed: %s', e)
+
+ def close(self):
+ self._dbus_names_changed_match.remove()
+ self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
+ super(TubeConnection, self).close()
+
+ def _on_get_dbus_names_reply(self, names):
+ self._on_dbus_names_changed(self.tube_id, names, ())
+
+ def _on_get_dbus_names_error(self, e):
+ logging.basicConfig()
+ logger.error('GetDBusNames failed: %s', e)
+
+ def _on_dbus_names_changed(self, tube_id, added, removed):
+ if tube_id == self.tube_id:
+ for handle, bus_name in added:
+ if handle == self.self_handle:
+ # I've just joined - set my unique name
+ self.set_unique_name(bus_name)
+ self.participants[handle] = bus_name
+ self.bus_name_to_handle[bus_name] = handle
+
+ # call the callback while the removed people are still in
+ # participants, so their bus names are available
+ for callback in self._mapping_watches:
+ callback(added, removed)
+
+ for handle in removed:
+ bus_name = self.participants.pop(handle, None)
+ self.bus_name_to_handle.pop(bus_name, None)
+
+ def watch_participants(self, callback):
+ self._mapping_watches.append(callback)
+ if self.participants:
+ # GetDBusNames already returned: fake a participant add event
+ # immediately
+ added = []
+ for k, v in self.participants.iteritems():
+ added.append((k, v))
+ callback(added, [])