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

This commit is contained in:
John (J5) Palmieri
2007-08-20 14:41:03 -04:00
74 changed files with 2170 additions and 1895 deletions
+75
View File
@@ -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")
)
+2
View File
@@ -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
+2
View File
@@ -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");
+19 -3
View File
@@ -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
+1 -1
View File
@@ -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
+44 -12
View File
@@ -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':
+5 -5
View File
@@ -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),)
+3
View File
@@ -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()
+23 -15
View File
@@ -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'))
+7 -7
View File
@@ -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')
+1 -1
View File
@@ -9,7 +9,7 @@ sugar_PYTHON = \
combobox.py \
icon.py \
iconbutton.py \
menuitem.py \
iconentry.py \
notebook.py \
objectchooser.py \
radiotoolbutton.py \
+1 -1
View File
@@ -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()
+15 -3
View File
@@ -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]
+19 -8
View File
@@ -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)
+45
View File
@@ -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)
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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
+60 -34
View File
@@ -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):
style = self._widget.style
if palette.is_up():
gap = _calculate_gap(self.get_rect(), palette.get_rect())
def has_rectangle_gap(self):
return True
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,
def draw_rectangle(self, event, palette):
style = self._widget.style
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)
+13 -4
View File
@@ -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')
+4 -14
View File
@@ -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)
+2
View File
@@ -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')
+4 -14
View File
@@ -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)
+5 -13
View File
@@ -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)
+1 -5
View File
@@ -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)
+32 -6
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
@@ -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
+1
View File
@@ -3,5 +3,6 @@ sugar_PYTHON = \
__init__.py \
activity.py \
buddy.py \
tubeconn.py \
presenceservice.py
+107
View File
@@ -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, [])