[Sugar-devel] [sugar PATCH] Multi-Select.
Ajay Garg
ajay at activitycentral.com
Mon Apr 15 03:12:38 EDT 2013
Thanks Daniel.
I have rebased the patches to the current state of the master branches, and
created pull requests as per ::
[sugar] ::: https://github.com/sugarlabs/sugar/pull/22
[sugar-artwork] ::: https://github.com/sugarlabs/sugar-artwork/pull/1
Rebasing the patches (the "sugar" one) time and again is a tedious task.
So if this works, please, please, please, please merge the patches into the
mainline/master as soon as possible.
If I should do that, please let me know :)
In either case, of course it is me who takes the responsibility of fixing
any regressions (if at all) that might be introduced.
Thanks, and looking forward to a definite action being taken soon, so as to
avoid a further rebasing of the "sugar" patch.
On Mon, Apr 15, 2013 at 12:53 AM, Daniel Narvaez <dwnarvaez at gmail.com>wrote:
> Did you read this?
>
> https://help.github.com/articles/using-pull-requests
>
> You need to
>
> * Fork the repo on the web UI
> * Clone your fork
> * Push the patches to your fork
> * Make a pull request from the web UI
>
> On 13 April 2013 06:13, Ajay Garg <ajay at activitycentral.com> wrote:
>
>> Hi all.
>> Any news on the progress on this front?
>>
>> Should I commit this on github?
>>
>> Daniel, Manuel : If yes, please let me know a step by step procedure for
>> this.
>> I don't want to mess up things, so I will be
>> grateful if you guys could let me know the step by step procedure
>> to get this in guthub, only one single time :)
>>
>>
>>
>> On Wed, Apr 3, 2013 at 12:22 AM, Ajay Garg <ajay at activitycentral.com>wrote:
>>
>>> The patch is to be applied in mainline-repo, master-branch.
>>>
>>>
>>> The workflow is as per
>>> http://wiki.sugarlabs.org/go/Features/Multi_selection_screenshots
>>>
>>>
>>> The only known "issues" are ::
>>>
>>> a)
>>> Journal-View is not updated, when screenshot(s) is(are) taken in
>>> "Multi-Select" mode.
>>>
>>> ====================================================================================
>>>
>>> The reason is that journal-refresh is inhibited in "Multi-Select" mode.
>>>
>>> Thankfully, this is not too great an inconsistency, as the users are
>>> need to be educated of the
>>> "Alt + 1 screenshot" feature anyway, when the screenshot is taken in any
>>> other view other than the
>>> Journal-View. That time, the users "need" to know where they may find
>>> the newly taken screenshot(s).
>>>
>>>
>>>
>>> b)
>>> Renaming of entries, does not happen in "Multi-Select" mode.
>>> =============================================================
>>>
>>> The issue is mainly in locations other than the "Journal"
>>> (Documents/Pen-Drives), as renaming the entry needs
>>> a corresponding change on the file-system. Due to the fact the refresh
>>> is inhibited in "Multi-Select" mode,
>>> this is not possible.
>>>
>>> Thus, with the aim of maintaining UI-consistency,
>>> renaming-in-Multi-Select mode is disabled for "Journal" too.
>>>
>>>
>>>
>>>
>>> src/jarabe/journal/expandedentry.py | 12 +-
>>> src/jarabe/journal/journalactivity.py | 182 ++++++++-
>>> src/jarabe/journal/journaltoolbox.py | 304 +++++++++++---
>>> src/jarabe/journal/journalwindow.py | 45 +++
>>> src/jarabe/journal/listmodel.py | 23 ++
>>> src/jarabe/journal/listview.py | 235 ++++++++++-
>>> src/jarabe/journal/model.py | 243 +++++++++---
>>> src/jarabe/journal/objectchooser.py | 22 +-
>>> src/jarabe/journal/palettes.py | 728
>>> +++++++++++++++++++++++++++++-----
>>> src/jarabe/journal/volumestoolbar.py | 92 +++--
>>> 10 files changed, 1637 insertions(+), 249 deletions(-)
>>>
>>> diff --git a/src/jarabe/journal/expandedentry.py
>>> b/src/jarabe/journal/expandedentry.py
>>> index 0cb59d6..6937370 100644
>>> --- a/src/jarabe/journal/expandedentry.py
>>> +++ b/src/jarabe/journal/expandedentry.py
>>> @@ -554,14 +554,10 @@ class ExpandedEntry(Gtk.EventBox):
>>> self._update_title_sid = None
>>>
>>> def _write_entry(self):
>>> - if self._metadata.get('mountpoint', '/') == '/':
>>> - model.write(self._metadata, update_mtime=False)
>>> - else:
>>> - old_file_path = os.path.join(
>>> - self._metadata['mountpoint'],
>>> - model.get_file_name(old_title,
>>> self._metadata['mime_type']))
>>> - model.write(self._metadata, file_path=old_file_path,
>>> - update_mtime=False)
>>> + from jarabe.journal.journalactivity import get_journal
>>> + self._metadata['mountpoint'] = \
>>> + get_journal().get_detail_toolbox().get_mount_point()
>>> +
>>> model.update_only_metadata_and_preview_files_and_return_file_paths(self._metadata)
>>>
>>> def _keep_icon_toggled_cb(self, keep_icon):
>>> if keep_icon.get_active():
>>> diff --git a/src/jarabe/journal/journalactivity.py
>>> b/src/jarabe/journal/journalactivity.py
>>> index 4bb68fd..56740cf 100644
>>> --- a/src/jarabe/journal/journalactivity.py
>>> +++ b/src/jarabe/journal/journalactivity.py
>>> @@ -19,6 +19,7 @@ import logging
>>> from gettext import gettext as _
>>> import uuid
>>>
>>> +from gi.repository import GObject
>>> from gi.repository import Gtk
>>> from gi.repository import Gdk
>>> from gi.repository import GdkX11
>>> @@ -27,7 +28,8 @@ import statvfs
>>> import os
>>>
>>> from sugar3.graphics.window import Window
>>> -from sugar3.graphics.alert import ErrorAlert
>>> +from sugar3.graphics.icon import Icon
>>> +from sugar3.graphics.alert import Alert
>>>
>>> from sugar3.bundle.bundle import ZipExtractException,
>>> RegistrationException
>>> from sugar3 import env
>>> @@ -37,7 +39,9 @@ from gi.repository import SugarExt
>>>
>>> from jarabe.model import bundleregistry
>>> from jarabe.journal.journaltoolbox import MainToolbox, DetailToolbox
>>> +from jarabe.journal.journaltoolbox import EditToolbox
>>> from jarabe.journal.listview import ListView
>>> +from jarabe.journal.listmodel import ListModel
>>> from jarabe.journal.detailview import DetailView
>>> from jarabe.journal.volumestoolbar import VolumesToolbar
>>> from jarabe.journal import misc
>>> @@ -46,6 +50,7 @@ from jarabe.journal.objectchooser import ObjectChooser
>>> from jarabe.journal.modalalert import ModalAlert
>>> from jarabe.journal import model
>>> from jarabe.journal.journalwindow import JournalWindow
>>> +from jarabe.journal.journalwindow import show_normal_cursor
>>>
>>>
>>> J_DBUS_SERVICE = 'org.laptop.Journal'
>>> @@ -56,6 +61,7 @@ _SPACE_TRESHOLD = 52428800
>>> _BUNDLE_ID = 'org.laptop.JournalActivity'
>>>
>>> _journal = None
>>> +_mount_point = None
>>>
>>>
>>> class JournalActivityDBusService(dbus.service.Object):
>>> @@ -124,8 +130,33 @@ class JournalActivity(JournalWindow):
>>> self._list_view = None
>>> self._detail_view = None
>>> self._main_toolbox = None
>>> + self._edit_toolbox = None
>>> self._detail_toolbox = None
>>> self._volumes_toolbar = None
>>> + self._editing_mode = False
>>> + self._alert = Alert()
>>> +
>>> + self._error_alert = Alert()
>>> + icon = Icon(icon_name='dialog-ok')
>>> + self._error_alert.add_button(Gtk.ResponseType.OK, _('Ok'), icon)
>>> + icon.show()
>>> +
>>> + self._confirmation_alert = Alert()
>>> + icon = Icon(icon_name='dialog-cancel')
>>> + self._confirmation_alert.add_button(Gtk.ResponseType.CANCEL,
>>> _('Stop'), icon)
>>> + icon.show()
>>> + icon = Icon(icon_name='dialog-ok')
>>> + self._confirmation_alert.add_button(Gtk.ResponseType.OK,
>>> _('Continue'), icon)
>>> + icon.show()
>>> +
>>> + self._current_alert = None
>>> + self.setup_handlers_for_alert_actions()
>>> +
>>> + self._info_alert = None
>>> + self._selected_entries = []
>>> + self._bundle_installation_allowed = True
>>> +
>>> + set_mount_point('/')
>>>
>>> self._setup_main_view()
>>> self._setup_secondary_view()
>>> @@ -151,10 +182,17 @@ class JournalActivity(JournalWindow):
>>> self._check_available_space()
>>>
>>> def __volume_error_cb(self, gobject, message, severity):
>>> - alert = ErrorAlert(title=severity, msg=message)
>>> - alert.connect('response', self.__alert_response_cb)
>>> - self.add_alert(alert)
>>> - alert.show()
>>> + self.update_title_and_message(self._error_alert, severity,
>>> + message)
>>> + self._callback = None
>>> + self._data = None
>>> + self.update_alert(self._error_alert)
>>> +
>>> + def _show_alert(self, message, severity):
>>> + self.__volume_error_cb(None, message, severity)
>>> +
>>> + def _volume_error_cb(self, gobject, message, severity):
>>> + self.update_error_alert(severity, message, None, None)
>>>
>>> def __alert_response_cb(self, alert, response_id):
>>> self.remove_alert(alert)
>>> @@ -196,11 +234,13 @@ class JournalActivity(JournalWindow):
>>> self._main_toolbox.search_entry.connect('icon-press',
>>>
>>> self.__search_icon_pressed_cb)
>>> self._main_toolbox.set_mount_point('/')
>>> + set_mount_point('/')
>>>
>>> def _setup_secondary_view(self):
>>> self._secondary_view = Gtk.VBox()
>>>
>>> self._detail_toolbox = DetailToolbox()
>>> + self._detail_toolbox.set_mount_point('/')
>>> self._detail_toolbox.connect('volume-error',
>>> self.__volume_error_cb)
>>>
>>> @@ -240,9 +280,16 @@ class JournalActivity(JournalWindow):
>>> self.connect('key-press-event', self._key_press_event_cb)
>>>
>>> def show_main_view(self):
>>> - if self.toolbar_box != self._main_toolbox:
>>> - self.set_toolbar_box(self._main_toolbox)
>>> - self._main_toolbox.show()
>>> + if self._editing_mode:
>>> + self._toolbox = EditToolbox()
>>> +
>>> + # TRANS: Do not translate the "%d"
>>> +
>>> self._toolbox.set_total_number_of_entries(self.get_total_number_of_entries())
>>> + else:
>>> + self._toolbox = self._main_toolbox
>>> +
>>> + self.set_toolbar_box(self._toolbox)
>>> + self._toolbox.show()
>>>
>>> if self.canvas != self._main_view:
>>> self.set_canvas(self._main_view)
>>> @@ -277,6 +324,10 @@ class JournalActivity(JournalWindow):
>>> def __volume_changed_cb(self, volume_toolbar, mount_point):
>>> logging.debug('Selected volume: %r.', mount_point)
>>> self._main_toolbox.set_mount_point(mount_point)
>>> + set_mount_point(mount_point)
>>> +
>>> + # Also, need to update the mount-point for Detail-View.
>>> + self._detail_toolbox.set_mount_point(mount_point)
>>>
>>> def __model_created_cb(self, sender, **kwargs):
>>> self._check_for_bundle(kwargs['object_id'])
>>> @@ -301,6 +352,9 @@ class JournalActivity(JournalWindow):
>>> self._list_view.update_dates()
>>>
>>> def _check_for_bundle(self, object_id):
>>> + if not self._bundle_installation_allowed:
>>> + return
>>> +
>>> registry = bundleregistry.get_registry()
>>>
>>> metadata = model.get(object_id)
>>> @@ -334,7 +388,13 @@ class JournalActivity(JournalWindow):
>>> return
>>>
>>> metadata['bundle_id'] = bundle.get_bundle_id()
>>> - model.write(metadata)
>>> +
>>> + from jarabe.journal.journalactivity import get_mount_point
>>> + metadata['mountpoint'] = get_mount_point()
>>> +
>>> model.update_only_metadata_and_preview_files_and_return_file_paths(metadata)
>>> +
>>> + def set_bundle_installation_allowed(self, allowed):
>>> + self._bundle_installation_allowed = allowed
>>>
>>> def __window_state_event_cb(self, window, event):
>>> logging.debug('window_state_event_cb %r', self)
>>> @@ -378,6 +438,102 @@ class JournalActivity(JournalWindow):
>>> self.reveal()
>>> self.show_main_view()
>>>
>>> + def switch_to_editing_mode(self, switch):
>>> + # (re)-switch, only if not already.
>>> + if (switch) and (not self._editing_mode):
>>> + self._editing_mode = True
>>> + self.get_list_view().disable_drag_and_copy()
>>> + self.show_main_view()
>>> + elif (not switch) and (self._editing_mode):
>>> + self._editing_mode = False
>>> + self.get_list_view().enable_drag_and_copy()
>>> + self.show_main_view()
>>> +
>>> + def get_list_view(self):
>>> + return self._list_view
>>> +
>>> + def setup_handlers_for_alert_actions(self):
>>> + self._error_alert.connect('response',
>>> + self.__check_for_alert_action)
>>> + self._confirmation_alert.connect('response',
>>> + self.__check_for_alert_action)
>>> +
>>> + def __check_for_alert_action(self, alert, response_id):
>>> + self.hide_alert()
>>> + if self._callback is not None:
>>> + GObject.idle_add(self._callback, self._data,
>>> + response_id)
>>> +
>>> + def update_title_and_message(self, alert, title, message):
>>> + alert.props.title = title
>>> + alert.props.msg = message
>>> +
>>> + def update_alert(self, alert):
>>> + if self._current_alert is None:
>>> + self.add_alert(alert)
>>> + elif self._current_alert != alert:
>>> + self.remove_alert(self._current_alert)
>>> + self.add_alert(alert)
>>> +
>>> + self.remove_alert(self._current_alert)
>>> + self.add_alert(alert)
>>> + self._current_alert = alert
>>> + self._current_alert.show()
>>> + show_normal_cursor()
>>> +
>>> + def hide_alert(self):
>>> + if self._current_alert is not None:
>>> + self._current_alert.hide()
>>> +
>>> + def update_info_alert(self, title, message):
>>> +
>>> self.get_toolbar_box().display_running_status_in_multi_select(title,
>>> message)
>>> +
>>> + def update_error_alert(self, title, message, callback, data):
>>> + self.update_title_and_message(self._error_alert, title,
>>> + message)
>>> + self._callback = callback
>>> + self._data = data
>>> + self.update_alert(self._error_alert)
>>> +
>>> + def update_confirmation_alert(self, title, message, callback,
>>> + data):
>>> + self.update_title_and_message(self._confirmation_alert, title,
>>> + message)
>>> + self._callback = callback
>>> + self._data = data
>>> + self.update_alert(self._confirmation_alert)
>>> +
>>> + def get_metadata_list(self, selected_state):
>>> + metadata_list = []
>>> +
>>> + list_view_model = self.get_list_view().get_model()
>>> + for index in range(0, len(list_view_model)):
>>> + metadata = list_view_model.get_metadata(index)
>>> + metadata_selected = \
>>> + list_view_model.get_selected_value(metadata['uid'])
>>> +
>>> + if ( (selected_state and metadata_selected) or \
>>> + ((not selected_state) and (not metadata_selected))
>>> ):
>>> + metadata_list.append(metadata)
>>> +
>>> + return metadata_list
>>> +
>>> + def get_total_number_of_entries(self):
>>> + list_view_model = self.get_list_view().get_model()
>>> + return len(list_view_model)
>>> +
>>> + def is_editing_mode_present(self):
>>> + return self._editing_mode
>>> +
>>> + def get_volumes_toolbar(self):
>>> + return self._volumes_toolbar
>>> +
>>> + def get_toolbar_box(self):
>>> + return self._toolbox
>>> +
>>> + def get_detail_toolbox(self):
>>> + return self._detail_toolbox
>>> +
>>>
>>> def get_journal():
>>> global _journal
>>> @@ -389,3 +545,11 @@ def get_journal():
>>>
>>> def start():
>>> get_journal()
>>> +
>>> +
>>> +def set_mount_point(mount_point):
>>> + global _mount_point
>>> + _mount_point = mount_point
>>> +
>>> +def get_mount_point():
>>> + return _mount_point
>>> diff --git a/src/jarabe/journal/journaltoolbox.py
>>> b/src/jarabe/journal/journaltoolbox.py
>>> index b9c5e68..77a0423 100644
>>> --- a/src/jarabe/journal/journaltoolbox.py
>>> +++ b/src/jarabe/journal/journaltoolbox.py
>>> @@ -16,6 +16,7 @@
>>> # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
>>> USA
>>>
>>> from gettext import gettext as _
>>> +from gettext import ngettext
>>> import logging
>>> from datetime import datetime, timedelta
>>> import os
>>> @@ -25,6 +26,7 @@ import time
>>> from gi.repository import GObject
>>> from gi.repository import Gio
>>> from gi.repository import Gtk
>>> +from gi.repository import Gdk
>>>
>>> from sugar3.graphics.palette import Palette
>>> from sugar3.graphics.toolbarbox import ToolbarBox
>>> @@ -46,8 +48,9 @@ from jarabe.journal import misc
>>> from jarabe.journal import model
>>> from jarabe.journal.palettes import ClipboardMenu
>>> from jarabe.journal.palettes import VolumeMenu
>>> -from jarabe.journal import journalwindow
>>> +from jarabe.journal import journalwindow, palettes
>>>
>>> +COPY_MENU_HELPER = palettes.get_copy_menu_helper()
>>>
>>> _AUTOSEARCH_TIMEOUT = 1000
>>>
>>> @@ -387,11 +390,27 @@ class DetailToolbox(ToolbarBox):
>>> separator.show()
>>>
>>> erase_button = ToolButton('list-remove')
>>> + self._erase_button = erase_button
>>> erase_button.set_tooltip(_('Erase'))
>>> erase_button.connect('clicked', self._erase_button_clicked_cb)
>>> self.toolbar.insert(erase_button, -1)
>>> erase_button.show()
>>>
>>> + def set_mount_point(self, mount_point):
>>> + self._mount_point = mount_point
>>> + self.set_sensitivity_of_icons()
>>> +
>>> + def get_mount_point(self):
>>> + return self._mount_point
>>> +
>>> + def set_sensitivity_of_icons(self):
>>> + mount_point = self.get_mount_point()
>>> + sensitivity = True
>>> +
>>> + self._resume.set_sensitive(sensitivity)
>>> + self._duplicate.set_sensitive(sensitivity)
>>> + self._erase_button.set_sensitive(sensitivity)
>>> +
>>> def set_metadata(self, metadata):
>>> self._metadata = metadata
>>> self._refresh_copy_palette()
>>> @@ -449,50 +468,11 @@ class DetailToolbox(ToolbarBox):
>>> 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.IconSize.MENU))
>>> - clipboard_menu.connect('volume-error', self.__volume_error_cb)
>>> - palette.menu.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.IconSize.MENU))
>>> - journal_menu.connect('volume-error', self.__volume_error_cb)
>>> - palette.menu.append(journal_menu)
>>> - journal_menu.show()
>>> -
>>> - documents_path = model.get_documents_path()
>>> - if documents_path is not None and not \
>>> - self._metadata['uid'].startswith(documents_path):
>>> - documents_menu = VolumeMenu(self._metadata, _('Documents'),
>>> - documents_path)
>>> - documents_menu.set_image(Icon(icon_name='user-documents',
>>> - icon_size=Gtk.IconSize.MENU))
>>> - documents_menu.connect('volume-error',
>>> self.__volume_error_cb)
>>> - palette.menu.append(documents_menu)
>>> - documents_menu.show()
>>> -
>>> - volume_monitor = Gio.VolumeMonitor.get()
>>> - icon_theme = Gtk.IconTheme.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.IconSize.MENU))
>>> - break
>>> - volume_menu.connect('volume-error', self.__volume_error_cb)
>>> - palette.menu.append(volume_menu)
>>> - volume_menu.show()
>>> + COPY_MENU_HELPER.insert_copy_to_menu_items(palette.menu,
>>> + [self._metadata],
>>> +
>>> show_editing_alert=False,
>>> +
>>> show_progress_info_alert=False,
>>> + batch_mode=False)
>>>
>>> def _refresh_duplicate_palette(self):
>>> color = misc.get_icon_color(self._metadata)
>>> @@ -532,6 +512,240 @@ class DetailToolbox(ToolbarBox):
>>> menu_item.show()
>>>
>>>
>>> +class EditToolbox(ToolbarBox):
>>> + def __init__(self):
>>> + ToolbarBox.__init__(self)
>>> +
>>> + self.toolbar.add(SelectNoneButton())
>>> + self.toolbar.add(SelectAllButton())
>>> +
>>> + self.toolbar.add(Gtk.SeparatorToolItem())
>>> +
>>> + self.toolbar.add(BatchEraseButton())
>>> + self.toolbar.add(BatchCopyButton())
>>> +
>>> + self.toolbar.add(Gtk.SeparatorToolItem())
>>> +
>>> + self._multi_select_info_widget = MultiSelectEntriesInfoWidget()
>>> + self.toolbar.add(self._multi_select_info_widget)
>>> +
>>> + self.show_all()
>>> + self.toolbar.show_all()
>>> +
>>> + def process_new_selected_entry_in_multi_select(self):
>>> + GObject.idle_add(self._multi_select_info_widget.update_text,
>>> + '', '', True, True)
>>> +
>>> + def process_new_deselected_entry_in_multi_select(self):
>>> + GObject.idle_add(self._multi_select_info_widget.update_text,
>>> + '', '', False, True)
>>> +
>>> + def display_running_status_in_multi_select(self, primary_info,
>>> + secondary_info):
>>> + GObject.idle_add(self._multi_select_info_widget.update_text,
>>> + primary_info, secondary_info,
>>> + None, None)
>>> +
>>> + def display_already_selected_entries_status(self):
>>> + GObject.idle_add(self._multi_select_info_widget.update_text,
>>> + '', '', True, False)
>>> +
>>> + def set_total_number_of_entries(self, total):
>>> +
>>> self._multi_select_info_widget.set_total_number_of_entries(total)
>>> +
>>> + def get_current_entry_number(self):
>>> + return self._multi_select_info_widget.get_current_entry_number()
>>> +
>>> +
>>> +class SelectNoneButton(ToolButton):
>>> + def __init__(self):
>>> + ToolButton.__init__(self, 'select-none')
>>> + self.props.tooltip = _('Deselect all')
>>> +
>>> + self.connect('clicked', self.__do_deselect_all)
>>> +
>>> + def __do_deselect_all(self, widget_clicked):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + journal.get_list_view()._selected_entries = 0
>>> + journal.switch_to_editing_mode(False)
>>> + journal.get_list_view().inhibit_refresh(False)
>>> + journal.get_list_view().refresh()
>>> +
>>> +
>>> +class SelectAllButton(ToolButton, palettes.ActionItem):
>>> + def __init__(self):
>>> + ToolButton.__init__(self, 'select-all')
>>> + palettes.ActionItem.__init__(self, '', [],
>>> + show_editing_alert=False,
>>> + show_progress_info_alert=False,
>>> + batch_mode=True,
>>> + auto_deselect_source_entries=True,
>>> + need_to_popup_options=False,
>>> + operate_on_deselected_entries=True,
>>> + show_not_completed_ops_info=False)
>>> + self.props.tooltip = _('Select all')
>>> +
>>> + def _get_actionable_signal(self):
>>> + return 'clicked'
>>> +
>>> + def _get_editing_alert_operation(self):
>>> + return _('Select all')
>>> +
>>> + def _get_info_alert_title(self):
>>> + return _('Selecting')
>>> +
>>> + def _get_post_selection_alert_message_entries_len(self):
>>> + return self._model_len
>>> +
>>> + def _get_post_selection_alert_message(self, entries_len):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + return ngettext('You have selected %d entry.',
>>> + 'You have selected %d entries.',
>>> + entries_len) % (entries_len,)
>>> +
>>> + def _operate(self, metadata):
>>> + # Nothing specific needs to be done.
>>> + # The checkboxes are unchecked as part of the toggling of any
>>> + # operation that operates on selected entries.
>>> +
>>> + # This is sync-operation. Thus, call the callback.
>>> + self._post_operate_per_metadata_per_action(metadata)
>>> +
>>> +
>>> +class BatchEraseButton(ToolButton, palettes.ActionItem):
>>> + def __init__(self):
>>> + ToolButton.__init__(self, 'edit-delete')
>>> + palettes.ActionItem.__init__(self, '', [],
>>> + show_editing_alert=True,
>>> + show_progress_info_alert=True,
>>> + batch_mode=True,
>>> + auto_deselect_source_entries=True,
>>> + need_to_popup_options=False,
>>> +
>>> operate_on_deselected_entries=False,
>>> + show_not_completed_ops_info=True)
>>> + self.props.tooltip = _('Erase')
>>> +
>>> + def _get_actionable_signal(self):
>>> + return 'clicked'
>>> +
>>> + def _get_editing_alert_title(self):
>>> + return _('Erase')
>>> +
>>> + def _get_editing_alert_message(self, entries_len):
>>> + return ngettext('Do you want to erase %d entry?',
>>> + 'Do you want to erase %d entries?',
>>> + entries_len) % (entries_len)
>>> +
>>> + def _get_editing_alert_operation(self):
>>> + return _('Erase')
>>> +
>>> + def _get_info_alert_title(self):
>>> + return _('Erasing')
>>> +
>>> + def _operate(self, metadata):
>>> + model.delete(metadata['uid'])
>>> +
>>> + # This is sync-operation. Thus, call the callback.
>>> + self._post_operate_per_metadata_per_action(metadata)
>>> +
>>> +
>>> +class BatchCopyButton(ToolButton, palettes.ActionItem):
>>> + def __init__(self):
>>> + ToolButton.__init__(self, 'edit-copy')
>>> + palettes.ActionItem.__init__(self, '', [],
>>> + show_editing_alert=True,
>>> + show_progress_info_alert=True,
>>> + batch_mode=True,
>>> + auto_deselect_source_entries=False,
>>> + need_to_popup_options=True,
>>> +
>>> operate_on_deselected_entries=False,
>>> + show_not_completed_ops_info=False)
>>> +
>>> + self.props.tooltip = _('Copy')
>>> +
>>> + self._metadata_list = None
>>> + self._fill_and_pop_up_options(None)
>>> +
>>> + def _get_actionable_signal(self):
>>> + return 'clicked'
>>> +
>>> + def _fill_and_pop_up_options(self, widget_clicked):
>>> + for child in self.props.palette.menu.get_children():
>>> + self.props.palette.menu.remove(child)
>>> +
>>> +
>>> COPY_MENU_HELPER.insert_copy_to_menu_items(self.props.palette.menu,
>>> + [],
>>> +
>>> show_editing_alert=True,
>>> +
>>> show_progress_info_alert=True,
>>> + batch_mode=True)
>>> + if widget_clicked is not None:
>>> + self.props.palette.popup(immediate=True, state=1)
>>> +
>>> +
>>> +class MultiSelectEntriesInfoWidget(Gtk.ToolItem):
>>> + def __init__(self):
>>> + Gtk.ToolItem.__init__(self)
>>> +
>>> + self._box = Gtk.VBox()
>>> + self._selected_entries = 0
>>> +
>>> + self._label = Gtk.Label()
>>> + self._box.pack_start(self._label, True, True, 0)
>>> +
>>> + self.add(self._box)
>>> +
>>> + self.show_all()
>>> + self._box.show_all()
>>> +
>>> + def set_total_number_of_entries(self, total):
>>> + self._total = total
>>> +
>>> + def update_text(self, primary_text, secondary_text, special_action,
>>> + update_selected_entries):
>>> + # If "special_action" is None,
>>> + # we need to display the info, conveyed by
>>> + # "primary_message" and "secondary_message"
>>> + #
>>> + # If "special_action" is True,
>>> + # a new entry has been selected.
>>> + #
>>> + # If "special_action" is False,
>>> + # an enrty has been deselected.
>>> + if special_action == None:
>>> + self._label.set_text(primary_text + secondary_text)
>>> + self._label.show()
>>> + else:
>>> + if update_selected_entries:
>>> + if special_action == True:
>>> + self._selected_entries = self._selected_entries + 1
>>> + elif special_action == False:
>>> + self._selected_entries = self._selected_entries - 1
>>> +
>>> + # TRANS: Do not translate the two "%d".
>>> + message = _('Selected %d of %d') % (self._selected_entries,
>>> + self._total)
>>> +
>>> + # Only show the "selected x of y" for "Select All", or
>>> + # "Deselect All", or if the user checked/unchecked a
>>> + # checkbox.
>>> + from jarabe.journal.palettes import get_current_action_item
>>> + current_action_item = get_current_action_item()
>>> + if current_action_item == None or \
>>> + isinstance(current_action_item, SelectAllButton) or \
>>> + isinstance(current_action_item, SelectNoneButton):
>>> + self._label.set_text(message)
>>> + self._label.show()
>>> +
>>> + Gdk.Window.process_all_updates()
>>> +
>>> + def get_current_entry_number(self):
>>> + return self._selected_entries
>>> +
>>> +
>>> class SortingButton(ToolButton):
>>> __gtype_name__ = 'JournalSortingButton'
>>>
>>> diff --git a/src/jarabe/journal/journalwindow.py
>>> b/src/jarabe/journal/journalwindow.py
>>> index 776a495..8fcecaf 100644
>>> --- a/src/jarabe/journal/journalwindow.py
>>> +++ b/src/jarabe/journal/journalwindow.py
>>> @@ -15,6 +15,8 @@
>>> # along with this program; if not, write to the Free Software
>>> # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
>>> USA
>>>
>>> +from gi.repository import Gdk
>>> +
>>> from sugar3.graphics.window import Window
>>>
>>> _journal_window = None
>>> @@ -31,3 +33,46 @@ class JournalWindow(Window):
>>>
>>> def get_journal_window():
>>> return _journal_window
>>> +
>>> +
>>> +def set_widgets_active_state(active_state):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + journal.get_toolbar_box().set_sensitive(active_state)
>>> + journal.get_list_view().set_sensitive(active_state)
>>> + journal.get_volumes_toolbar().set_sensitive(active_state)
>>> +
>>> +
>>> +def show_waiting_cursor():
>>> + # Only show waiting-cursor, if this is the batch-mode.
>>> +
>>> + from jarabe.journal.journalactivity import get_journal
>>> + if not get_journal().is_editing_mode_present():
>>> + return
>>> +
>>> +
>>> _journal_window.get_root_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
>>> +
>>> +
>>> +def freeze_ui():
>>> + # Only freeze, if this is the batch-mode.
>>> +
>>> + from jarabe.journal.journalactivity import get_journal
>>> + if not get_journal().is_editing_mode_present():
>>> + return
>>> +
>>> + show_waiting_cursor()
>>> +
>>> + set_widgets_active_state(False)
>>> +
>>> +
>>> +def show_normal_cursor():
>>> +
>>> _journal_window.get_root_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR))
>>> +
>>> +
>>> +def unfreeze_ui():
>>> + # Unfreeze, irrespective of whether this is the batch mode.
>>> +
>>> + set_widgets_active_state(True)
>>> +
>>> + show_normal_cursor()
>>> diff --git a/src/jarabe/journal/listmodel.py
>>> b/src/jarabe/journal/listmodel.py
>>> index b98d01c..a5bb7b0 100644
>>> --- a/src/jarabe/journal/listmodel.py
>>> +++ b/src/jarabe/journal/listmodel.py
>>> @@ -54,6 +54,7 @@ class ListModel(GObject.GObject, Gtk.TreeModel,
>>> Gtk.TreeDragSource):
>>> COLUMN_BUDDY_1 = 9
>>> COLUMN_BUDDY_2 = 10
>>> COLUMN_BUDDY_3 = 11
>>> + COLUMN_SELECT = 12
>>>
>>> _COLUMN_TYPES = {
>>> COLUMN_UID: str,
>>> @@ -68,6 +69,7 @@ class ListModel(GObject.GObject, Gtk.TreeModel,
>>> Gtk.TreeDragSource):
>>> COLUMN_BUDDY_1: object,
>>> COLUMN_BUDDY_3: object,
>>> COLUMN_BUDDY_2: object,
>>> + COLUMN_SELECT: bool,
>>> }
>>>
>>> _PAGE_SIZE = 10
>>> @@ -79,6 +81,8 @@ class ListModel(GObject.GObject, Gtk.TreeModel,
>>> Gtk.TreeDragSource):
>>> self._cached_row = None
>>> self._result_set = model.find(query, ListModel._PAGE_SIZE)
>>> self._temp_drag_file_path = None
>>> + self._selected = {}
>>> + self._uid_metadata_assoc = {}
>>>
>>> # HACK: The view will tell us that it is resizing so the model
>>> can
>>> # avoid hitting D-Bus and disk.
>>> @@ -248,3 +252,22 @@ class ListModel(GObject.GObject, Gtk.TreeModel,
>>> Gtk.TreeDragSource):
>>> return True
>>>
>>> return False
>>> +
>>> + def update_uid_metadata_assoc(self, uid, metadata):
>>> + self._uid_metadata_assoc[uid] = metadata
>>> +
>>> + def set_selected_value(self, uid, value):
>>> + if value == False:
>>> + del self._selected[uid]
>>> + elif value == True:
>>> + self._selected[uid] = value
>>> +
>>> + def get_selected_value(self, uid):
>>> + if self._selected.has_key(uid):
>>> + return True
>>> + else:
>>> + return False
>>> +
>>> + def get_in_memory_metadata(self, path):
>>> + uid = self[path][ListModel.COLUMN_UID]
>>> + return self._uid_metadata_assoc[uid]
>>> diff --git a/src/jarabe/journal/listview.py
>>> b/src/jarabe/journal/listview.py
>>> index 3356d6f..32cbc2f 100644
>>> --- a/src/jarabe/journal/listview.py
>>> +++ b/src/jarabe/journal/listview.py
>>> @@ -70,7 +70,8 @@ class BaseListView(Gtk.Bin):
>>> 'clear-clicked': (GObject.SignalFlags.RUN_FIRST, None, ([])),
>>> }
>>>
>>> - def __init__(self):
>>> + def __init__(self, is_object_chooser):
>>> + self._is_object_chooser = is_object_chooser
>>> self._query = {}
>>> self._model = None
>>> self._progress_bar = None
>>> @@ -101,11 +102,9 @@ class BaseListView(Gtk.Bin):
>>> self._title_column = None
>>> self.sort_column = None
>>> self._add_columns()
>>> -
>>> -
>>> self.tree_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
>>> - [('text/uri-list', 0,
>>> 0),
>>> - ('journal-object-id',
>>> 0, 0)],
>>> - Gdk.DragAction.COPY)
>>> + self._inhibit_refresh = False
>>> + self._selected_entries = 0
>>> + self.enable_drag_and_copy()
>>>
>>> # Auto-update stuff
>>> self._fully_obscured = True
>>> @@ -117,6 +116,15 @@ class BaseListView(Gtk.Bin):
>>> model.updated.connect(self.__model_updated_cb)
>>> model.deleted.connect(self.__model_deleted_cb)
>>>
>>> + def enable_drag_and_copy(self):
>>> +
>>> self.tree_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
>>> + [('text/uri-list', 0,
>>> 0),
>>> + ('journal-object-id',
>>> 0, 0)],
>>> + Gdk.DragAction.COPY)
>>> +
>>> + def disable_drag_and_copy(self):
>>> + self.tree_view.unset_rows_drag_source()
>>> +
>>> def __model_created_cb(self, sender, signal, object_id):
>>> if self._is_new_item_visible(object_id):
>>> self._set_dirty()
>>> @@ -137,6 +145,17 @@ class BaseListView(Gtk.Bin):
>>> return object_id.startswith(self._query['mountpoints'][0])
>>>
>>> def _add_columns(self):
>>> + if not self._is_object_chooser:
>>> + cell_select = CellRendererToggle(self.tree_view)
>>> + cell_select.connect('clicked',
>>> self.__cell_select_clicked_cb)
>>> +
>>> + column = Gtk.TreeViewColumn()
>>> + column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
>>> + column.props.fixed_width = cell_select.props.width
>>> + column.pack_start(cell_select, True)
>>> + column.set_cell_data_func(cell_select,
>>> self.__select_set_data_cb)
>>> + self.tree_view.append_column(column)
>>> +
>>> cell_favorite = CellRendererFavorite(self.tree_view)
>>> cell_favorite.connect('clicked', self.__favorite_clicked_cb)
>>>
>>> @@ -262,8 +281,30 @@ class BaseListView(Gtk.Bin):
>>>
>>> def __favorite_set_data_cb(self, column, cell, tree_model,
>>> tree_iter, data):
>>> - favorite = tree_model[tree_iter][ListModel.COLUMN_FAVORITE]
>>> - if favorite:
>>> + # Instead of querying the favorite-status from the "cached"
>>> + # entries in listmodel, hit the DS, and retrieve the persisted
>>> + # favorite-status.
>>> + # This solves the issue in "Multi-Select", wherein the
>>> + # listview is inhibited from refreshing. Now, if the user
>>> + # clicks favorite-star-icon(s), the change(s) is(are) written
>>> + # to the DS, but no refresh takes place. Thus, in order to have
>>> + # the change(s) reflected on the UI, we need to hit the DS for
>>> + # querying the favorite-status (instead of relying on the
>>> + # cached-listmodel.
>>> + uid = tree_model[tree_iter][ListModel.COLUMN_UID]
>>> + if uid is None:
>>> + return
>>> +
>>> + try:
>>> + metadata = model.get(uid)
>>> + except:
>>> + return
>>> +
>>> + favorite = None
>>> + if 'keep' in metadata.keys():
>>> + favorite = str(metadata['keep'])
>>> +
>>> + if favorite == '1':
>>> client = GConf.Client.get_default()
>>> color =
>>> XoColor(client.get_string('/desktop/sugar/user/color'))
>>> cell.props.xo_color = color
>>> @@ -279,7 +320,94 @@ class BaseListView(Gtk.Bin):
>>> metadata['keep'] = '0'
>>> else:
>>> metadata['keep'] = '1'
>>> - model.write(metadata, update_mtime=False)
>>> +
>>> + from jarabe.journal.journalactivity import get_mount_point
>>> + metadata['mountpoint'] = get_mount_point()
>>> +
>>> +
>>> model.update_only_metadata_and_preview_files_and_return_file_paths(metadata)
>>> + self.__redraw_view_if_necessary()
>>> +
>>> + def __select_set_data_cb(self, column, cell, tree_model, tree_iter,
>>> + data):
>>> + uid = tree_model[tree_iter][ListModel.COLUMN_UID]
>>> + if uid is None:
>>> + return
>>> +
>>> + # Hack to associate the cell with the metadata, so that it (the
>>> + # cell) is available offline as well (example during
>>> + # batch-operations, when the processing has to be done, without
>>> + # actually clicking any cell.
>>> + try:
>>> + metadata = model.get(uid)
>>> + except:
>>> + # https://dev.laptop.org.au/issues/1119
>>> + # http://bugs.sugarlabs.org/ticket/3344
>>> + # Occurs, when copying entries from journal to pen-drive.
>>> + # Simply swallow the exception, and return, as this too,
>>> + # like the above case, does not have any impact on the
>>> + # functionality.
>>> + return
>>> +
>>> + metadata['cell'] = cell
>>> + tree_model.update_uid_metadata_assoc(uid, metadata)
>>> +
>>> + self.do_ui_select_change(metadata)
>>> +
>>> + def __cell_select_clicked_cb(self, cell, path):
>>> + row = self._model[path]
>>> + treeiter = self._model.get_iter(path)
>>> + metadata = model.get(row[ListModel.COLUMN_UID])
>>> + self.do_backend_select_change(metadata)
>>> +
>>> + def do_ui_select_change(self, metadata):
>>> + tree_model = self.get_model()
>>> + selected = tree_model.get_selected_value(metadata['uid'])
>>> +
>>> + if 'cell' in metadata.keys():
>>> + cell = metadata['cell']
>>> + if selected:
>>> + cell.props.icon_name = 'emblem-checked'
>>> + else:
>>> + cell.props.icon_name = 'emblem-unchecked'
>>> +
>>> + def do_backend_select_change(self, metadata):
>>> + uid = metadata['uid']
>>> + selected = self._model.get_selected_value(uid)
>>> +
>>> + self._model.set_selected_value(uid, not selected)
>>> + self._process_new_selected_status(not selected)
>>> +
>>> + def _process_new_selected_status(self, new_status):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> + journal_toolbar_box = journal.get_toolbar_box()
>>> +
>>> + self.__redraw_view_if_necessary()
>>> +
>>> + if new_status == False:
>>> + self._selected_entries = self._selected_entries - 1
>>> +
>>> journal_toolbar_box.process_new_deselected_entry_in_multi_select()
>>> + GObject.idle_add(self._post_backend_processing)
>>> + else:
>>> + self._selected_entries = self._selected_entries + 1
>>> + journal.get_list_view().inhibit_refresh(True)
>>> + journal.switch_to_editing_mode(True)
>>> +
>>> + # For the case, when we are switching to editing-mode.
>>> + # The previous call won't actually redraw, as we are not in
>>> + # editing-mode that time.
>>> + self.__redraw_view_if_necessary()
>>> +
>>> +
>>> journal.get_toolbar_box().process_new_selected_entry_in_multi_select()
>>> +
>>> + def _post_backend_processing(self):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + if self._selected_entries == 0:
>>> + journal.switch_to_editing_mode(False)
>>> + journal.get_list_view().inhibit_refresh(False)
>>> + journal.get_list_view().refresh()
>>>
>>> def update_with_query(self, query_dict):
>>> logging.debug('ListView.update_with_query')
>>> @@ -296,6 +424,11 @@ class BaseListView(Gtk.Bin):
>>> self.refresh()
>>>
>>> def refresh(self):
>>> + if not self._inhibit_refresh:
>>> + self.set_sensitive(True)
>>> + self.proceed_with_refresh()
>>> +
>>> + def proceed_with_refresh(self):
>>> logging.debug('ListView.refresh query %r', self._query)
>>> self._stop_progress_bar()
>>>
>>> @@ -497,6 +630,64 @@ class BaseListView(Gtk.Bin):
>>> self.update_dates()
>>> return True
>>>
>>> + def get_model(self):
>>> + return self._model
>>> +
>>> + def inhibit_refresh(self, inhibit):
>>> + self._inhibit_refresh = inhibit
>>> +
>>> + def __redraw_view_if_necessary(self):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + if not get_journal().is_editing_mode_present():
>>> + return
>>> +
>>> + # First, get the total number of entries, for which the
>>> + # batch-operation is under progress.
>>> + from jarabe.journal.palettes import get_current_action_item
>>> +
>>> + current_action_item = get_current_action_item()
>>> + if current_action_item is None:
>>> + # A single checkbox has been clicked/unclicked.
>>> + self.__redraw()
>>> + return
>>> +
>>> + total_items =
>>> current_action_item.get_number_of_entries_to_operate_upon()
>>> +
>>> + # Then, get the current entry being processed.
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> + current_entry_number =
>>> journal.get_toolbar_box().get_current_entry_number()
>>> +
>>> + # Redraw, if "current_entry_number" is 10.
>>> + if current_entry_number == 10:
>>> + self.__log(current_entry_number, total_items)
>>> + self.__redraw()
>>> + return
>>> +
>>> + # Redraw, if this is the last entry.
>>> + if current_entry_number == total_items:
>>> + self.__log(current_entry_number, total_items)
>>> + self.__redraw()
>>> + return
>>> +
>>> + # Redraw, if this is the 20% interval.
>>> + twenty_percent_of_total_items = total_items / 5
>>> + if twenty_percent_of_total_items < 10:
>>> + return
>>> +
>>> + if (current_entry_number % twenty_percent_of_total_items) == 0:
>>> + self.__log(current_entry_number, total_items)
>>> + self.__redraw()
>>> + return
>>> +
>>> + def __log(self, current_entry_number, total_items):
>>> + pass
>>> +
>>> + def __redraw(self):
>>> + tree_view_window = self.tree_view.get_bin_window()
>>> + tree_view_window.hide()
>>> + tree_view_window.show()
>>> +
>>>
>>> class ListView(BaseListView):
>>> __gtype_name__ = 'JournalListView'
>>> @@ -512,8 +703,8 @@ class ListView(BaseListView):
>>> ([])),
>>> }
>>>
>>> - def __init__(self):
>>> - BaseListView.__init__(self)
>>> + def __init__(self, is_object_chooser=False):
>>> + BaseListView.__init__(self, is_object_chooser)
>>> self._is_dragging = False
>>>
>>> self.tree_view.connect('drag-begin', self.__drag_begin_cb)
>>> @@ -579,6 +770,11 @@ class ListView(BaseListView):
>>> misc.resume(metadata)
>>>
>>> def __cell_title_edited_cb(self, cell, path, new_text):
>>> + from jarabe.journal.journalactivity import get_journal, \
>>> + get_mount_point
>>> + if get_journal().is_editing_mode_present():
>>> + return
>>> +
>>> row = self._model[path]
>>> metadata = model.get(row[ListModel.COLUMN_UID])
>>> metadata['title'] = new_text
>>> @@ -607,6 +803,18 @@ class CellRendererFavorite(CellRendererIcon):
>>> self.props.prelit_stroke_color = prelit_color.get_stroke_color()
>>> self.props.prelit_fill_color = prelit_color.get_fill_color()
>>>
>>> +class CellRendererToggle(CellRendererIcon):
>>> + __gtype_name__ = 'JournalCellRendererSelect'
>>> +
>>> + def __init__(self, tree_view):
>>> + CellRendererIcon.__init__(self, tree_view)
>>> +
>>> + self.props.width = style.GRID_CELL_SIZE
>>> + self.props.height = style.GRID_CELL_SIZE
>>> + self.props.size = style.SMALL_ICON_SIZE
>>> + self.props.icon_name = 'checkbox-unchecked'
>>> + self.props.mode = Gtk.CellRendererMode.ACTIVATABLE
>>> +
>>>
>>> class CellRendererDetail(CellRendererIcon):
>>> __gtype_name__ = 'JournalCellRendererDetail'
>>> @@ -651,6 +859,11 @@ class CellRendererActivityIcon(CellRendererIcon):
>>> if not self._show_palette:
>>> return None
>>>
>>> + # Also, if we are in batch-operations mode, return 'None'
>>> + from jarabe.journal.journalactivity import get_journal
>>> + if get_journal().is_editing_mode_present():
>>> + return None
>>> +
>>> tree_model = self.tree_view.get_model()
>>> metadata =
>>> tree_model.get_metadata(self.props.palette_invoker.path)
>>>
>>> diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
>>> index 0a5b354..f1fe931 100644
>>> --- a/src/jarabe/journal/model.py
>>> +++ b/src/jarabe/journal/model.py
>>> @@ -16,6 +16,7 @@
>>>
>>> import logging
>>> import os
>>> +import stat
>>> import errno
>>> import subprocess
>>> from datetime import datetime
>>> @@ -58,6 +59,16 @@ updated = dispatch.Signal()
>>> deleted = dispatch.Signal()
>>>
>>>
>>> +def _get_mount_point(path):
>>> + dir_path = os.path.dirname(path)
>>> + while dir_path:
>>> + if os.path.ismount(dir_path):
>>> + return dir_path
>>> + else:
>>> + dir_path = dir_path.rsplit(os.sep, 1)[0]
>>> + return None
>>> +
>>> +
>>> class _Cache(object):
>>>
>>> __gtype_name__ = 'model_Cache'
>>> @@ -201,6 +212,14 @@ class BaseResultSet(object):
>>>
>>> return self._cache[self._position - self._offset]
>>>
>>> + def is_favorite_compatible(self, metadata):
>>> + if self._favorite == '0':
>>> + return True
>>> +
>>> + return ((metadata is not None) and \
>>> + ('keep' in metadata.keys()) and \
>>> + (str(metadata['keep']) == '1'))
>>> +
>>>
>>> class DatastoreResultSet(BaseResultSet):
>>> """Encapsulates the result of a query on the datastore
>>> @@ -264,6 +283,8 @@ class InplaceResultSet(BaseResultSet):
>>>
>>> self._sort = query.get('order_by', ['+timestamp'])[0]
>>>
>>> + self._favorite = str(query.get('keep', 0))
>>> +
>>> def setup(self):
>>> self._file_list = []
>>> self._pending_directories = [self._mount_point]
>>> @@ -371,10 +392,13 @@ class InplaceResultSet(BaseResultSet):
>>> if S_IFMT(stat.st_mode) != S_IFREG:
>>> return
>>>
>>> + metadata = _get_file_metadata(full_path, stat,
>>> + fetch_preview=False)
>>> +
>>> + if not self.is_favorite_compatible(metadata):
>>> + return
>>> if self._regex is not None and \
>>> not self._regex.match(full_path):
>>> - metadata = _get_file_metadata(full_path, stat,
>>> - fetch_preview=False)
>>> if not metadata:
>>> return
>>> add_to_list = False
>>> @@ -434,9 +458,13 @@ def _get_file_metadata(path, stat,
>>> fetch_preview=True):
>>> metadata = _get_file_metadata_from_json(dir_path, filename,
>>> fetch_preview)
>>> if metadata:
>>> if 'filesize' not in metadata:
>>> - metadata['filesize'] = stat.st_size
>>> + if stat is not None:
>>> + metadata['filesize'] = stat.st_size
>>> return metadata
>>>
>>> + if stat is None:
>>> + raise ValueError('File does not exist')
>>> +
>>> mime_type, uncertain_result_ = Gio.content_type_guess(filename=path,
>>> data=None)
>>> return {'uid': path,
>>> @@ -457,10 +485,17 @@ def _get_file_metadata_from_json(dir_path,
>>> filename, fetch_preview):
>>> If the metadata is corrupted we do remove it and the preview as
>>> well.
>>>
>>> """
>>> +
>>> + # In case of nested mount-points, (eg. ~/Documents/in1/in2/in3.txt),
>>> + # "dir_path = ~/Documents/in1/in2"; while
>>> + # "metadata_dir_path = ~/Documents".
>>> + from jarabe.journal.journalactivity import get_mount_point
>>> + metadata_dir_path = get_mount_point()
>>> +
>>> metadata = None
>>> - metadata_path = os.path.join(dir_path, JOURNAL_METADATA_DIR,
>>> + metadata_path = os.path.join(metadata_dir_path,
>>> JOURNAL_METADATA_DIR,
>>> filename + '.metadata')
>>> - preview_path = os.path.join(dir_path, JOURNAL_METADATA_DIR,
>>> + preview_path = os.path.join(metadata_dir_path, JOURNAL_METADATA_DIR,
>>> filename + '.preview')
>>>
>>> if not os.path.exists(metadata_path):
>>> @@ -546,8 +581,12 @@ def _get_mount_point(path):
>>> def get(object_id):
>>> """Returns the metadata for an object
>>> """
>>> - if os.path.exists(object_id):
>>> - stat = os.stat(object_id)
>>> + if (object_id[0] == '/'):
>>> + if os.path.exists(object_id):
>>> + stat = os.stat(object_id)
>>> + else:
>>> + stat = None
>>> +
>>> metadata = _get_file_metadata(object_id, stat)
>>> metadata['mountpoint'] = _get_mount_point(object_id)
>>> else:
>>> @@ -620,7 +659,12 @@ def delete(object_id):
>>> def copy(metadata, mount_point):
>>> """Copies an object to another mount point
>>> """
>>> + # In all cases, "copy" means the actual duplication of
>>> + # the content.
>>> + transfer_ownership = False
>>> +
>>> metadata = get(metadata['uid'])
>>> +
>>> if mount_point == '/' and metadata['icon-color'] ==
>>> '#000000,#ffffff':
>>> client = GConf.Client.get_default()
>>> metadata['icon-color'] =
>>> client.get_string('/desktop/sugar/user/color')
>>> @@ -631,7 +675,7 @@ def copy(metadata, mount_point):
>>> metadata['mountpoint'] = mount_point
>>> del metadata['uid']
>>>
>>> - return write(metadata, file_path, transfer_ownership=False)
>>> + return write(metadata, file_path,
>>> transfer_ownership=transfer_ownership)
>>>
>>>
>>> def write(metadata, file_path='', update_mtime=True,
>>> transfer_ownership=True):
>>> @@ -654,21 +698,45 @@ def write(metadata, file_path='',
>>> update_mtime=True, transfer_ownership=True):
>>> file_path,
>>> transfer_ownership)
>>> else:
>>> - object_id = _write_entry_on_external_device(metadata, file_path)
>>> + object_id = _write_entry_on_external_device(metadata,
>>> + file_path,
>>> + transfer_ownership)
>>>
>>> return object_id
>>>
>>>
>>> -def _rename_entry_on_external_device(file_path, destination_path,
>>> - metadata_dir_path):
>>> +def make_file_fully_permissible(file_path):
>>> + fd = os.open(file_path, os.O_RDONLY)
>>> + os.fchmod(fd, stat.S_IRWXU | stat.S_IRWXG |stat.S_IRWXO)
>>> + os.close(fd)
>>> +
>>> +
>>> +def _rename_entry_on_external_device(file_path, destination_path):
>>> """Rename an entry with the associated metadata on an external
>>> device."""
>>> old_file_path = file_path
>>> if old_file_path != destination_path:
>>> - os.rename(file_path, destination_path)
>>> + # Strangely, "os.rename" works fine on sugar-jhbuild, but fails
>>> + # on XOs, wih the OSError 13 ("invalid cross-device link"). So,
>>> + # using the system call "mv".
>>> + os.system('mv "%s" "%s"' % (file_path, destination_path))
>>> + make_file_fully_permissible(destination_path)
>>> +
>>> +
>>> + # In renaming, we want to delete the metadata-, and preview-
>>> + # files of the current mount-point, and not the destination
>>> + # mount-point.
>>> + # But we also need to ensure that the directory of
>>> + # 'old_file_path' and 'destination_path' are not same.
>>> + if os.path.dirname(old_file_path) ==
>>> os.path.dirname(destination_path):
>>> + return
>>> +
>>> + from jarabe.journal.journalactivity import get_mount_point
>>> + source_metadata_dir_path = get_mount_point() +
>>> '/.Sugar-Metadata'
>>> +
>>> old_fname = os.path.basename(file_path)
>>> - old_files = [os.path.join(metadata_dir_path,
>>> + old_files = [os.path.join(source_metadata_dir_path,
>>> old_fname + '.metadata'),
>>> - os.path.join(metadata_dir_path,
>>> + os.path.join(source_metadata_dir_path,
>>> old_fname + '.preview')]
>>> for ofile in old_files:
>>> if os.path.exists(ofile):
>>> @@ -679,42 +747,27 @@ def _rename_entry_on_external_device(file_path,
>>> destination_path,
>>> 'for file=%s', ofile, old_fname)
>>>
>>>
>>> -def _write_entry_on_external_device(metadata, file_path):
>>> - """Create and update an entry copied from the
>>> - DS to an external storage device.
>>> -
>>> - Besides copying the associated file a file for the preview
>>> - and one for the metadata are stored in the hidden directory
>>> - .Sugar-Metadata.
>>> -
>>> - This function handles renames of an entry on the
>>> - external device and avoids name collisions. Renames are
>>> - handled failsafe.
>>> -
>>> - """
>>> - if 'uid' in metadata and os.path.exists(metadata['uid']):
>>> - file_path = metadata['uid']
>>> -
>>> - if not file_path or not os.path.exists(file_path):
>>> - raise ValueError('Entries without a file cannot be copied to '
>>> - 'removable devices')
>>> -
>>> - if not metadata.get('title'):
>>> - metadata['title'] = _('Untitled')
>>> - file_name = get_file_name(metadata['title'], metadata['mime_type'])
>>> -
>>> - destination_path = os.path.join(metadata['mountpoint'], file_name)
>>> - if destination_path != file_path:
>>> - file_name = get_unique_file_name(metadata['mountpoint'],
>>> file_name)
>>> - destination_path = os.path.join(metadata['mountpoint'],
>>> file_name)
>>> - clean_name, extension_ = os.path.splitext(file_name)
>>> - metadata['title'] = clean_name
>>> -
>>> +def _write_metadata_and_preview_files_and_return_file_paths(metadata,
>>> + file_name):
>>> metadata_copy = metadata.copy()
>>> metadata_copy.pop('mountpoint', None)
>>> metadata_copy.pop('uid', None)
>>> metadata_copy.pop('filesize', None)
>>>
>>> + # For journal case, there is the special treatment.
>>> + if metadata.get('mountpoint', '/') == '/':
>>> + if metadata.get('uid', ''):
>>> + object_id = _get_datastore().update(metadata['uid'],
>>> +
>>> dbus.Dictionary(metadata),
>>> + '',
>>> + False)
>>> + else:
>>> + object_id =
>>> _get_datastore().create(dbus.Dictionary(metadata),
>>> + '',
>>> + False)
>>> + return
>>> +
>>> +
>>> metadata_dir_path = os.path.join(metadata['mountpoint'],
>>> JOURNAL_METADATA_DIR)
>>> if not os.path.exists(metadata_dir_path):
>>> @@ -742,11 +795,77 @@ def _write_entry_on_external_device(metadata,
>>> file_path):
>>> os.close(fh)
>>> os.rename(fn, os.path.join(metadata_dir_path,
>>> preview_fname))
>>>
>>> - if not os.path.dirname(destination_path) ==
>>> os.path.dirname(file_path):
>>> - shutil.copy(file_path, destination_path)
>>> + metadata_destination_path = os.path.join(metadata_dir_path,
>>> file_name + '.metadata')
>>> + make_file_fully_permissible(metadata_destination_path)
>>> + if preview:
>>> + preview_destination_path = os.path.join(metadata_dir_path,
>>> preview_fname)
>>> + make_file_fully_permissible(preview_destination_path)
>>> else:
>>> - _rename_entry_on_external_device(file_path, destination_path,
>>> - metadata_dir_path)
>>> + preview_destination_path = None
>>> +
>>> + return (metadata_destination_path, preview_destination_path)
>>> +
>>> +
>>> +def
>>> update_only_metadata_and_preview_files_and_return_file_paths(metadata):
>>> + """
>>> +
>>> + This function replaces the following paradigm for updating just the
>>> + metadata ::
>>> +
>>> + def write(metadata, file_path, update_mtime=False)
>>> +
>>> +
>>> + Using the new API serves the following purpose ::
>>> + * Clearer name <===> Clearer intention
>>> + * Just one argument ("metadata")
>>> +
>>> + """
>>> +
>>> + file_name = metadata['title']
>>> + _write_metadata_and_preview_files_and_return_file_paths(metadata,
>>> + file_name)
>>> +
>>> +
>>> +def _write_entry_on_external_device(metadata, file_path,
>>> + transfer_ownership):
>>> + """Create and update an entry copied from the
>>> + DS to an external storage device.
>>> +
>>> + Besides copying the associated file a file for the preview
>>> + and one for the metadata are stored in the hidden directory
>>> + .Sugar-Metadata.
>>> +
>>> + This function handles renames of an entry on the
>>> + external device and avoids name collisions. Renames are
>>> + handled failsafe.
>>> +
>>> + """
>>> + if 'uid' in metadata and os.path.exists(metadata['uid']):
>>> + file_path = metadata['uid']
>>> +
>>> + if not file_path or not os.path.exists(file_path):
>>> + raise ValueError('Entries without a file cannot be copied to '
>>> + 'removable devices')
>>> +
>>> + if not metadata.get('title'):
>>> + metadata['title'] = _('Untitled')
>>> + file_name = get_file_name(metadata['title'], metadata['mime_type'])
>>> +
>>> + destination_path = os.path.join(metadata['mountpoint'], file_name)
>>> + if destination_path != file_path:
>>> + file_name = get_unique_file_name(metadata['mountpoint'],
>>> file_name)
>>> + destination_path = os.path.join(metadata['mountpoint'],
>>> file_name)
>>> + metadata['title'] = file_name
>>> +
>>> + _write_metadata_and_preview_files_and_return_file_paths(metadata,
>>> + file_name)
>>> +
>>> + if (os.path.dirname(destination_path) ==
>>> os.path.dirname(file_path)) or \
>>> + (transfer_ownership == True):
>>> + _rename_entry_on_external_device(file_path, destination_path)
>>> + else:
>>> + shutil.copy(file_path, destination_path)
>>> + make_file_fully_permissible(destination_path)
>>>
>>> object_id = destination_path
>>> created.send(None, object_id=object_id)
>>> @@ -757,12 +876,6 @@ def _write_entry_on_external_device(metadata,
>>> file_path):
>>> def get_file_name(title, mime_type):
>>> file_name = title
>>>
>>> - extension = mime.get_primary_extension(mime_type)
>>> - if extension is not None and extension:
>>> - extension = '.' + extension
>>> - if not file_name.endswith(extension):
>>> - file_name += extension
>>> -
>>> # Invalid characters in VFAT filenames. From
>>> # http://en.wikipedia.org/wiki/File_Allocation_Table
>>> invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|',
>>> '\x7F']
>>> @@ -770,11 +883,11 @@ def get_file_name(title, mime_type):
>>> for char in invalid_chars:
>>> file_name = file_name.replace(char, '_')
>>>
>>> - # FAT limit is 255, leave some space for uniqueness
>>> - max_len = 250
>>> - if len(file_name) > max_len:
>>> - name, extension = os.path.splitext(file_name)
>>> - file_name = name[0:max_len - len(extension)] + extension
>>> + extension = mime.get_primary_extension(mime_type)
>>> + if extension is not None and extension:
>>> + extension = '.' + extension
>>> + if not file_name.endswith(extension):
>>> + file_name += extension
>>>
>>> return file_name
>>>
>>> @@ -796,7 +909,15 @@ def is_editable(metadata):
>>> if metadata.get('mountpoint', '/') == '/':
>>> return True
>>> else:
>>> - return os.access(metadata['mountpoint'], os.W_OK)
>>> + # sl#3605: Instead of relying on mountpoint property being
>>> + # present in the metadata, use journalactivity api.
>>> + # This would work seamlessly, as "Details View' is
>>> + # called, upon an entry in the context of a singular
>>> + # mount-point.
>>> + from jarabe.journal.journalactivity import get_mount_point
>>> + mount_point = get_mount_point()
>>> +
>>> + return os.access(mount_point, os.W_OK)
>>>
>>>
>>> def get_documents_path():
>>> diff --git a/src/jarabe/journal/objectchooser.py
>>> b/src/jarabe/journal/objectchooser.py
>>> index 9315192..ccee840 100644
>>> --- a/src/jarabe/journal/objectchooser.py
>>> +++ b/src/jarabe/journal/objectchooser.py
>>> @@ -16,15 +16,20 @@
>>>
>>> from gettext import gettext as _
>>> import logging
>>> +import os
>>>
>>> from gi.repository import GObject
>>> from gi.repository import Gtk
>>> from gi.repository import Gdk
>>> from gi.repository import Wnck
>>>
>>> +from sugar3 import env
>>> +
>>> from sugar3.graphics import style
>>> from sugar3.graphics.toolbutton import ToolButton
>>>
>>> +from sugar3.datastore import datastore
>>> +
>>> from jarabe.journal.listview import BaseListView
>>> from jarabe.journal.listmodel import ListModel
>>> from jarabe.journal.journaltoolbox import MainToolbox
>>> @@ -48,6 +53,7 @@ class ObjectChooser(Gtk.Window):
>>> self.set_has_resize_grip(False)
>>>
>>> self._selected_object_id = None
>>> + self._callback = None
>>>
>>> self.add_events(Gdk.EventMask.VISIBILITY_NOTIFY_MASK)
>>> self.connect('visibility-notify-event',
>>> @@ -112,6 +118,15 @@ class ObjectChooser(Gtk.Window):
>>> self._selected_object_id = uid
>>> self.emit('response', Gtk.ResponseType.ACCEPT)
>>>
>>> + if self._callback is not None:
>>> + self._callback(self._selected_object_id)
>>> +
>>> + def get_selected_object(self):
>>> + if self._selected_object_id is None:
>>> + return None
>>> + else:
>>> + return datastore.get(self._selected_object_id)
>>> +
>>> def __delete_event_cb(self, chooser, event):
>>> self.emit('response', Gtk.ResponseType.DELETE_EVENT)
>>>
>>> @@ -122,6 +137,8 @@ class ObjectChooser(Gtk.Window):
>>>
>>> def __close_button_clicked_cb(self, button):
>>> self.emit('response', Gtk.ResponseType.DELETE_EVENT)
>>> + if self._callback is not None:
>>> + self._callback(self._selected_object_id)
>>>
>>> def get_selected_object_id(self):
>>> return self._selected_object_id
>>> @@ -141,6 +158,9 @@ class ObjectChooser(Gtk.Window):
>>> def __clear_clicked_cb(self, list_view):
>>> self._toolbar.clear_query()
>>>
>>> + def _set_callback(self, callback):
>>> + self._callback = callback
>>> +
>>>
>>> class TitleBox(VolumesToolbar):
>>> __gtype_name__ = 'TitleBox'
>>> @@ -179,7 +199,7 @@ class ChooserListView(BaseListView):
>>> }
>>>
>>> def __init__(self):
>>> - BaseListView.__init__(self)
>>> + BaseListView.__init__(self, True)
>>>
>>> self.cell_icon.props.show_palette = False
>>> self.tree_view.props.hover_selection = True
>>> diff --git a/src/jarabe/journal/palettes.py
>>> b/src/jarabe/journal/palettes.py
>>> index c675c6a..c790bc0 100644
>>> --- a/src/jarabe/journal/palettes.py
>>> +++ b/src/jarabe/journal/palettes.py
>>> @@ -15,6 +15,7 @@
>>> # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
>>> USA
>>>
>>> from gettext import gettext as _
>>> +from gettext import ngettext
>>> import logging
>>> import os
>>>
>>> @@ -39,6 +40,15 @@ from jarabe.model import mimeregistry
>>> from jarabe.journal import misc
>>> from jarabe.journal import model
>>> from jarabe.journal import journalwindow
>>> +from jarabe.journal.journalwindow import freeze_ui, \
>>> + unfreeze_ui, \
>>> + show_normal_cursor, \
>>> + show_waiting_cursor
>>> +
>>> +friends_model = friends.get_model()
>>> +
>>> +_copy_menu_helper = None
>>> +_current_action_item = None
>>>
>>>
>>> class ObjectPalette(Palette):
>>> @@ -100,6 +110,15 @@ class ObjectPalette(Palette):
>>> self.menu.append(menu_item)
>>> menu_item.show()
>>> copy_menu = CopyMenu(metadata)
>>> + copy_menu_helper = get_copy_menu_helper()
>>> +
>>> + metadata_list = []
>>> + metadata_list.append(metadata)
>>> + copy_menu_helper.insert_copy_to_menu_items(copy_menu,
>>> + metadata_list,
>>> + False,
>>> + False,
>>> + False)
>>> copy_menu.connect('volume-error', self.__volume_error_cb)
>>> menu_item.set_submenu(copy_menu)
>>>
>>> @@ -198,134 +217,576 @@ class CopyMenu(Gtk.Menu):
>>>
>>> __gsignals__ = {
>>> 'volume-error': (GObject.SignalFlags.RUN_FIRST, None,
>>> - ([str, str])),
>>> + ([str, str])),
>>> }
>>>
>>> def __init__(self, metadata):
>>> Gtk.Menu.__init__(self)
>>>
>>> - self._metadata = metadata
>>>
>>> - clipboard_menu = ClipboardMenu(self._metadata)
>>> - clipboard_menu.set_image(Icon(icon_name='toolbar-edit',
>>> - icon_size=Gtk.IconSize.MENU))
>>> - clipboard_menu.connect('volume-error', self.__volume_error_cb)
>>> - self.append(clipboard_menu)
>>> - clipboard_menu.show()
>>> +class ActionItem(GObject.GObject):
>>> + """
>>> + This class implements the course of actions that happens when
>>> clicking
>>> + upon an Action-Item (eg. Batch-Copy-Toolbar-button;
>>> + Actual-Batch-Copy-To-Journal-button;
>>> + Actual-Batch-Copy-To-Documents-button;
>>> + Actual-Batch-Copy-To-Mounted-Drive-button;
>>> + Actual-Batch-Copy-To-Clipboard-button;
>>> + Single-Copy-To-Journal-button;
>>> + Single-Copy-To-Documents-button;
>>> + Single-Copy-To-Mounted-Drive-button;
>>> + Single-Copy-To-Clipboard-button;
>>> + Batch-Erase-Button;
>>> + Select-None-Toolbar-button;
>>> + Select-All-Toolbar-button
>>> + """
>>> + __gtype_name__ = 'JournalActionItem'
>>> +
>>> + def __init__(self, label, metadata_list, show_editing_alert,
>>> + show_progress_info_alert, batch_mode,
>>> + auto_deselect_source_entries,
>>> + need_to_popup_options,
>>> + operate_on_deselected_entries,
>>> + show_not_completed_ops_info):
>>> + GObject.GObject.__init__(self)
>>> +
>>> + self._label = label
>>> +
>>> + # Make a copy.
>>> + self._immutable_metadata_list = []
>>> + for metadata in metadata_list:
>>> + self._immutable_metadata_list.append(metadata)
>>> +
>>> + self._metadata_list = metadata_list
>>> + self._show_progress_info_alert = show_progress_info_alert
>>> + self._batch_mode = batch_mode
>>> + self._auto_deselect_source_entries = \
>>> + auto_deselect_source_entries
>>> + self._need_to_popup_options = \
>>> + need_to_popup_options
>>> + self._operate_on_deselected_entries = \
>>> + operate_on_deselected_entries
>>> + self._show_not_completed_ops_info = \
>>> + show_not_completed_ops_info
>>> +
>>> + actionable_signal = self._get_actionable_signal()
>>> +
>>> + if need_to_popup_options:
>>> + self.connect(actionable_signal,
>>> self._pre_fill_and_pop_up_options)
>>> + else:
>>> + if show_editing_alert:
>>> + self.connect(actionable_signal,
>>> self._show_editing_alert)
>>> + else:
>>> + self.connect(actionable_signal,
>>> + self._pre_operate_per_action,
>>> + Gtk.ResponseType.OK)
>>> +
>>> + def _get_actionable_signal(self):
>>> + """
>>> + Some widgets like 'buttons' have 'clicked' as actionable signal;
>>> + some like 'menuitems' have 'activate' as actionable signal.
>>> + """
>>> +
>>> + raise NotImplementedError
>>> +
>>> + def _pre_fill_and_pop_up_options(self, widget_clicked):
>>> + self._set_current_action_item_widget()
>>> + self._fill_and_pop_up_options(widget_clicked)
>>> +
>>> + def _fill_and_pop_up_options(self, widget_clicked):
>>> + """
>>> + Eg. Batch-Copy-Toolbar-button does not do anything by itself
>>> + useful; but rather pops-up the actual 'copy-to' options.
>>> + """
>>> +
>>> + raise NotImplementedError
>>> +
>>> + def _show_editing_alert(self, widget_clicked):
>>> + """
>>> + Upon clicking the actual operation button (eg.
>>> + Batch-Erase-Button and Batch-Copy-To-Clipboard button; BUT NOT
>>> + Batch-Copy-Toolbar-button, since it does not do anything
>>> + actually useful, but only pops-up the actual 'copy-to' options.
>>> + """
>>> +
>>> + freeze_ui()
>>> + GObject.idle_add(self.__show_editing_alert_after_freezing_ui,
>>> + widget_clicked)
>>> +
>>> + def __show_editing_alert_after_freezing_ui(self, widget_clicked):
>>> + self._set_current_action_item_widget()
>>> +
>>> + alert_parameters = self._get_editing_alert_parameters()
>>> + title = alert_parameters[0]
>>> + message = alert_parameters[1]
>>> + operation = alert_parameters[2]
>>> +
>>> + from jarabe.journal.journalactivity import get_journal
>>> + get_journal().update_confirmation_alert(title, message,
>>> +
>>> self._pre_operate_per_action,
>>> + None)
>>> +
>>> + def _get_editing_alert_parameters(self):
>>> + """
>>> + Get the alert parameters for widgets that can show editing
>>> + alert.
>>> + """
>>> +
>>> + self._metadata_list = self._get_metadata_list()
>>> + entries_len = len(self._metadata_list)
>>> +
>>> + title = self._get_editing_alert_title()
>>> + message = self._get_editing_alert_message(entries_len)
>>> + operation = self._get_editing_alert_operation()
>>> +
>>> + return (title, message, operation)
>>> +
>>> + def _get_list_model_len(self):
>>> + """
>>> + Get the total length of the model under view.
>>> + """
>>> +
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + return len(journal.get_list_view().get_model())
>>> +
>>> + def _get_metadata_list(self):
>>> + """
>>> + For batch-mode, get the metadata list, according to button-type.
>>> + For eg, Select-All-Toolbar-button operates on non-selected
>>> entries;
>>> + while othere operate on selected-entries.
>>> +
>>> + For single-mode, simply copy from the
>>> + "immutable_metadata_list".
>>> + """
>>> +
>>> + if self._batch_mode:
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + if self._operate_on_deselected_entries:
>>> + metadata_list = journal.get_metadata_list(False)
>>> + else:
>>> + metadata_list = journal.get_metadata_list(True)
>>>
>>> - 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.IconSize.MENU))
>>> - journal_menu.connect('volume-error', self.__volume_error_cb)
>>> - self.append(journal_menu)
>>> - journal_menu.show()
>>> + # Make a backup copy, of this metadata_list.
>>> + self._immutable_metadata_list = []
>>> + for metadata in metadata_list:
>>> + self._immutable_metadata_list.append(metadata)
>>>
>>> - documents_path = model.get_documents_path()
>>> - if documents_path is not None and not \
>>> - self._metadata['uid'].startswith(documents_path):
>>> - documents_menu = VolumeMenu(self._metadata, _('Documents'),
>>> - documents_path)
>>> - documents_menu.set_image(Icon(icon_name='user-documents',
>>> - icon_size=Gtk.IconSize.MENU))
>>> - documents_menu.connect('volume-error',
>>> self.__volume_error_cb)
>>> - self.append(documents_menu)
>>> - documents_menu.show()
>>> + return metadata_list
>>> + else:
>>> + metadata_list = []
>>> + for metadata in self._immutable_metadata_list:
>>> + metadata_list.append(metadata)
>>> + return metadata_list
>>> +
>>> + def _get_editing_alert_title(self):
>>> + raise NotImplementedError
>>> +
>>> + def _get_editing_alert_message(self, entries_len):
>>> + raise NotImplementedError
>>> +
>>> + def _get_editing_alert_operation(self):
>>> + raise NotImplementedError
>>> +
>>> + def _is_metadata_list_empty(self):
>>> + return (self._metadata_list is None) or \
>>> + (len(self._metadata_list) == 0)
>>> +
>>> + def _set_current_action_item_widget(self):
>>> + """
>>> + Only set this, if this widget achieves some effective action.
>>> + """
>>> + if not self._need_to_popup_options:
>>> + global _current_action_item
>>> + _current_action_item = self
>>> +
>>> + def _pre_operate_per_action(self, obj, response_id):
>>> + """
>>> + This is the stage, just before the FIRST metadata gets into its
>>> + processing cycle.
>>> + """
>>> + freeze_ui()
>>> +
>>> GObject.idle_add(self._pre_operate_per_action_after_done_ui_freezing,
>>> + obj, response_id)
>>> +
>>> + def _pre_operate_per_action_after_done_ui_freezing(self, obj,
>>> + response_id):
>>> + self._set_current_action_item_widget()
>>> +
>>> + self._continue_operation = True
>>> +
>>> + # If the user chose to cancel the operation from the onset,
>>> + # simply proceeed to the last.
>>> + if response_id == Gtk.ResponseType.CANCEL:
>>> + unfreeze_ui()
>>> +
>>> + self._cancel_further_batch_operation_items()
>>> + self._post_operate_per_action()
>>> + return
>>>
>>> - volume_monitor = Gio.VolumeMonitor.get()
>>> - icon_theme = Gtk.IconTheme.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.IconSize.MENU))
>>> - break
>>> - volume_menu.connect('volume-error', self.__volume_error_cb)
>>> - self.append(volume_menu)
>>> - volume_menu.show()
>>> + self._skip_all = False
>>>
>>> - def __volume_error_cb(self, menu_item, message, severity):
>>> - self.emit('volume-error', message, severity)
>>> + # Also, get the initial length of the model.
>>> + self._model_len = self._get_list_model_len()
>>>
>>> + # Speed Optimisation:
>>> + # ===================
>>> + # If the metadata-list is empty, fetch it;
>>> + # else we have already fetched it, when we showed the
>>> + # "editing-alert".
>>> + if len(self._metadata_list) == 0:
>>> + self._metadata_list = self._get_metadata_list()
>>>
>>> -class VolumeMenu(MenuItem):
>>> - __gtype_name__ = 'JournalVolumeMenu'
>>> + # Set the initial length of metadata-list.
>>> + self._metadata_list_initial_len = len(self._metadata_list)
>>>
>>> - __gsignals__ = {
>>> - 'volume-error': (GObject.SignalFlags.RUN_FIRST, None,
>>> - ([str, str])),
>>> - }
>>> + self._metadata_processed = 0
>>>
>>> - def __init__(self, metadata, label, mount_point):
>>> - MenuItem.__init__(self, label)
>>> - self._metadata = metadata
>>> - self.connect('activate', self.__copy_to_volume_cb, mount_point)
>>> + # Next, proceed with the metadata
>>> + self._pre_operate_per_metadata_per_action()
>>>
>>> - def __copy_to_volume_cb(self, menu_item, mount_point):
>>> - file_path = model.get_file(self._metadata['uid'])
>>> + def _pre_operate_per_metadata_per_action(self):
>>> + """
>>> + This is the stage, just before EVERY metadata gets into doing
>>> + its actual work.
>>> + """
>>> +
>>> + show_waiting_cursor()
>>> +
>>> GObject.idle_add(self.__pre_operate_per_metadata_per_action_after_freezing_ui)
>>> +
>>> + def __pre_operate_per_metadata_per_action_after_freezing_ui(self):
>>> + from jarabe.journal.journalactivity import get_journal
>>> +
>>> + # If there is still some metadata left, proceed with the
>>> + # metadata operation.
>>> + # Else, proceed to post-operations.
>>> + if len(self._metadata_list) > 0:
>>> + metadata = self._metadata_list.pop(0)
>>> +
>>> + # If info-alert needs to be shown, show the alert, and
>>> + # arrange for actual operation.
>>> + # Else, proceed to actual operation directly.
>>> + if self._show_progress_info_alert:
>>> + current_len = len(self._metadata_list)
>>> +
>>> + # TRANS: Do not translate the two %d, and the %s.
>>> + info_alert_message = _(' %d of %d : %s') % (
>>> + self._metadata_list_initial_len - current_len,
>>> + self._metadata_list_initial_len,
>>> metadata['title'])
>>> +
>>> +
>>> get_journal().update_info_alert(self._get_info_alert_title(),
>>> + info_alert_message)
>>> +
>>> + # Call the core-function !!
>>> + GObject.idle_add(self._operate_per_metadata_per_action,
>>> metadata)
>>> + else:
>>> + self._post_operate_per_action()
>>> +
>>> + def _get_info_alert_title(self):
>>> + raise NotImplementedError
>>> +
>>> + def _operate_per_metadata_per_action(self, metadata):
>>> + """
>>> + This is just a code-convenient-function, which allows
>>> + runtime-overriding. It just delegates to the actual
>>> + "self._operate" method, the actual which is determined at
>>> + runtime.
>>> + """
>>> +
>>> + if self._continue_operation is False:
>>> + # Jump directly to the post-operation
>>> + self._post_operate_per_metadata_per_action(metadata)
>>> + else:
>>> + # Pass the callback for the post-operation-for-metadata.
>>> This
>>> + # will ensure that async-operations on the metadata are
>>> taken
>>> + # care of.
>>> + if self._operate(metadata) is False:
>>> + return
>>> + else:
>>> + self._metadata_processed = self._metadata_processed + 1
>>> +
>>> + def _operate(self, metadata):
>>> + """
>>> + Actual, core, productive stage for EVERY metadata.
>>> + """
>>> +
>>> + raise NotImplementedError
>>> +
>>> + def _post_operate_per_metadata_per_action(self, metadata,
>>> + response_id=None):
>>> + """
>>> + This is the stage, just after EVERY metadata has been
>>> + processed.
>>> + """
>>> + # Toggle the corresponding checkbox - but only for batch-mode.
>>> + if self._batch_mode and self._auto_deselect_source_entries:
>>> + from jarabe.journal.journalactivity import get_journal
>>> + list_view = get_journal().get_list_view()
>>> +
>>> + list_view.do_ui_select_change(metadata)
>>> + list_view.do_backend_select_change(metadata)
>>> +
>>> + # Call the next ...
>>> + self._pre_operate_per_metadata_per_action()
>>> +
>>> + def _post_operate_per_action(self):
>>> + """
>>> + This is the stage, just after the LAST metadata has been
>>> + processed.
>>> + """
>>> +
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> + journal_toolbar_box = journal.get_toolbar_box()
>>> +
>>> + if self._batch_mode and (not
>>> self._auto_deselect_source_entries):
>>> +
>>> journal_toolbar_box.display_already_selected_entries_status()
>>> +
>>> + self._process_switching_mode(None, False)
>>> +
>>> + unfreeze_ui()
>>>
>>> + # Set the "_current_action_item" to None.
>>> + global _current_action_item
>>> + _current_action_item = None
>>> +
>>> + def _process_switching_mode(self, metadata, ok_clicked=False):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + # Necessary to do this, when the alert needs to be hidden,
>>> + # WITHOUT user-intervention.
>>> + journal.hide_alert()
>>> +
>>> + def _refresh(self):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + get_journal().get_list_view().refresh()
>>> +
>>> + def _handle_single_mode_notification(self, message, severity):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + journal._show_alert(message, severity)
>>> +
>>> + def _handle_error_alert(self, error_message, metadata):
>>> + """
>>> + This handles any error scenarios. Examples are of entries that
>>> + display the message "Entries without a file cannot be copied."
>>> + This is kind of controller-functionl the model-function is
>>> + "self._set_error_info_alert".
>>> + """
>>> +
>>> + if self._skip_all:
>>> + self._post_operate_per_metadata_per_action(metadata)
>>> + else:
>>> + self._set_error_info_alert(error_message, metadata)
>>> +
>>> + def _set_error_info_alert(self, error_message, metadata):
>>> + """
>>> + This method displays the error alert.
>>> + """
>>> +
>>> + current_len = len(self._metadata_list)
>>> +
>>> + # Only show the alert, if allowed to.
>>> + if self._show_not_completed_ops_info:
>>> + from jarabe.journal.journalactivity import get_journal
>>> + get_journal().update_confirmation_alert(_('Error'),
>>> + error_message,
>>> +
>>> self._process_error_skipping,
>>> + metadata)
>>> + else:
>>> + self._process_error_skipping(metadata, gtk.RESPONSE_OK)
>>> +
>>> + def _process_error_skipping(self, metadata, response_id):
>>> + # This sets up the decision, as to whether continue operations
>>> + # with the rest of the metadata.
>>> + if response_id == Gtk.ResponseType.CANCEL:
>>> + self._cancel_further_batch_operation_items()
>>> +
>>> + self._post_operate_per_metadata_per_action(metadata)
>>> +
>>> + def _cancel_further_batch_operation_items(self):
>>> + self._continue_operation = False
>>> +
>>> + # Optimization:
>>> + # Clear the metadata-list as well.
>>> + # This would prevent the unnecessary traversing of the
>>> + # remaining checkboxes-corresponding-to-remaining-metadata (of
>>> + # course without doing any effective action).
>>> + self._metadata_list = []
>>> +
>>> + def _file_path_valid(self, metadata):
>>> + file_path = model.get_file(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
>>> + error_message = _('Entries without a file cannot be
>>> copied.')
>>> + if self._batch_mode:
>>> + self._handle_error_alert(error_message, metadata)
>>> + else:
>>> + self._handle_single_mode_notification(error_message,
>>> _('Warning'))
>>> + return False
>>> + else:
>>> + return True
>>> +
>>> + def _copy_entry_and_check_status(self, metadata, mount_point):
>>> + self._set_bundle_installation_allowed(False)
>>>
>>> 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'))
>>> + model.copy(metadata, mount_point)
>>> + return True
>>> + except Exception, e:
>>> + logging.exception(e)
>>> + error_message = _('Error while copying the entry. %s') % e
>>> + if self._batch_mode:
>>> + self._handle_error_alert(error_message, metadata)
>>> + else:
>>> + self._handle_single_mode_notification(error_message,
>>> _('Error'))
>>> + return False
>>> + finally:
>>> + self._set_bundle_installation_allowed(True)
>>> +
>>> + def _set_bundle_installation_allowed(self, allowed):
>>> + """
>>> + This method serves only as a "delegating" method.
>>> + This has been done to aid easy configurability.
>>> + """
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>>
>>> + if self._batch_mode:
>>> + journal.set_bundle_installation_allowed(allowed)
>>>
>>> -class ClipboardMenu(MenuItem):
>>> - __gtype_name__ = 'JournalClipboardMenu'
>>> + def get_number_of_entries_to_operate_upon(self):
>>> + return len(self._immutable_metadata_list)
>>> +
>>> +
>>> +class BaseCopyMenuItem(MenuItem, ActionItem):
>>> + __gtype_name__ = 'JournalBaseCopyMenuItem'
>>>
>>> __gsignals__ = {
>>> - 'volume-error': (GObject.SignalFlags.RUN_FIRST, None,
>>> - ([str, str])),
>>> - }
>>> + 'volume-error': (GObject.SignalFlags.RUN_FIRST,
>>> + None, ([str, str])),
>>> + }
>>>
>>> - def __init__(self, metadata):
>>> - MenuItem.__init__(self, _('Clipboard'))
>>> + def __init__(self, metadata_list, label, show_editing_alert,
>>> + show_progress_info_alert, batch_mode, mount_point):
>>> + MenuItem.__init__(self, label)
>>> + ActionItem.__init__(self, label, metadata_list,
>>> show_editing_alert,
>>> + show_progress_info_alert, batch_mode,
>>> + auto_deselect_source_entries=False,
>>> + need_to_popup_options=False,
>>> + operate_on_deselected_entries=False,
>>> + show_not_completed_ops_info=True)
>>> + self._mount_point = mount_point
>>>
>>> - self._temp_file_path = None
>>> - self._metadata = metadata
>>> - self.connect('activate', self.__copy_to_clipboard_cb)
>>> + def get_mount_point(self):
>>> + return self._mount_point
>>>
>>> - 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
>>> + def _get_actionable_signal(self):
>>> + return 'activate'
>>> +
>>> + def _get_editing_alert_title(self):
>>> + return _('Copy')
>>> +
>>> + def _get_editing_alert_message(self, entries_len):
>>> + return ngettext('Do you want to copy %d entry to %s?',
>>> + 'Do you want to copy %d entries to %s?',
>>> + entries_len) % (entries_len, self._label)
>>> +
>>> + def _get_editing_alert_operation(self):
>>> + return _('Copy')
>>> +
>>> + def _get_info_alert_title(self):
>>> + return _('Copying')
>>> +
>>> + def _operate(self, metadata):
>>> + self._proceed_with_copy(metadata)
>>> +
>>> + def _proceed_with_copy(self, metadata):
>>> + return NotImplementedError
>>> +
>>> + def _post_successful_copy(self, metadata, response_id=None):
>>> + self._post_operate_per_metadata_per_action(metadata)
>>> +
>>> +
>>> +class VolumeMenu(BaseCopyMenuItem):
>>> + def __init__(self, metadata_list, label, mount_point,
>>> + show_editing_alert, show_progress_info_alert,
>>> + batch_mode):
>>> + BaseCopyMenuItem.__init__(self, metadata_list, label,
>>> + show_editing_alert,
>>> + show_progress_info_alert, batch_mode,
>>> + mount_point)
>>> +
>>> + def _proceed_with_copy(self, metadata):
>>> + if not self._file_path_valid(metadata):
>>> + return False
>>> +
>>> + if not self._copy_entry_and_check_status(metadata,
>>> self._mount_point):
>>> + return False
>>> +
>>> + # This is sync-operation. Thus, call the callback.
>>> + self._post_successful_copy(metadata)
>>> +
>>> +
>>> +class ClipboardMenu(BaseCopyMenuItem):
>>> + def __init__(self, metadata_list, show_editing_alert,
>>> + show_progress_info_alert, batch_mode):
>>> + BaseCopyMenuItem.__init__(self, metadata_list, _('Clipboard'),
>>> + show_editing_alert,
>>> + show_progress_info_alert,
>>> + batch_mode, None)
>>> + self._temp_file_path_list = []
>>> +
>>> + def _proceed_with_copy(self, metadata):
>>> + if not self._file_path_valid(metadata):
>>> + return False
>>>
>>> clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
>>> clipboard.set_with_data([Gtk.TargetEntry.new('text/uri-list',
>>> 0, 0)],
>>> self.__clipboard_get_func_cb,
>>> - self.__clipboard_clear_func_cb, None)
>>> + self.__clipboard_clear_func_cb,
>>> + metadata)
>>>
>>> - def __clipboard_get_func_cb(self, clipboard, selection_data, info,
>>> data):
>>> + def __clipboard_get_func_cb(self, clipboard, selection_data, info,
>>> + metadata):
>>> # Get hold of a reference so the temp file doesn't get deleted
>>> - self._temp_file_path = model.get_file(self._metadata['uid'])
>>> + self._temp_file_path = model.get_file(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):
>>> + def __clipboard_clear_func_cb(self, clipboard, metadata):
>>> # Release and delete the temp file
>>> self._temp_file_path = None
>>>
>>> + # This is async-operation; and this is the ending point.
>>> + self._post_successful_copy(metadata)
>>> +
>>> +
>>> +class DocumentsMenu(BaseCopyMenuItem):
>>> + def __init__(self, metadata_list, show_editing_alert,
>>> + show_progress_info_alert, batch_mode):
>>> + BaseCopyMenuItem.__init__(self, metadata_list, _('Documents'),
>>> + show_editing_alert,
>>> + show_progress_info_alert,
>>> + batch_mode,
>>> + model.get_documents_path())
>>> +
>>> + def _proceed_with_copy(self, metadata):
>>> + if not self._file_path_valid(metadata):
>>> + return False
>>> +
>>> + if not self._copy_entry_and_check_status(metadata,
>>> +
>>> model.get_documents_path()):
>>> + return False
>>> +
>>> + # This is sync-operation. Call the post-operation now.
>>> + self._post_successful_copy(metadata)
>>> +
>>>
>>> class FriendsMenu(Gtk.Menu):
>>> __gtype_name__ = 'JournalFriendsMenu'
>>> @@ -413,3 +874,92 @@ class BuddyPalette(Palette):
>>> icon=buddy_icon)
>>>
>>> # TODO: Support actions on buddies, like make friend, invite,
>>> etc.
>>> +
>>> +
>>> +class CopyMenuHelper(Gtk.Menu):
>>> + __gtype_name__ = 'JournalCopyMenuHelper'
>>> +
>>> + __gsignals__ = {
>>> + 'volume-error': (GObject.SignalFlags.RUN_FIRST,
>>> + None, ([str, str])),
>>> + }
>>> +
>>> + def insert_copy_to_menu_items(self, menu, metadata_list,
>>> + show_editing_alert,
>>> + show_progress_info_alert,
>>> + batch_mode):
>>> + self._metadata_list = metadata_list
>>> +
>>> + clipboard_menu = ClipboardMenu(metadata_list,
>>> + show_editing_alert,
>>> + show_progress_info_alert,
>>> + batch_mode)
>>> + clipboard_menu.set_image(Icon(icon_name='toolbar-edit',
>>> + icon_size=Gtk.IconSize.MENU))
>>> + clipboard_menu.connect('volume-error', self.__volume_error_cb)
>>> + menu.append(clipboard_menu)
>>> + clipboard_menu.show()
>>> +
>>> + from jarabe.journal.journalactivity import get_mount_point
>>> +
>>> + if get_mount_point() != model.get_documents_path():
>>> + documents_menu = DocumentsMenu(metadata_list,
>>> + show_editing_alert,
>>> + show_progress_info_alert,
>>> + batch_mode)
>>> + documents_menu.set_image(Icon(icon_name='user-documents',
>>> + icon_size=Gtk.IconSize.MENU))
>>> + documents_menu.connect('volume-error',
>>> self.__volume_error_cb)
>>> + menu.append(documents_menu)
>>> + documents_menu.show()
>>> +
>>> + if get_mount_point() != '/':
>>> + client = GConf.Client.get_default()
>>> + color =
>>> XoColor(client.get_string('/desktop/sugar/user/color'))
>>> + journal_menu = VolumeMenu(metadata_list, _('Journal'), '/',
>>> + show_editing_alert,
>>> + show_progress_info_alert,
>>> + batch_mode)
>>> + journal_menu.set_image(Icon(icon_name='activity-journal',
>>> + xo_color=color,
>>> + icon_size=Gtk.IconSize.MENU))
>>> + journal_menu.connect('volume-error', self.__volume_error_cb)
>>> + menu.append(journal_menu)
>>> + journal_menu.show()
>>> +
>>> + volume_monitor = Gio.VolumeMonitor.get()
>>> + icon_theme = Gtk.IconTheme.get_default()
>>> + for mount in volume_monitor.get_mounts():
>>> + if get_mount_point() == mount.get_root().get_path():
>>> + continue
>>> +
>>> + volume_menu = VolumeMenu(metadata_list, mount.get_name(),
>>> + mount.get_root().get_path(),
>>> + show_editing_alert,
>>> + show_progress_info_alert,
>>> + batch_mode)
>>> + 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.IconSize.MENU))
>>> + break
>>> +
>>> + volume_menu.connect('volume-error', self.__volume_error_cb)
>>> + menu.insert(volume_menu, -1)
>>> + volume_menu.show()
>>> +
>>> + def __volume_error_cb(self, menu_item, message, severity):
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> + journal._volume_error_cb(menu_item, message, severity)
>>> +
>>> +
>>> +def get_copy_menu_helper():
>>> + global _copy_menu_helper
>>> + if _copy_menu_helper is None:
>>> + _copy_menu_helper = CopyMenuHelper()
>>> + return _copy_menu_helper
>>> +
>>> +
>>> +def get_current_action_item():
>>> + return _current_action_item
>>> diff --git a/src/jarabe/journal/volumestoolbar.py
>>> b/src/jarabe/journal/volumestoolbar.py
>>> index e1e6331..76b691b 100644
>>> --- a/src/jarabe/journal/volumestoolbar.py
>>> +++ b/src/jarabe/journal/volumestoolbar.py
>>> @@ -193,6 +193,9 @@ class VolumesToolbar(Gtk.Toolbar):
>>> def _set_up_volumes(self):
>>> self._set_up_documents_button()
>>>
>>> + client = GConf.Client.get_default()
>>> + color = XoColor(client.get_string('/desktop/sugar/user/color'))
>>> +
>>> volume_monitor = Gio.VolumeMonitor.get()
>>> self._mount_added_hid = volume_monitor.connect('mount-added',
>>>
>>> self.__mount_added_cb)
>>> @@ -202,12 +205,11 @@ class VolumesToolbar(Gtk.Toolbar):
>>> for mount in volume_monitor.get_mounts():
>>> self._add_button(mount)
>>>
>>> - def _set_up_documents_button(self):
>>> - documents_path = model.get_documents_path()
>>> - if documents_path is not None:
>>> - button = DocumentsButton(documents_path)
>>> + def _set_up_directory_button(self, dir_path, icon_name, label_text):
>>> + if dir_path is not None:
>>> + button = DirectoryButton(dir_path, icon_name)
>>> button.props.group = self._volume_buttons[0]
>>> - label = GLib.markup_escape_text(_('Documents'))
>>> + label = GLib.markup_escape_text(label_text)
>>> button.set_palette(Palette(label))
>>> button.connect('toggled', self._button_toggled_cb)
>>> button.show()
>>> @@ -217,6 +219,12 @@ class VolumesToolbar(Gtk.Toolbar):
>>> self._volume_buttons.append(button)
>>> self.show()
>>>
>>> + def _set_up_documents_button(self):
>>> + documents_path = model.get_documents_path()
>>> + self._set_up_directory_button(documents_path,
>>> + 'user-documents',
>>> + _('Documents'))
>>> +
>>> def __mount_added_cb(self, volume_monitor, mount):
>>> self._add_button(mount)
>>>
>>> @@ -247,10 +255,26 @@ class VolumesToolbar(Gtk.Toolbar):
>>> def __volume_error_cb(self, button, strerror, severity):
>>> self.emit('volume-error', strerror, severity)
>>>
>>> - def _button_toggled_cb(self, button):
>>> - if button.props.active:
>>> + def _button_toggled_cb(self, button, force_toggle=False):
>>> + if button.props.active or force_toggle:
>>> + button.set_active(True)
>>> + from jarabe.journal.journalactivity import get_journal
>>> + journal = get_journal()
>>> +
>>> + journal.hide_alert()
>>> + journal.get_list_view()._selected_entries = 0
>>> + journal.switch_to_editing_mode(False)
>>> + journal.get_list_view().inhibit_refresh(False)
>>> +
>>> self.emit('volume-changed', button.mount_point)
>>>
>>> + def _unmount_activated_cb(self, menu_item, mount):
>>> + logging.debug('VolumesToolbar._unmount_activated_cb: %r', mount)
>>> + mount.unmount(self.__unmount_cb)
>>> +
>>> + def __unmount_cb(self, source, result):
>>> + logging.debug('__unmount_cb %r %r', source, result)
>>> +
>>> def _get_button_for_mount(self, mount):
>>> mount_point = mount.get_root().get_path()
>>> for button in self.get_children():
>>> @@ -259,6 +283,13 @@ class VolumesToolbar(Gtk.Toolbar):
>>> logging.error('Couldnt find button with mount_point %r',
>>> mount_point)
>>> return None
>>>
>>> + def _get_button_for_mount_point(self, mount_point):
>>> + for button in self.get_children():
>>> + if button.mount_point == mount_point:
>>> + return button
>>> + logging.error('Couldnt find button with mount_point %r',
>>> mount_point)
>>> + return None
>>> +
>>> def _remove_button(self, mount):
>>> button = self._get_button_for_mount(mount)
>>> self._volume_buttons.remove(button)
>>> @@ -272,6 +303,12 @@ class VolumesToolbar(Gtk.Toolbar):
>>> button = self._get_button_for_mount(mount)
>>> button.props.active = True
>>>
>>> + def get_journal_button(self):
>>> + return self._volume_buttons[0]
>>> +
>>> + def get_button_toggled_cb(self):
>>> + return self._button_toggled_cb
>>> +
>>>
>>> class BaseButton(RadioToolButton):
>>> __gsignals__ = {
>>> @@ -293,21 +330,25 @@ class BaseButton(RadioToolButton):
>>> selection_data, info, timestamp):
>>> object_id = selection_data.data
>>> metadata = model.get(object_id)
>>> - file_path = model.get_file(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(metadata, self.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'))
>>> + from jarabe.journal.palettes import CopyMenu,
>>> get_copy_menu_helper
>>> + copy_menu_helper = get_copy_menu_helper()
>>> +
>>> + dummy_copy_menu = CopyMenu()
>>> + copy_menu_helper.insert_copy_to_menu_items(dummy_copy_menu,
>>> + [metadata],
>>> + False,
>>> + False,
>>> + False)
>>> +
>>> + # Now, activate the menuitem, whose mount-point matches the
>>> + # mount-point of the button, upon whom the item has been
>>> + # dragged.
>>> + children_menu_items = dummy_copy_menu.get_children()
>>> + for child in children_menu_items:
>>> + if child.get_mount_point() == self.mount_point:
>>> + child.activate()
>>> + return
>>>
>>>
>>> class VolumeButton(BaseButton):
>>> @@ -386,12 +427,13 @@ class JournalButtonPalette(Palette):
>>> {'free_space': free_space / (1024 * 1024)}
>>>
>>>
>>> -class DocumentsButton(BaseButton):
>>> +class DirectoryButton(BaseButton):
>>>
>>> - def __init__(self, documents_path):
>>> - BaseButton.__init__(self, mount_point=documents_path)
>>> + def __init__(self, dir_path, icon_name):
>>> + BaseButton.__init__(self, mount_point=dir_path)
>>> + self._mount = dir_path
>>>
>>> - self.props.icon_name = 'user-documents'
>>> + self.props.icon_name = icon_name
>>>
>>> client = GConf.Client.get_default()
>>> color = XoColor(client.get_string('/desktop/sugar/user/color'))
>>> --
>>> 1.8.1.2
>>>
>>>
>>
>>
>> --
>> Regards,
>>
>> Ajay Garg
>> Dextrose Developer
>> Activity Central: http://activitycentral.com
>>
>
>
>
> --
> Daniel Narvaez
>
--
Regards,
Ajay Garg
Dextrose Developer
Activity Central: http://activitycentral.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.sugarlabs.org/archive/sugar-devel/attachments/20130415/327a9ad0/attachment-0001.html>
More information about the Sugar-devel
mailing list