[Sugar-devel] [PATCH sugar] Add duplicate functionality to the Journal and enhance copy functionality
Simon Schampijer
simon at schampijer.de
Mon Aug 15 13:57:44 EDT 2011
Pushed as:
http://git.sugarlabs.org/sugar/mainline/commit/2148b47c2a5910eeb14fe827059585bef09fc111
Thanks for everyone involved who made this happen.
Regards,
Simon
On 08/12/2011 08:01 PM, Simon Schampijer wrote:
> 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<simon at laptop.org>
> ---
> src/jarabe/journal/journalactivity.py | 1 +
> src/jarabe/journal/journaltoolbox.py | 116 ++++++++++++----------
> src/jarabe/journal/listview.py | 13 +++
> src/jarabe/journal/model.py | 2 +
> src/jarabe/journal/palettes.py | 174 +++++++++++++++++++++++++++++----
> 5 files changed, 230 insertions(+), 76 deletions(-)
>
> 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'
>
More information about the Sugar-devel
mailing list