From f50d9f5f9a972f7149c4a57f225e00d7e48834c6 Mon Sep 17 00:00:00 2001 From: Tomeu Vizoso Date: Fri, 5 Jan 2007 21:13:46 +0100 Subject: [PATCH] Added icon, title and preview for clipboard objects. Also allow for opening docs and rtfs from the web. --- services/clipboard/Makefile.am | 3 +- services/clipboard/clipboardobject.py | 19 ++- services/clipboard/clipboardservice.py | 61 +++++--- services/clipboard/typeregistry.py | 169 +++++++++++++++++++++++ shell/view/clipboardicon.py | 41 ++++-- shell/view/clipboardmenu.py | 7 +- shell/view/frame/clipboardbox.py | 15 +- shell/view/frame/clipboardpanelwindow.py | 2 +- sugar/clipboard/clipboardservice.py | 39 +++--- sugar/graphics/canvasicon.py | 6 + sugar/graphics/menu.py | 10 +- 11 files changed, 308 insertions(+), 64 deletions(-) create mode 100644 services/clipboard/typeregistry.py diff --git a/services/clipboard/Makefile.am b/services/clipboard/Makefile.am index 72f8593e..a656ee2f 100644 --- a/services/clipboard/Makefile.am +++ b/services/clipboard/Makefile.am @@ -9,7 +9,8 @@ sugardir = $(pkgdatadir)/services/clipboard sugar_PYTHON = \ __init__.py \ clipboardobject.py \ - clipboardservice.py + clipboardservice.py \ + typeregistry.py bin_SCRIPTS = sugar-clipboard diff --git a/services/clipboard/clipboardobject.py b/services/clipboard/clipboardobject.py index fd15363c..09aa7718 100644 --- a/services/clipboard/clipboardobject.py +++ b/services/clipboard/clipboardobject.py @@ -1,3 +1,5 @@ +import typeregistry + class ClipboardObject: def __init__(self, id, name): @@ -8,10 +10,23 @@ class ClipboardObject: def get_id(self): return self._id + + def _get_type_info(self): + type_registry = typeregistry.get_instance() + return type_registry.get_type(self._formats) def get_name(self): - return self._name + if self._name: + return self._name + else: + return self._get_type_info().get_name() + def get_icon(self): + return self._get_type_info().get_icon() + + def get_preview(self): + return self._get_type_info().get_preview() + def get_percent(self): return self._percent @@ -23,7 +38,7 @@ class ClipboardObject: def get_formats(self): return self._formats - + class Format: def __init__(self, type, data, on_disk): diff --git a/services/clipboard/clipboardservice.py b/services/clipboard/clipboardservice.py index 02ee7ac6..d9da8cc2 100644 --- a/services/clipboard/clipboardservice.py +++ b/services/clipboard/clipboardservice.py @@ -19,8 +19,15 @@ import gobject import dbus import dbus.service from sugar import env +from sugar import util from clipboardobject import ClipboardObject, Format +NAME_KEY = 'NAME' +PERCENT_KEY = 'PERCENT' +ICON_KEY = 'ICON' +PREVIEW_KEY = 'PREVIEW' +FORMATS_KEY = 'FORMATS' + class ClipboardDBusServiceHelper(dbus.service.Object): _CLIPBOARD_DBUS_INTERFACE = "org.laptop.Clipboard" @@ -43,21 +50,20 @@ class ClipboardDBusServiceHelper(dbus.service.Object): logging.debug('Added object ' + object_id + ' with name ' + name) @dbus.service.method(_CLIPBOARD_DBUS_INTERFACE, - in_signature="ssayb", out_signature="") + in_signature="ssayb", out_signature="", byte_arrays=True) def add_object_format(self, object_id, format_type, data, on_disk): - - # FIXME: Take it out when using the 0.80 dbus bindings - s = "" - for i in data: - s += chr(i) - cb_object = self._objects[object_id] - cb_object.add_format(Format(format_type, s, on_disk)) - + cb_object.add_format(Format(format_type, data, on_disk)) + if on_disk: - logging.debug('Added format of type ' + format_type + ' with path at ' + s) + logging.debug('Added format of type ' + format_type + ' with path at ' + data) else: logging.debug('Added in-memory format of type ' + format_type + '.') + + self.object_state_changed(object_id, {NAME_KEY: cb_object.get_name(), + PERCENT_KEY: cb_object.get_percent(), + ICON_KEY: cb_object.get_icon(), + PREVIEW_KEY: cb_object.get_preview()}) @dbus.service.method(_CLIPBOARD_DBUS_INTERFACE, in_signature="s", out_signature="") @@ -68,30 +74,39 @@ class ClipboardDBusServiceHelper(dbus.service.Object): @dbus.service.method(_CLIPBOARD_DBUS_INTERFACE, in_signature="si", out_signature="") - def set_object_state(self, object_id, percent): + def set_object_percent(self, object_id, percent): cb_object = self._objects[object_id] cb_object.set_percent(percent) - self.object_state_changed(object_id, percent) - logging.debug('Changed object with object_id ' + object_id + ' with percent ' + str(percent)) + self.object_state_changed(object_id, {NAME_KEY: cb_object.get_name(), + PERCENT_KEY: percent, + ICON_KEY: cb_object.get_icon(), + PREVIEW_KEY: cb_object.get_preview()}) + + logging.debug('Changed object with object_id ' + object_id + + ' with percent ' + str(percent)) @dbus.service.method(_CLIPBOARD_DBUS_INTERFACE, - in_signature="s", out_signature="as") - def get_object_format_types(self, object_id): + in_signature="s", out_signature="a{sv}") + def get_object(self, object_id): cb_object = self._objects[object_id] formats = cb_object.get_formats() - array = [] + format_types = [] for type, format in formats.iteritems(): - array.append(type) + format_types.append(type) - return array + result_dict = {NAME_KEY: cb_object.get_name(), + PERCENT_KEY: cb_object.get_percent(), + ICON_KEY: cb_object.get_icon(), + PREVIEW_KEY: cb_object.get_preview(), + FORMATS_KEY: format_types} + return result_dict @dbus.service.method(_CLIPBOARD_DBUS_INTERFACE, in_signature="ss", out_signature="ay") - def get_object_data(self, object_id, format_type): + def get_object_data(self, object_id, format_type): cb_object = self._objects[object_id] formats = cb_object.get_formats() - return formats[format_type].get_data() # dbus signals @@ -103,8 +118,8 @@ class ClipboardDBusServiceHelper(dbus.service.Object): def object_deleted(self, object_id): pass - @dbus.service.signal(_CLIPBOARD_DBUS_INTERFACE, signature="si") - def object_state_changed(self, object_id, percent): + @dbus.service.signal(_CLIPBOARD_DBUS_INTERFACE, signature="sa{sv}") + def object_state_changed(self, object_id, values): pass class ClipboardService(object): @@ -115,5 +130,5 @@ class ClipboardService(object): loop = gobject.MainLoop() try: loop.run() - except idboardInterrupt: + except KeyboardInterrupt: print 'Ctrl+C pressed, exiting...' diff --git a/services/clipboard/typeregistry.py b/services/clipboard/typeregistry.py new file mode 100644 index 00000000..9666c9b5 --- /dev/null +++ b/services/clipboard/typeregistry.py @@ -0,0 +1,169 @@ +import logging + +class FileType: + def __init__(self, formats): + self._formats = formats + + def get_name(self): + raise NotImplementedError + + def get_icon(self): + raise NotImplementedError + + def get_preview(self): + raise NotImplementedError + + def matches_mime_type(cls, mime_type): + raise NotImplementedError + matches_mime_type = classmethod(matches_mime_type) + +class TextFileType(FileType): + + _types = set(['text/plain', 'UTF8_STRING', 'STRING']) + + def get_name(self): + return 'Text snippet' + + def get_icon(self): + return 'activity-xbook' + + def get_preview(self): + for format, data in self._formats.iteritems(): + if format in TextFileType._types: + return str(data.get_data()) + + return '' + + def matches_mime_type(cls, mime_type): + return mime_type in cls._types + matches_mime_type = classmethod(matches_mime_type) + +class ImageFileType(FileType): + + _types = set(['image/jpeg', 'image/gif', 'image/png', 'image/tiff']) + + def get_name(self): + return 'Image' + + def get_icon(self): + return 'activity-sketch' + + def get_preview(self): + return '' + + def matches_mime_type(cls, mime_type): + return mime_type in cls._types + matches_mime_type = classmethod(matches_mime_type) + +class UriFileType(FileType): + + _types = set(['_NETSCAPE_URL']) + + def get_name(self): + return 'URL' + + def get_icon(self): + return 'activity-web' + + def get_preview(self): + for format, data in self._formats.iteritems(): + if format in UriFileType._types: + string = data.get_data() + title = string.split("\n")[1] + return title + + return '' + + def matches_mime_type(cls, mime_type): + return mime_type in cls._types + matches_mime_type = classmethod(matches_mime_type) + +class PdfFileType(FileType): + + _types = set(['application/pdf']) + + def get_name(self): + return 'PDF file' + + def get_icon(self): + return 'activity-xbook' + + def get_preview(self): + return '' + + def matches_mime_type(cls, mime_type): + return mime_type in cls._types + matches_mime_type = classmethod(matches_mime_type) + +class MsWordFileType(FileType): + + _types = set(['application/msword']) + + def get_name(self): + return 'MS Word file' + + def get_icon(self): + return 'activity-abiword' + + def get_preview(self): + return '' + + def matches_mime_type(cls, mime_type): + return mime_type in cls._types + matches_mime_type = classmethod(matches_mime_type) + +class RtfFileType(FileType): + + _types = set(['application/rtf', 'text/rtf']) + + def get_name(self): + return 'RTF file' + + def get_icon(self): + return 'activity-abiword' + + def get_preview(self): + return '' + + def matches_mime_type(cls, mime_type): + return mime_type in cls._types + matches_mime_type = classmethod(matches_mime_type) + +class UnknownFileType(FileType): + def get_name(self): + return 'Object' + + def get_icon(self): + return 'stock-missing' + + def get_preview(self): + return '' + + def matches_mime_type(cls, mime_type): + return true + matches_mime_type = classmethod(matches_mime_type) + +class TypeRegistry: + def __init__(self): + self._types = [] + self._types.append(PdfFileType) + self._types.append(MsWordFileType) + self._types.append(RtfFileType) + self._types.append(UriFileType) + self._types.append(ImageFileType) + self._types.append(TextFileType) + + def get_type(self, formats): + for file_type in self._types: + for format, data in formats.iteritems(): + if file_type.matches_mime_type(format): + return file_type(formats) + + return UnknownFileType(formats) + +_type_registry = None +def get_instance(): + global _type_registry + if not _type_registry: + _type_registry = TypeRegistry() + return _type_registry diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py index 25a14e9d..ade37bd4 100644 --- a/shell/view/clipboardicon.py +++ b/shell/view/clipboardicon.py @@ -1,3 +1,5 @@ +import logging + from sugar.graphics.menuicon import MenuIcon from view.clipboardmenu import ClipboardMenu from sugar.activity import ActivityFactory @@ -6,31 +8,54 @@ from sugar.clipboard import clipboardservice class ClipboardIcon(MenuIcon): def __init__(self, menu_shell, object_id, name): - MenuIcon.__init__(self, menu_shell, icon_name='activity-xbook') + MenuIcon.__init__(self, menu_shell) self._object_id = object_id self._name = name self._percent = 0 + self._preview = None self.connect('activated', self._icon_activated_cb) self._menu = None def create_menu(self): - self._menu = ClipboardMenu(self._name, self._percent) + self._menu = ClipboardMenu(self._name, self._percent, self._preview) self._menu.connect('action', self._popup_action_cb) return self._menu - def set_percent(self, percent): + def set_state(self, name, percent, icon_name, preview): + self._name = name self._percent = percent + self._preview = preview + self.set_icon_name(icon_name) if self._menu: - self._menu.set_percent(percent) + self._menu.set_state(name, percent, preview) + + def _get_activity_for_mime_type(self, mime_type): + # FIXME: We should use some kind of registry that could be extended by + # newly installed activities. + if mime_type == "application/pdf": + return "org.laptop.sugar.Xbook" + elif mime_type in ["application/msword", "text/rtf", "application/rtf"]: + return "org.laptop.AbiWordActivity" + else: + return None def _icon_activated_cb(self, icon): if self._percent == 100: cb_service = clipboardservice.get_instance() - format_types = cb_service.get_object_format_types(self._object_id) - if len(format_types) > 0 and format_types[0] == "application/pdf": - activity = ActivityFactory.create("org.laptop.sugar.Xbook") - activity.execute("open_document", [self._object_id]) + + (name, percent, icon, preview, format_types) = \ + cb_service.get_object(self._object_id) + if format_types: + logging.debug("_icon_activated_cb: " + self._object_id) + + activity_id = self._get_activity_for_mime_type(format_types[0]) + + if activity_id: + activity = ActivityFactory.create(activity_id) + activity.start() + activity.execute("open_document", [self._object_id]) + def _popup_action_cb(self, popup, action): self.popdown() diff --git a/shell/view/clipboardmenu.py b/shell/view/clipboardmenu.py index 3d555e1e..bcd16cb2 100644 --- a/shell/view/clipboardmenu.py +++ b/shell/view/clipboardmenu.py @@ -19,7 +19,7 @@ class ClipboardMenu(Menu): ACTION_SHARE = 1 ACTION_STOP_DOWNLOAD = 2 - def __init__(self, name, percent): + def __init__(self, name, percent, preview): Menu.__init__(self, name) if percent < 100: @@ -31,6 +31,8 @@ class ClipboardMenu(Menu): self._remove_icon = None self._stop_icon = None + self.add_item(preview) + self._update_icons(percent) def _update_icons(self, percent): @@ -51,7 +53,8 @@ class ClipboardMenu(Menu): self.remove_action(self._remove_icon) self._remove_icon = None - def set_percent(self, percent): + def set_state(self, name, percent, preview): + self.set_title(name) if self._progress_bar: self._progress_bar.set_property('percent', percent) self._update_icons(percent) diff --git a/shell/view/frame/clipboardbox.py b/shell/view/frame/clipboardbox.py index 7059214c..ac6798e5 100644 --- a/shell/view/frame/clipboardbox.py +++ b/shell/view/frame/clipboardbox.py @@ -88,9 +88,10 @@ class ClipboardBox(hippo.CanvasBox): del self._icons[object_id] logging.debug('ClipboardBox: ' + object_id + ' was deleted.') - def _object_state_changed_cb(self, cb_service, object_id, percent): + def _object_state_changed_cb(self, cb_service, object_id, name, percent, + icon_name, preview): icon = self._icons[object_id] - icon.set_percent(percent) + icon.set_state(name, percent, icon_name, preview) logging.debug('ClipboardBox: ' + object_id + ' state was changed.') def drag_motion_cb(self, widget, context, x, y, time): @@ -104,13 +105,13 @@ class ClipboardBox(hippo.CanvasBox): self._context_map.add_context(context, object_id, len(context.targets)) cb_service = clipboardservice.get_instance() - cb_service.add_object(object_id, "name") + cb_service.add_object(object_id, name="") for target in context.targets: if str(target) not in ('TIMESTAMP', 'TARGETS', 'MULTIPLE'): widget.drag_get_data(context, target, time) - cb_service.set_object_state(object_id, percent = 100) + cb_service.set_object_percent(object_id, percent = 100) return True @@ -186,9 +187,11 @@ class ClipboardBox(hippo.CanvasBox): def _get_targets_for_dnd(self, object_id): cb_service = clipboardservice.get_instance() - format_types = cb_service.get_object_format_types(object_id) - targets = [] + + (name, percent, icon, preview, format_types) = \ + cb_service.get_object(object_id) + targets = [] for format_type in format_types: targets.append((format_type, 0, 0)) diff --git a/shell/view/frame/clipboardpanelwindow.py b/shell/view/frame/clipboardpanelwindow.py index d46bf6a8..fd0d7f01 100644 --- a/shell/view/frame/clipboardpanelwindow.py +++ b/shell/view/frame/clipboardpanelwindow.py @@ -45,7 +45,7 @@ class ClipboardPanelWindow(PanelWindow): cb_service = clipboardservice.get_instance() cb_service.add_object(key, "name") - cb_service.set_object_state(key, percent = 100) + cb_service.set_object_percent(key, percent = 100) targets = clipboard.wait_for_targets() for target in targets: diff --git a/sugar/clipboard/clipboardservice.py b/sugar/clipboard/clipboardservice.py index 425a16d0..84557518 100644 --- a/sugar/clipboard/clipboardservice.py +++ b/sugar/clipboard/clipboardservice.py @@ -2,7 +2,11 @@ import logging import dbus import gobject -from sugar import util +NAME_KEY = 'NAME' +PERCENT_KEY = 'PERCENT' +ICON_KEY = 'ICON' +PREVIEW_KEY = 'PREVIEW' +FORMATS_KEY = 'FORMATS' DBUS_SERVICE = "org.laptop.Clipboard" DBUS_INTERFACE = "org.laptop.Clipboard" @@ -16,7 +20,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, int])), + ([str, str, int, str, str])), } def __init__(self): @@ -58,8 +62,9 @@ class ClipboardService(gobject.GObject): def _object_deleted_cb(self, object_id): self.emit('object-deleted', object_id) - def _object_state_changed_cb(self, object_id, percent): - self.emit('object-state-changed', object_id, percent) + def _object_state_changed_cb(self, object_id, values): + self.emit('object-state-changed', object_id, values[NAME_KEY], + values[PERCENT_KEY], values[ICON_KEY], values[PREVIEW_KEY]) def add_object(self, object_id, name): self._dbus_service.add_object(object_id, name) @@ -67,27 +72,25 @@ class ClipboardService(gobject.GObject): def add_object_format(self, object_id, formatType, data, on_disk): self._dbus_service.add_object_format(object_id, formatType, - dbus.types.ByteArray(data), + data, on_disk) def delete_object(self, object_id): self._dbus_service.delete_object(object_id) - def set_object_state(self, object_id, percent): - self._dbus_service.set_object_state(object_id, percent) + def set_object_percent(self, object_id, percent): + self._dbus_service.set_object_percent(object_id, percent) - def get_object_format_types(self, object_id): - return self._dbus_service.get_object_format_types(object_id) - - def get_object_data(self, object_id, formatType): - data = self._dbus_service.get_object_data(object_id, formatType) + def get_object(self, object_id): + result_dict = self._dbus_service.get_object(object_id,) - # FIXME: Take it out when using the 0.80 dbus bindings - s = "" - for i in data: - s += chr(i) - - return s + return (result_dict[NAME_KEY], result_dict[PERCENT_KEY], + result_dict[ICON_KEY], result_dict[PREVIEW_KEY], + result_dict[FORMATS_KEY]) + + def get_object_data(self, object_id, formatType): + return self._dbus_service.get_object_data(object_id, formatType, + byte_arrays=True) _clipboard_service = None def get_instance(): diff --git a/sugar/graphics/canvasicon.py b/sugar/graphics/canvasicon.py index 06aff7b1..f47f06bc 100644 --- a/sugar/graphics/canvasicon.py +++ b/sugar/graphics/canvasicon.py @@ -16,6 +16,7 @@ # Boston, MA 02111-1307, USA. import re +import logging import gobject import gtk @@ -156,3 +157,8 @@ class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem): def _button_press_event_cb(self, item, event): item.emit_activated() + + def set_icon_name(self, icon_name): + self._icon_name = icon_name + self._buffer = None + self.emit_paint_needed(0, 0, -1, -1) diff --git a/sugar/graphics/menu.py b/sugar/graphics/menu.py index 5b68d61e..153c5bc1 100644 --- a/sugar/graphics/menu.py +++ b/sugar/graphics/menu.py @@ -76,7 +76,7 @@ class Menu(gtk.Window): orientation=hippo.ORIENTATION_HORIZONTAL) self._root.append(self._action_box) - def add_item(self, label, action_id): + def add_item(self, label, action_id=None): if not self._item_box: self._create_item_box() @@ -84,8 +84,9 @@ class Menu(gtk.Window): style.apply_stylesheet(text, 'menu.Item') # FIXME need a way to make hippo items activable in python - text.connect('button-press-event', self._item_clicked_cb, action_id) - #text.connect('activated', self._action_clicked_cb, action_id) + if action_id: + text.connect('button-press-event', self._item_clicked_cb, action_id) + #text.connect('activated', self._action_clicked_cb, action_id) self._item_box.append(text) @@ -105,3 +106,6 @@ class Menu(gtk.Window): def _action_clicked_cb(self, icon, action): self.emit('action', action) + + def set_title(self, title): + self._title_item.set_text(title)