Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rwxr-xr-xbuild-snapshot.sh10
-rw-r--r--configure.ac2
-rw-r--r--services/clipboard/Makefile.am17
-rw-r--r--services/clipboard/objecttypeservice.py62
-rw-r--r--services/clipboard/org.laptop.ObjectTypeRegistry.service.in4
-rw-r--r--sugar/activity/activity.py75
-rw-r--r--sugar/datastore/datastore.py43
-rw-r--r--sugar/graphics/palette.py63
-rw-r--r--sugar/graphics/toolbutton.py3
-rw-r--r--sugar/logger.py68
-rw-r--r--sugar/objects/objecttype.py43
12 files changed, 323 insertions, 71 deletions
diff --git a/.gitignore b/.gitignore
index 8701f96..1820275 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ Makefile.in
*.lo
*.loT
.*.sw?
+*.service
# Absolute
@@ -51,9 +52,6 @@ browser/sugar-marshal.c
browser/sugar-marshal.h
browser/stamp-sugar-marshal.c
browser/stamp-sugar-marshal.h
-services/clipboard/org.laptop.Clipboard.service
-services/console/org.laptop.sugar.Console.service
-services/presence/org.laptop.Sugar.Presence.service
bin/sugar
shell/extensions/_extensions.c
data/sugar.gtkrc
diff --git a/build-snapshot.sh b/build-snapshot.sh
index 9606dc0..b0624f3 100755
--- a/build-snapshot.sh
+++ b/build-snapshot.sh
@@ -1,12 +1,10 @@
-VERSION=0.63
-DATE=`date +%Y%m%d`
-RELEASE=2.87
-TARBALL=sugar-$VERSION-$RELEASE.${DATE}git.tar.bz2
+VERSION=0.64
+ALPHATAG=`git-show-ref --hash=10 refs/heads/master`
+TARBALL=sugar-$VERSION-git$ALPHATAG.tar.bz2
rm sugar-$VERSION.tar.bz2
-XUL_SDK=/home/marco/sugar-jhbuild/build/lib/xulrunner-1.9a5pre-dev
-DISTCHECK_CONFIGURE_FLAGS="--with-libxul-sdk=$XUL_SDK" make distcheck
+make distcheck
mv sugar-$VERSION.tar.bz2 $TARBALL
scp $TARBALL mpg@devserv.devel.redhat.com:~
diff --git a/configure.ac b/configure.ac
index 3966d46..e346316 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-AC_INIT([Sugar],[0.63],[],[sugar])
+AC_INIT([Sugar],[0.64],[],[sugar])
AC_PREREQ([2.59])
diff --git a/services/clipboard/Makefile.am b/services/clipboard/Makefile.am
index a656ee2..7a957c2 100644
--- a/services/clipboard/Makefile.am
+++ b/services/clipboard/Makefile.am
@@ -1,17 +1,22 @@
servicedir = $(datadir)/dbus-1/services
-service_in_files = org.laptop.Clipboard.service.in
+
+service_in_files = \
+ org.laptop.Clipboard.service.in \
+ org.laptop.ObjectTypeRegistry.service.in
+
service_DATA = $(service_in_files:.service.in=.service)
$(service_DATA): $(service_in_files) Makefile
@sed -e "s|\@bindir\@|$(bindir)|" $< > $@
sugardir = $(pkgdatadir)/services/clipboard
-sugar_PYTHON = \
- __init__.py \
- clipboardobject.py \
- clipboardservice.py \
- typeregistry.py
+sugar_PYTHON = \
+ __init__.py \
+ clipboardobject.py \
+ clipboardservice.py \
+ objecttypeservice.py \
+ typeregistry.py
bin_SCRIPTS = sugar-clipboard
diff --git a/services/clipboard/objecttypeservice.py b/services/clipboard/objecttypeservice.py
new file mode 100644
index 0000000..bf9083f
--- /dev/null
+++ b/services/clipboard/objecttypeservice.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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
+
+from sugar.objects.objecttype import ObjectType
+
+_REGISTRY_IFACE = "org.laptop.ObjectTypeRegistry"
+_REGISTRY_PATH = "/org/laptop/ObjectTypeRegistry"
+
+class ObjectTypeRegistry(dbus.service.Object):
+ def __init__(self):
+ bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName(self._REGISTRY_IFACE, bus=bus)
+ dbus.service.Object.__init__(self, bus_name, self._REGISTRY_PATH)
+
+ self._types = {}
+
+ self._add_primitive('Text', _('Text'), 'object-text',
+ [ 'text/rtf' ])
+ self._add_primitive('Image', _('Image'), 'object-image',
+ [ 'image/png' ])
+
+ def _add_primitive(self, type_id, name, icon, mime_types):
+ object_type = ObjectType(type_id, name, icon, mime_types)
+ self._types.add(object_type)
+
+ def _get_type_for_mime(self, mime_type):
+ for object_type in self._types.values():
+ if mime_type in object_type.mime_types:
+ return object_type
+
+ @dbus.service.method(_CLIPBOARD_DBUS_INTERFACE,
+ in_signature="s", out_signature="a{sv}")
+ def GetType(self, type_id):
+ if self._types.has_key(type_id):
+ return self._types[type_id].to_dict()
+ else:
+ return []
+
+ @dbus.service.method(_CLIPBOARD_DBUS_INTERFACE,
+ in_signature="s", out_signature="a{sv}")
+ def GetTypeForMIME(self, mime_type):
+ object_type = self._get_type_for_mime(mime_type)
+ if object_type:
+ return object_type.to_dict()
+ else:
+ return []
diff --git a/services/clipboard/org.laptop.ObjectTypeRegistry.service.in b/services/clipboard/org.laptop.ObjectTypeRegistry.service.in
new file mode 100644
index 0000000..66477eb
--- /dev/null
+++ b/services/clipboard/org.laptop.ObjectTypeRegistry.service.in
@@ -0,0 +1,4 @@
+[D-BUS Service]
+Name = org.laptop.ObjectTypeRegistry
+Exec = @bindir@/sugar-clipboard
+
diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py
index dbcbd60..f1bda6c 100644
--- a/sugar/activity/activity.py
+++ b/sugar/activity/activity.py
@@ -42,14 +42,14 @@ class ActivityToolbar(gtk.Toolbar):
activity.connect('shared', self._activity_shared_cb)
activity.connect('joined', self._activity_shared_cb)
- if activity.jobject:
+ if activity.metadata:
self.title = gtk.Entry()
self.title.set_size_request(int(gtk.gdk.screen_width() / 6), -1)
- self.title.set_text(activity.jobject['title'])
+ self.title.set_text(activity.metadata['title'])
self.title.connect('focus-out-event', self._title_focus_out_event_cb)
self._add_widget(self.title)
- activity.jobject.connect('updated', self._jobject_updated_cb)
+ activity.metadata.connect('updated', self._jobject_updated_cb)
separator = gtk.SeparatorToolItem()
separator.props.draw = False
@@ -84,8 +84,8 @@ class ActivityToolbar(gtk.Toolbar):
self.title.set_text(jobject['title'])
def _title_focus_out_event_cb(self, entry, event):
- if self._activity.jobject['title'] != self.title.get_text():
- self._activity.jobject['title'] = self.title.get_text()
+ if self._activity.metadata['title'] != self.title.get_text():
+ self._activity.metadata['title'] = self.title.get_text()
self._activity.save()
def _add_widget(self, widget, expand=False):
@@ -199,54 +199,62 @@ class Activity(Window, gtk.Container):
self._bus = ActivityService(self)
if handle.object_id:
- self.jobject = datastore.get(handle.object_id)
- self.jobject.object_id = ''
- del self.jobject['ctime']
- del self.jobject['mtime']
+ self._jobject = datastore.get(handle.object_id)
+ self._jobject.object_id = ''
+ del self._jobject.metadata['ctime']
+ del self._jobject.metadata['mtime']
elif create_jobject:
logging.debug('Creating a jobject.')
- self.jobject = datastore.create()
- self.jobject['title'] = '%s %s' % (get_bundle_name(), 'Activity')
- self.jobject['activity'] = self.get_service_name()
- self.jobject['keep'] = '0'
- self.jobject['buddies'] = ''
- self.jobject['preview'] = ''
- self.jobject['icon-color'] = profile.get_color().to_string()
- self.jobject.file_path = ''
- datastore.write(self.jobject,
+ self._jobject = datastore.create()
+ self._jobject.metadata['title'] = '%s %s' % (get_bundle_name(), 'Activity')
+ self._jobject.metadata['activity'] = self.get_service_name()
+ self._jobject.metadata['keep'] = '0'
+ self._jobject.metadata['buddies'] = ''
+ self._jobject.metadata['preview'] = ''
+ self._jobject.metadata['icon-color'] = profile.get_color().to_string()
+ self._jobject.file_path = ''
+ datastore.write(self._jobject,
reply_handler=self._internal_jobject_create_cb,
error_handler=self._internal_jobject_error_cb)
else:
- self.jobject = None
+ self._jobject = None
def do_set_property(self, pspec, value):
if pspec.name == 'active':
if self._active != value:
self._active = value
- if not self._active and self.jobject:
+ if not self._active and self._jobject:
self.save()
def do_get_property(self, pspec):
if pspec.name == 'active':
return self._active
+ def set_canvas(self, canvas):
+ Window.set_canvas(self, canvas)
+ canvas.connect('map', self._canvas_map_cb)
+
+ def _canvas_map_cb(self, canvas):
+ if self._jobject and self._jobject.file_path:
+ self.read_file(self._jobject.file_path)
+
def _internal_jobject_create_cb(self):
pass
def _internal_jobject_error_cb(self, err):
logging.debug("Error creating activity datastore object: %s" % err)
- def read_file(self):
+ def read_file(self, file_path):
"""
Subclasses implement this method if they support resuming objects from
- the journal. Can access the object through the jobject attribute.
+ the journal. 'file_path' is the file to read from.
"""
raise NotImplementedError
- def write_file(self):
+ def write_file(self, file_path):
"""
Subclasses implement this method if they support saving data to objects
- in the journal. Can access the object through the jobject attribute.
+ in the journal. 'file_path' is the file to write to.
"""
raise NotImplementedError
@@ -259,11 +267,12 @@ class Activity(Window, gtk.Container):
def save(self):
"""Request that the activity is saved to the Journal."""
try:
- self.jobject.file_path = os.path.join('/tmp', '%i.txt' % time.time())
- self.write_file()
+ file_path = os.path.join('/tmp', '%i' % time.time())
+ self.write_file(file_path)
+ self._jobject.file_path = file_path
except NotImplementedError:
- self.jobject.file_path = ''
- datastore.write(self.jobject,
+ pass
+ datastore.write(self._jobject,
reply_handler=self._internal_save_cb,
error_handler=self._internal_save_error_cb)
@@ -322,7 +331,7 @@ class Activity(Window, gtk.Container):
self._shared_activity.leave()
def close(self):
- if self.jobject:
+ if self._jobject:
try:
self.save()
except:
@@ -330,6 +339,14 @@ class Activity(Window, gtk.Container):
raise
self.destroy()
+ def get_metadata(self):
+ if self._jobject:
+ return self._jobject.metadata
+ else:
+ return None
+
+ metadata = property(get_metadata, None)
+
def get_bundle_name():
"""Return the bundle name for the current process' bundle
"""
diff --git a/sugar/datastore/datastore.py b/sugar/datastore/datastore.py
index 82f5026..52d735f 100644
--- a/sugar/datastore/datastore.py
+++ b/sugar/datastore/datastore.py
@@ -19,38 +19,48 @@ import gobject
from sugar.datastore import dbus_helpers
-class DSObject(gobject.GObject):
+class DSMetadata(gobject.GObject):
__gsignals__ = {
'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([]))
}
- def __init__(self, object_id, metadata=None, file_path=None):
+ def __init__(self, props={}):
gobject.GObject.__init__(self)
- self.object_id = object_id
- self._metadata = metadata
- self._file_path = file_path
+ self._props = props
def __getitem__(self, key):
- return self.metadata[key]
+ return self._props[key]
def __setitem__(self, key, value):
- if not self.metadata.has_key(key) or self.metadata[key] != value:
- self.metadata[key] = value
+ if not self._props.has_key(key) or self._props[key] != value:
+ self._props[key] = value
self.emit('updated')
def __delitem__(self, key):
- del self.metadata[key]
+ del self._props[key]
+
+ def has_key(self, key):
+ return self._props.has_key(key)
+
+ def get_dictionary(self):
+ return self._props
+
+class DSObject:
+ def __init__(self, object_id, metadata=None, file_path=None):
+ self.object_id = object_id
+ self._metadata = metadata
+ self._file_path = file_path
def get_metadata(self):
if self._metadata is None and not self.object_id is None:
- self.set_metadata(dbus_helpers.get_properties(self.object_id))
+ metadata = DSMetadata(dbus_helpers.get_properties(self.object_id))
+ self._metadata = metadata
return self._metadata
def set_metadata(self, metadata):
if self._metadata != metadata:
self._metadata = metadata
- self.emit('updated')
metadata = property(get_metadata, set_metadata)
@@ -62,7 +72,6 @@ class DSObject(gobject.GObject):
def set_file_path(self, file_path):
if self._file_path != file_path:
self._file_path = file_path
- self.emit('updated')
file_path = property(get_file_path, set_file_path)
@@ -71,23 +80,23 @@ def get(object_id):
metadata = dbus_helpers.get_properties(object_id)
file_path = dbus_helpers.get_filename(object_id)
- ds_object = DSObject(object_id, metadata, file_path)
+ ds_object = DSObject(object_id, DSMetadata(metadata), file_path)
# TODO: register the object for updates
return ds_object
def create():
- return DSObject(object_id=None, metadata={}, file_path=None)
+ return DSObject(object_id=None, metadata=DSMetadata(), file_path=None)
def write(ds_object, reply_handler=None, error_handler=None):
logging.debug('datastore.write')
if ds_object.object_id:
dbus_helpers.update(ds_object.object_id,
- ds_object.metadata,
+ ds_object.metadata.get_dictionary(),
ds_object.file_path,
reply_handler=reply_handler,
error_handler=error_handler)
else:
- ds_object.object_id = dbus_helpers.create(ds_object.metadata,
+ ds_object.object_id = dbus_helpers.create(ds_object.metadata.get_dictionary(),
ds_object.file_path)
# TODO: register the object for updates
logging.debug('Written object %s to the datastore.' % ds_object.object_id)
@@ -114,7 +123,7 @@ def find(query, sorting=None, limit=None, offset=None, reply_handler=None,
object_id = props['uid']
del props['uid']
- ds_object = DSObject(object_id, props, file_path)
+ ds_object = DSObject(object_id, DSMetadata(props), file_path)
objects.append(ds_object)
return objects, total_count
diff --git a/sugar/graphics/palette.py b/sugar/graphics/palette.py
index c174e6e..3daeaad 100644
--- a/sugar/graphics/palette.py
+++ b/sugar/graphics/palette.py
@@ -17,6 +17,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk
+from gtk import gdk, keysyms
import gobject
import pango
@@ -49,6 +50,7 @@ class Palette(gtk.Window):
self._palette_label = gtk.Label()
self._palette_label.set_ellipsize(pango.ELLIPSIZE_START)
+ self._palette_label.show()
self._separator = gtk.HSeparator()
self._separator.hide()
@@ -58,7 +60,10 @@ class Palette(gtk.Window):
self._menu_bar.show()
self._content = gtk.HBox()
+ self._content.show()
+
self._button_bar = gtk.HButtonBox()
+ self._button_bar.show()
# Set main container
vbox = gtk.VBox(False, 0)
@@ -69,11 +74,25 @@ class Palette(gtk.Window):
vbox.pack_start(self._button_bar, True, True, self._PADDING)
vbox.show()
- # FIXME
- self.connect('focus_out_event', self._close_palette)
+ # Widget events
+ self.connect('motion-notify-event', self._mouse_over_widget)
+ self.connect('leave-notify-event', self._mouse_out_widget)
+ self.connect('button-press-event', self._close_palette)
+ self.connect('key-press-event', self._on_key_press_event)
self.set_border_width(self._WIN_BORDER)
self.add(vbox)
+
+ def _is_mouse_out(self, window, event):
+ # If we're clicking outside of the Palette
+ # return True
+ if (event.window != self.window or
+ (tuple(self.allocation.intersect(
+ gdk.Rectangle(x=int(event.x), y=int(event.y),
+ width=1, height=1)))) == (0, 0, 0, 0)):
+ return True
+ else:
+ return False
def do_set_property(self, pspec, value):
@@ -99,7 +118,7 @@ class Palette(gtk.Window):
elif self._alignment == ALIGNMENT_BOTTOM_RIGHT:
move_x = parent_rectangle.x - palette_width
move_y = window_axis[1] + parent_rectangle.y + parent_rectangle.height
-
+
elif self._alignment == ALIGNMENT_LEFT_BOTTOM:
move_x = parent_rectangle.x - palette_width
move_y = palette_rectangle.y
@@ -126,8 +145,9 @@ class Palette(gtk.Window):
self.move(move_x, move_y)
- def _close_palette(self, widget, event):
- self.destroy()
+ def _close_palette(self, widget=None, event=None):
+ gtk.gdk.pointer_ungrab()
+ self.hide()
def set_primary_state(self, label, accel_path=None):
if accel_path != None:
@@ -148,9 +168,42 @@ class Palette(gtk.Window):
widget.show()
def append_button(self, button):
+ button.connect('released', self._close_palette)
self._button_bar.pack_start(button, True, True, self._PADDING)
button.show()
def display(self, button):
self.show()
self.set_position()
+ self._pointer_grab()
+
+ def _pointer_grab(self):
+ gtk.gdk.pointer_grab(self.window, owner_events=False,
+ event_mask=gtk.gdk.BUTTON_PRESS_MASK |
+ gtk.gdk.BUTTON_RELEASE_MASK |
+ gtk.gdk.ENTER_NOTIFY_MASK |
+ gtk.gdk.LEAVE_NOTIFY_MASK |
+ gtk.gdk.POINTER_MOTION_MASK)
+
+ gdk.keyboard_grab(self.window, False)
+
+ def _mouse_out_widget(self, widget, event):
+ if (widget == self) and self._is_mouse_out(widget, event):
+ self._pointer_grab()
+
+ def _mouse_over_widget(self, widget, event):
+ gtk.gdk.pointer_ungrab()
+
+ def _on_key_press_event(self, window, event):
+
+ # Escape or Alt+Up: Close
+ # Enter, Return or Space: Select
+
+ keyval = event.keyval
+ state = event.state & gtk.accelerator_get_default_mod_mask()
+ if (keyval == keysyms.Escape or
+ ((keyval == keysyms.Up or keyval == keysyms.KP_Up) and
+ state == gdk.MOD1_MASK)):
+ self._close_palette()
+ elif keyval == keysyms.Tab:
+ self._close_palette()
diff --git a/sugar/graphics/toolbutton.py b/sugar/graphics/toolbutton.py
index 51edc2e..a2a6ce2 100644
--- a/sugar/graphics/toolbutton.py
+++ b/sugar/graphics/toolbutton.py
@@ -36,4 +36,5 @@ class ToolButton(gtk.ToolButton):
palette.props.alignment = ALIGNMENT_BOTTOM_LEFT
def set_tooltip(self, text):
- pass
+ tp = gtk.Tooltips()
+ self.set_tooltip(tp, text, text)
diff --git a/sugar/logger.py b/sugar/logger.py
index fa2e28f..495d7a0 100644
--- a/sugar/logger.py
+++ b/sugar/logger.py
@@ -21,6 +21,7 @@ import os
import logging
import traceback
from cStringIO import StringIO
+import time
from sugar import env
@@ -113,6 +114,67 @@ def start(module_id):
sys.excepthook = __exception_handler
def cleanup():
- logs_dir = _get_logs_dir()
- for f in os.listdir(logs_dir):
- os.remove(os.path.join(logs_dir, f))
+ logs_dir = _get_logs_dir()
+
+ # File extension for backed up logfiles.
+
+ file_suffix = int(time.time())
+
+ # Absolute directory path where to store old logfiles.
+ # It will be created recursivly if it's not present.
+
+ backup_dirpath = os.path.join(logs_dir, 'old')
+
+ # How many versions shall be backed up of every logfile?
+
+ num_backup_versions = 4
+
+ # Make sure the backup location for old log files exists
+
+ if not os.path.exists(backup_dirpath):
+ os.makedirs(backup_dirpath)
+
+ # Iterate over every item in 'logs' directory
+
+ for filename in os.listdir(logs_dir):
+
+ old_filepath = os.path.join(logs_dir, filename)
+
+ if os.path.isfile(old_filepath):
+
+ # Backup every file
+
+ new_filename = filename + '.' + str(file_suffix)
+ new_filepath = os.path.join(backup_dirpath, new_filename)
+ os.rename(old_filepath, new_filepath)
+
+ backup_map = {}
+
+ # Temporarily map all backup logfiles
+
+ for filename in os.listdir(backup_dirpath):
+
+ # Remove the 'file_suffix' from the filename.
+
+ end = filename.rfind(".")
+ key = filename[0:end].lower()
+ key = key.replace(".", "_")
+
+ if key not in backup_map:
+ backup_map[key] = []
+
+ backup_list = backup_map[key]
+
+ backup_list.append( os.path.join(backup_dirpath, filename) )
+
+ # Only keep 'num_backup_versions' versions of every logfile.
+ # Remove the others.
+
+ for key in backup_map:
+ backup_list = backup_map[key]
+ backup_list.sort()
+ backup_list.reverse()
+
+ for i in range(num_backup_versions, len(backup_list)):
+ os.remove(backup_list[i])
+
diff --git a/sugar/objects/objecttype.py b/sugar/objects/objecttype.py
new file mode 100644
index 0000000..81b8ec1
--- /dev/null
+++ b/sugar/objects/objecttype.py
@@ -0,0 +1,43 @@
+_SERVICE = "org.laptop.ObjectTypeRegistry"
+_PATH = "/org/laptop/ObjectTypeRegistry"
+_IFACE = "org.laptop.ObjectTypeRegistry"
+
+def _object_type_from_dict(info_dict):
+ if info_dict:
+ return ObjectType(info_dict['type_id'],
+ info_dict['name'],
+ info_dict['icon'])
+ else:
+ return None
+
+class ObjectType(object):
+ def __init__(self, type_id, name, icon, mime_types):
+ self.type_id = type_id
+ self.name = name
+ self.icon = icon
+ self.mime_types = []
+
+ def to_dict(self):
+ return { 'type_id' : self.type_id,
+ 'name' : self.name,
+ 'icon' : self.icon
+ }
+
+class ObjectTypeRegistry(object):
+ def __init__(self):
+ bus = dbus.SessionBus()
+ bus_object = bus.get_object(_SERVICE, _PATH)
+ self._registry = dbus.Interface(bus_object, _IFACE)
+
+ def get_type(type_id):
+ type_dict = self._registry.GetType(type_id)
+ return _object_type_from_dict(type_dict)
+
+ def get_type_for_mime(mime_type):
+ type_dict = self._registry.GetTypeForMime(type_id)
+ return _object_type_from_dict(type_dict)
+
+_registry = ObjectRegistry()
+
+def get_registry():
+ return _registry