[Sugar-devel] [PATCH sugar] Add duplicate functionality to the Journal and enhance copy functionality
Simon Schampijer
simon at schampijer.de
Fri Jul 29 12:35:41 EDT 2011
This patch adds a duplicate option to the Journal entry palette and
the entry detail view. This will replace the keep button from the
activity toolbar.
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 | 112 +++++++++++----------
src/jarabe/journal/listview.py | 13 +++
src/jarabe/journal/model.py | 2 +
src/jarabe/journal/palettes.py | 174 +++++++++++++++++++++++++++++----
5 files changed, 227 insertions(+), 75 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..82d327e 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,24 @@ 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._duplicate.set_icon_widget(icon)
+ self._duplicate.set_tooltip(_('Duplicate'))
+ self.add(self._duplicate)
+
separator = gtk.SeparatorToolItem()
self.add(separator)
separator.show()
@@ -395,6 +402,16 @@ class EntryToolbar(gtk.Toolbar):
def set_metadata(self, metadata):
self._metadata = metadata
+ 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()
self._refresh_copy_palette()
self._refresh_resume_palette()
@@ -402,19 +419,17 @@ class EntryToolbar(gtk.Toolbar):
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. %s', e.strerror)
+ 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 +442,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 +449,42 @@ 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()
- 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'
--
1.7.4.4
More information about the Sugar-devel
mailing list