<div dir="ltr"><div><div><div><div><div><div><div>Thanks Daniel.<br><br></div>I have rebased the patches to the current state of the master branches, and created pull requests as per ::<br><br></div>[sugar]             :::  <a href="https://github.com/sugarlabs/sugar/pull/22">https://github.com/sugarlabs/sugar/pull/22</a><br>
</div>[sugar-artwork]  :::  <a href="https://github.com/sugarlabs/sugar-artwork/pull/1">https://github.com/sugarlabs/sugar-artwork/pull/1</a><br><br><br></div>Rebasing the patches (the "sugar" one) time  and again is a tedious task.<br>
<br>So if this works, please, please, please, please merge the patches into the mainline/master as soon as possible.<br></div>If I should do that, please let me know :)<br><br></div>In either case, of course it is me who takes the responsibility of fixing any regressions (if at all) that might be introduced.<br>
<br><br></div>Thanks, and looking forward to a definite action being taken soon, so as to avoid a further rebasing of the "sugar" patch.<br><br></div><div class="gmail_extra"><br><br><div class="gmail_quote">On Mon, Apr 15, 2013 at 12:53 AM, Daniel Narvaez <span dir="ltr"><<a href="mailto:dwnarvaez@gmail.com" target="_blank">dwnarvaez@gmail.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Did you read this?<br><br><a href="https://help.github.com/articles/using-pull-requests" target="_blank">https://help.github.com/articles/using-pull-requests</a><br>
<br>You need to <br><br>* Fork the repo on the web UI<br>* Clone your fork<br>
* Push the patches to your fork<br>* Make a pull request from the web UI<br><br><div class="gmail_quote">On 13 April 2013 06:13, Ajay Garg <span dir="ltr"><<a href="mailto:ajay@activitycentral.com" target="_blank">ajay@activitycentral.com</a>></span> wrote:<br>

<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div dir="ltr"><div><div><div><div><div>Hi all.<br></div>Any news on the progress on this front?<br><br></div>Should I commit this on github?<br>

<br></div>Daniel, Manuel : If yes, please let me know a step by step procedure for this.<br>
</div>                         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<br></div>                         to get this in guthub, only one single time :)<br>


<div><div><div><div><br></div></div></div></div></div><div class="gmail_extra"><br><br><div class="gmail_quote">On Wed, Apr 3, 2013 at 12:22 AM, Ajay Garg <span dir="ltr"><<a href="mailto:ajay@activitycentral.com" target="_blank">ajay@activitycentral.com</a>></span> wrote:<br>


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

<font face="arial, sans-serif">Dextrose Developer</font><br style="font-size:13px;font-family:arial,sans-serif">
<span style="font-size:13px;font-family:arial,sans-serif">Activity Central: </span><a href="http://activitycentral.com/" style="font-size:13px;font-family:arial,sans-serif" target="_blank">http://activitycentral.com</a>
</font></span></font></span></div><span class="HOEnZb"><font color="#888888">
</font></span></blockquote></div><span class="HOEnZb"><font color="#888888"><br><br clear="all"><br>-- <br>Daniel Narvaez<br>
</font></span></blockquote></div><br><br clear="all"><br>-- <br><font face="arial, sans-serif">Regards,<br><br>Ajay Garg</font><br style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)"><font face="arial, sans-serif">Dextrose Developer</font><br style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">
<span style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)">Activity Central: </span><a href="http://activitycentral.com/" style="font-family:arial,sans-serif;font-size:13px;background-color:rgb(255,255,255)" target="_blank">http://activitycentral.com</a>
</div>