From 2148b47c2a5910eeb14fe827059585bef09fc111 Mon Sep 17 00:00:00 2001 From: Simon Schampijer Date: Fri, 12 Aug 2011 17:58:02 +0000 Subject: Add duplicate functionality to the Journal and enhance copy functionality This patch adds a duplicate option to the Journal entry palette and the entry detail view. This will replace the keep button functionality from the activity toolbar. The keep button will be deprecated in another patch for the toolkit. The copy option which copied previously to the clipboard by default has been enhanced to allow: copying to the clipboard, copying to an external device, copying from an external device to the Journal and copying between external devices. Copying to the clipboard is now a visible option in the menu. In the detail view the palette pops up when doing a left click and showing the available options instead of copying directly to the clipboard. The design discussion has been taking place at: http://lists.sugarlabs.org/archive/sugar-devel/2011-May/031316.html Signed-off-by: Simon Schampijer Acked-By: Sascha Silbe --- (limited to 'src') diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py index a33038a..bb1c7f6 100644 --- a/src/jarabe/journal/journalactivity.py +++ b/src/jarabe/journal/journalactivity.py @@ -171,6 +171,7 @@ class JournalActivity(JournalWindow): self._list_view = ListView() self._list_view.connect('detail-clicked', self.__detail_clicked_cb) self._list_view.connect('clear-clicked', self.__clear_clicked_cb) + self._list_view.connect('volume-error', self.__volume_error_cb) self._main_view.pack_start(self._list_view) self._list_view.show() diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py index d825bc9..77334b4 100644 --- a/src/jarabe/journal/journaltoolbox.py +++ b/src/jarabe/journal/journaltoolbox.py @@ -26,6 +26,7 @@ import gobject import gio import gtk +from sugar.graphics.palette import Palette from sugar.graphics.toolbox import Toolbox from sugar.graphics.toolcombobox import ToolComboBox from sugar.graphics.toolbutton import ToolButton @@ -37,11 +38,12 @@ from sugar.graphics.xocolor import XoColor from sugar.graphics import iconentry from sugar.graphics import style from sugar import mime -from sugar import profile from jarabe.model import bundleregistry from jarabe.journal import misc from jarabe.journal import model +from jarabe.journal.palettes import ClipboardMenu +from jarabe.journal.palettes import VolumeMenu _AUTOSEARCH_TIMEOUT = 1000 @@ -370,19 +372,23 @@ class EntryToolbar(gtk.Toolbar): self.add(self._resume) self._resume.show() - self._copy = ToolButton() - client = gconf.client_get_default() color = XoColor(client.get_string('/desktop/sugar/user/color')) + self._copy = ToolButton() icon = Icon(icon_name='edit-copy', xo_color=color) self._copy.set_icon_widget(icon) icon.show() - - self._copy.set_tooltip(_('Copy')) + self._copy.set_tooltip(_('Copy to')) self._copy.connect('clicked', self._copy_clicked_cb) self.add(self._copy) self._copy.show() + self._duplicate = ToolButton() + icon = Icon(icon_name='edit-duplicate', xo_color=color) + self._copy.set_icon_widget(icon) + self._duplicate.set_tooltip(_('Duplicate')) + self.add(self._duplicate) + separator = gtk.SeparatorToolItem() self.add(separator) separator.show() @@ -396,25 +402,24 @@ class EntryToolbar(gtk.Toolbar): def set_metadata(self, metadata): self._metadata = metadata self._refresh_copy_palette() + self._refresh_duplicate_palette() self._refresh_resume_palette() def _resume_clicked_cb(self, button): misc.resume(self._metadata) def _copy_clicked_cb(self, button): - clipboard = gtk.Clipboard() - clipboard.set_with_data([('text/uri-list', 0, 0)], - self.__clipboard_get_func_cb, - self.__clipboard_clear_func_cb) - - def __clipboard_get_func_cb(self, clipboard, selection_data, info, data): - # Get hold of a reference so the temp file doesn't get deleted - self._temp_file_path = model.get_file(self._metadata['uid']) - selection_data.set_uris(['file://' + self._temp_file_path]) - - def __clipboard_clear_func_cb(self, clipboard, data): - # Release and delete the temp file - self._temp_file_path = None + button.palette.popup(immediate=True, state=Palette.SECONDARY) + + def _duplicate_clicked_cb(self, button): + file_path = model.get_file(self._metadata['uid']) + try: + model.copy(self._metadata, '/') + except IOError, e: + logging.exception('Error while copying the entry.') + self.emit('volume-error', + _('Error while copying the entry. %s') % (e.strerror, ), + _('Error')) def _erase_button_clicked_cb(self, button): registry = bundleregistry.get_registry() @@ -427,24 +432,6 @@ class EntryToolbar(gtk.Toolbar): def _resume_menu_item_activate_cb(self, menu_item, service_name): misc.resume(self._metadata, service_name) - def _copy_menu_item_activate_cb(self, menu_item, mount_point): - file_path = model.get_file(self._metadata['uid']) - - if not file_path or not os.path.exists(file_path): - logging.warn('Entries without a file cannot be copied.') - self.emit('volume-error', - _('Entries without a file cannot be copied.'), - _('Warning')) - return - - try: - model.copy(self._metadata, mount_point) - except IOError, e: - logging.exception('Error while copying the entry. %s', e.strerror) - self.emit('volume-error', - _('Error while copying the entry. %s') % e.strerror, - _('Error')) - def _refresh_copy_palette(self): palette = self._copy.get_palette() @@ -452,35 +439,54 @@ class EntryToolbar(gtk.Toolbar): palette.menu.remove(menu_item) menu_item.destroy() + clipboard_menu = ClipboardMenu(self._metadata) + clipboard_menu.set_image(Icon(icon_name='toolbar-edit', + icon_size=gtk.ICON_SIZE_MENU)) + clipboard_menu.connect('volume-error', self.__volume_error_cb) + palette.menu.append(clipboard_menu) + clipboard_menu.show() + if self._metadata['mountpoint'] != '/': - journal_item = MenuItem(_('Journal')) - journal_item.set_image(Icon( - icon_name='activity-journal', - xo_color=profile.get_color(), - icon_size=gtk.ICON_SIZE_MENU)) - journal_item.connect('activate', - self._copy_menu_item_activate_cb, '/') - journal_item.show() - palette.menu.append(journal_item) + client = gconf.client_get_default() + color = XoColor(client.get_string('/desktop/sugar/user/color')) + journal_menu = VolumeMenu(self._metadata, _('Journal'), '/') + journal_menu.set_image(Icon(icon_name='activity-journal', + xo_color=color, + icon_size=gtk.ICON_SIZE_MENU)) + journal_menu.connect('volume-error', self.__volume_error_cb) + palette.menu.append(journal_menu) + journal_menu.show() volume_monitor = gio.volume_monitor_get() + icon_theme = gtk.icon_theme_get_default() for mount in volume_monitor.get_mounts(): if self._metadata['mountpoint'] == mount.get_root().get_path(): continue - menu_item = MenuItem(mount.get_name()) - - icon_theme = gtk.icon_theme_get_default() + volume_menu = VolumeMenu(self._metadata, mount.get_name(), + mount.get_root().get_path()) for name in mount.get_icon().props.names: if icon_theme.has_icon(name): - menu_item.set_image(Icon(icon_name=name, - icon_size=gtk.ICON_SIZE_MENU)) + volume_menu.set_image(Icon(icon_name=name, + icon_size=gtk.ICON_SIZE_MENU)) break + volume_menu.connect('volume-error', self.__volume_error_cb) + palette.menu.append(volume_menu) + volume_menu.show() + + def _refresh_duplicate_palette(self): + color = misc.get_icon_color(self._metadata) + self._copy.get_icon_widget().props.xo_color = color + if self._metadata['mountpoint'] == '/': + self._duplicate.connect('clicked', self._duplicate_clicked_cb) + self._duplicate.show() + icon = self._duplicate.get_icon_widget() + icon.props.xo_color = color + icon.show() + else: + self._duplicate.hide() - menu_item.connect('activate', - self._copy_menu_item_activate_cb, - mount.get_root().get_path()) - palette.menu.append(menu_item) - menu_item.show() + def __volume_error_cb(self, menu_item, message, severity): + self.emit('volume-error', message, severity) def _refresh_resume_palette(self): if self._metadata.get('activity_id', ''): diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py index 70ab701..a9f5a53 100644 --- a/src/jarabe/journal/listview.py +++ b/src/jarabe/journal/listview.py @@ -476,6 +476,8 @@ class ListView(BaseListView): __gsignals__ = { 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object])), + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), } def __init__(self): @@ -491,6 +493,7 @@ class ListView(BaseListView): self.cell_icon.connect('clicked', self.__icon_clicked_cb) self.cell_icon.connect('detail-clicked', self.__detail_clicked_cb) + self.cell_icon.connect('volume-error', self.__volume_error_cb) cell_detail = CellRendererDetail(self.tree_view) cell_detail.connect('clicked', self.__detail_cell_clicked_cb) @@ -532,6 +535,9 @@ class ListView(BaseListView): def __detail_clicked_cb(self, cell, uid): self.emit('detail-clicked', uid) + def __volume_error_cb(self, cell, message, severity): + self.emit('volume-error', message, severity) + def __icon_clicked_cb(self, cell, path): row = self.tree_view.get_model()[path] metadata = model.get(row[ListModel.COLUMN_UID]) @@ -586,6 +592,8 @@ class CellRendererActivityIcon(CellRendererIcon): __gsignals__ = { 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), } def __init__(self, tree_view): @@ -610,11 +618,16 @@ class CellRendererActivityIcon(CellRendererIcon): palette = ObjectPalette(metadata, detail=True) palette.connect('detail-clicked', self.__detail_clicked_cb) + palette.connect('volume-error', + self.__volume_error_cb) return palette def __detail_clicked_cb(self, palette, uid): self.emit('detail-clicked', uid) + def __volume_error_cb(self, palette, message, severity): + self.emit('volume-error', message, severity) + def set_show_palette(self, show_palette): self._show_palette = show_palette diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index 4ea6b7e..ddf9c07 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -621,6 +621,8 @@ def copy(metadata, mount_point): client = gconf.client_get_default() metadata['icon-color'] = client.get_string('/desktop/sugar/user/color') file_path = get_file(metadata['uid']) + if file_path is None: + file_path = '' metadata['mountpoint'] = mount_point del metadata['uid'] diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py index 7091378..0812475 100644 --- a/src/jarabe/journal/palettes.py +++ b/src/jarabe/journal/palettes.py @@ -21,6 +21,7 @@ import os import gobject import gtk import gconf +import gio from sugar.graphics import style from sugar.graphics.palette import Palette @@ -43,16 +44,18 @@ class ObjectPalette(Palette): __gsignals__ = { 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), } def __init__(self, metadata, detail=False): self._metadata = metadata - self._temp_file_path = None activity_icon = Icon(icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR) activity_icon.props.file = misc.get_icon_name(metadata) - activity_icon.props.xo_color = misc.get_icon_color(metadata) + color = misc.get_icon_color(metadata) + activity_icon.props.xo_color = color if 'title' in metadata: title = gobject.markup_escape_text(metadata['title']) @@ -86,15 +89,24 @@ class ObjectPalette(Palette): self.menu.append(menu_item) menu_item.show() - client = gconf.client_get_default() - color = XoColor(client.get_string('/desktop/sugar/user/color')) - menu_item = MenuItem(_('Copy')) + menu_item = MenuItem(_('Copy to')) icon = Icon(icon_name='edit-copy', xo_color=color, icon_size=gtk.ICON_SIZE_MENU) menu_item.set_image(icon) - menu_item.connect('activate', self.__copy_activate_cb) self.menu.append(menu_item) menu_item.show() + copy_menu = CopyMenu(metadata) + copy_menu.connect('volume-error', self.__volume_error_cb) + menu_item.set_submenu(copy_menu) + + if self._metadata['mountpoint'] == '/': + menu_item = MenuItem(_('Duplicate')) + icon = Icon(icon_name='edit-duplicate', xo_color=color, + icon_size=gtk.ICON_SIZE_MENU) + menu_item.set_image(icon) + menu_item.connect('activate', self.__duplicate_activate_cb) + self.menu.append(menu_item) + menu_item.show() menu_item = MenuItem(_('Send to'), 'document-send') self.menu.append(menu_item) @@ -118,21 +130,15 @@ class ObjectPalette(Palette): def __start_activate_cb(self, menu_item): misc.resume(self._metadata) - def __copy_activate_cb(self, menu_item): - clipboard = gtk.Clipboard() - clipboard.set_with_data([('text/uri-list', 0, 0)], - self.__clipboard_get_func_cb, - self.__clipboard_clear_func_cb) - - def __clipboard_get_func_cb(self, clipboard, selection_data, info, data): - # Get hold of a reference so the temp file doesn't get deleted - self._temp_file_path = model.get_file(self._metadata['uid']) - logging.debug('__clipboard_get_func_cb %r', self._temp_file_path) - selection_data.set_uris(['file://' + self._temp_file_path]) - - def __clipboard_clear_func_cb(self, clipboard, data): - # Release and delete the temp file - self._temp_file_path = None + def __duplicate_activate_cb(self, menu_item): + file_path = model.get_file(self._metadata['uid']) + try: + model.copy(self._metadata, '/') + except IOError, e: + logging.exception('Error while copying the entry. %s', e.strerror) + self.emit('volume-error', + _('Error while copying the entry. %s') % e.strerror, + _('Error')) def __erase_activate_cb(self, menu_item): model.delete(self._metadata['uid']) @@ -140,6 +146,9 @@ class ObjectPalette(Palette): def __detail_activate_cb(self, menu_item): self.emit('detail-clicked', self._metadata['uid']) + def __volume_error_cb(self, menu_item, message, severity): + self.emit('volume-error', message, severity) + def __friend_selected_cb(self, menu_item, buddy): logging.debug('__friend_selected_cb') file_name = model.get_file(self._metadata['uid']) @@ -162,6 +171,129 @@ class ObjectPalette(Palette): mime_type) +class CopyMenu(gtk.Menu): + __gtype_name__ = 'JournalCopyMenu' + + __gsignals__ = { + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), + } + + def __init__(self, metadata): + gobject.GObject.__init__(self) + + self._metadata = metadata + + clipboard_menu = ClipboardMenu(self._metadata) + clipboard_menu.set_image(Icon(icon_name='toolbar-edit', + icon_size=gtk.ICON_SIZE_MENU)) + clipboard_menu.connect('volume-error', self.__volume_error_cb) + self.append(clipboard_menu) + clipboard_menu.show() + + if self._metadata['mountpoint'] != '/': + client = gconf.client_get_default() + color = XoColor(client.get_string('/desktop/sugar/user/color')) + journal_menu = VolumeMenu(self._metadata, _('Journal'), '/') + journal_menu.set_image(Icon(icon_name='activity-journal', + xo_color=color, + icon_size=gtk.ICON_SIZE_MENU)) + journal_menu.connect('volume-error', self.__volume_error_cb) + self.append(journal_menu) + journal_menu.show() + + volume_monitor = gio.volume_monitor_get() + icon_theme = gtk.icon_theme_get_default() + for mount in volume_monitor.get_mounts(): + if self._metadata['mountpoint'] == mount.get_root().get_path(): + continue + volume_menu = VolumeMenu(self._metadata, mount.get_name(), + mount.get_root().get_path()) + for name in mount.get_icon().props.names: + if icon_theme.has_icon(name): + volume_menu.set_image(Icon(icon_name=name, + icon_size=gtk.ICON_SIZE_MENU)) + break + volume_menu.connect('volume-error', self.__volume_error_cb) + self.append(volume_menu) + volume_menu.show() + + def __volume_error_cb(self, menu_item, message, severity): + self.emit('volume-error', message, severity) + + +class VolumeMenu(MenuItem): + __gtype_name__ = 'JournalVolumeMenu' + + __gsignals__ = { + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), + } + + def __init__(self, metadata, label, mount_point): + MenuItem.__init__(self, label) + self._metadata = metadata + self.connect('activate', self.__copy_to_volume_cb, mount_point) + + def __copy_to_volume_cb(self, menu_item, mount_point): + file_path = model.get_file(self._metadata['uid']) + + if not file_path or not os.path.exists(file_path): + logging.warn('Entries without a file cannot be copied.') + self.emit('volume-error', + _('Entries without a file cannot be copied.'), + _('Warning')) + return + + try: + model.copy(self._metadata, mount_point) + except IOError, e: + logging.exception('Error while copying the entry. %s', e.strerror) + self.emit('volume-error', + _('Error while copying the entry. %s') % e.strerror, + _('Error')) + + +class ClipboardMenu(MenuItem): + __gtype_name__ = 'JournalClipboardMenu' + + __gsignals__ = { + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), + } + + def __init__(self, metadata): + MenuItem.__init__(self, _('Clipboard')) + + self._temp_file_path = None + self._metadata = metadata + self.connect('activate', self.__copy_to_clipboard_cb) + + def __copy_to_clipboard_cb(self, menu_item): + file_path = model.get_file(self._metadata['uid']) + if not file_path or not os.path.exists(file_path): + logging.warn('Entries without a file cannot be copied.') + self.emit('volume-error', + _('Entries without a file cannot be copied.'), + _('Warning')) + return + + clipboard = gtk.Clipboard() + clipboard.set_with_data([('text/uri-list', 0, 0)], + self.__clipboard_get_func_cb, + self.__clipboard_clear_func_cb) + + def __clipboard_get_func_cb(self, clipboard, selection_data, info, data): + # Get hold of a reference so the temp file doesn't get deleted + self._temp_file_path = model.get_file(self._metadata['uid']) + logging.debug('__clipboard_get_func_cb %r', self._temp_file_path) + selection_data.set_uris(['file://' + self._temp_file_path]) + + def __clipboard_clear_func_cb(self, clipboard, data): + # Release and delete the temp file + self._temp_file_path = None + + class FriendsMenu(gtk.Menu): __gtype_name__ = 'JournalFriendsMenu' -- cgit v0.9.1