diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 98888ff818..77f6393573 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -923,6 +923,11 @@ class ActionSortBy(InterfaceActionBase): actual_plugin = 'calibre.gui2.actions.sort:SortByAction' description = _('Sort the list of books') +class ActionMarkBooks(InterfaceActionBase): + name = 'Mark Books' + actual_plugin = 'calibre.gui2.actions.mark_books:MarkBooksAction' + description = _('Temporarily mark books') + class ActionStore(InterfaceActionBase): name = 'Store' author = 'John Schember' @@ -953,7 +958,8 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks, ActionAddToLibrary, ActionEditCollections, ActionMatchBooks, ActionChooseLibrary, ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore, - ActionPluginUpdater, ActionPickRandom, ActionEditToC, ActionSortBy] + ActionPluginUpdater, ActionPickRandom, ActionEditToC, ActionSortBy, + ActionMarkBooks] # }}} diff --git a/src/calibre/db/view.py b/src/calibre/db/view.py index 74242fcf66..76690f440b 100644 --- a/src/calibre/db/view.py +++ b/src/calibre/db/view.py @@ -382,6 +382,12 @@ class View(object): if func is not None: func(old_marked_ids, cmids) + def toggle_marked_ids(self, book_ids): + book_ids = set(book_ids) + mids = set(self.marked_ids) + common = mids.intersection(book_ids) + self.set_marked_ids((mids | book_ids) - common) + def refresh(self, field=None, ascending=True, clear_caches=True, do_search=True): self._map = tuple(sorted(self.cache.all_book_ids())) self._map_filtered = tuple(self._map) diff --git a/src/calibre/gui2/actions/mark_books.py b/src/calibre/gui2/actions/mark_books.py new file mode 100644 index 0000000000..8711a55ef0 --- /dev/null +++ b/src/calibre/gui2/actions/mark_books.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' + +from functools import partial + +from PyQt4.Qt import QTimer, QApplication, Qt + +from calibre.gui2 import error_dialog +from calibre.gui2.actions import InterfaceAction + +class MarkBooksAction(InterfaceAction): + + name = 'Mark Books' + action_spec = (_('Mark Books'), 'marked.png', _('Temporarily mark books'), 'Ctrl+M') + action_type = 'current' + action_add_menu = True + dont_add_to = frozenset([ + 'toolbar-device', 'context-menu-device', 'menubar-device', 'context-menu-cover-browser']) + action_menu_clone_qaction = _('Toggle mark for selected books') + + accepts_drops = True + + def accept_enter_event(self, event, mime_data): + if mime_data.hasFormat("application/calibre+from_library"): + return True + return False + + def accept_drag_move_event(self, event, mime_data): + if mime_data.hasFormat("application/calibre+from_library"): + return True + return False + + def drop_event(self, event, mime_data): + mime = 'application/calibre+from_library' + if mime_data.hasFormat(mime): + self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split())) + QTimer.singleShot(1, self.do_drop) + return True + return False + + def do_drop(self): + book_ids = self.dropped_ids + del self.dropped_ids + if book_ids: + self.toggle_ids(book_ids) + + def genesis(self): + self.qaction.triggered.connect(self.toolbar_triggered) + self.menu = m = self.qaction.menu() + m.aboutToShow.connect(self.about_to_show_menu) + ma = partial(self.create_menu_action, m) + self.show_marked_action = a = ma('show-marked', _('Show marked books'), icon='search.png', shortcut='Shift+Ctrl+M') + a.triggered.connect(self.show_marked) + self.clear_marked_action = a = ma('clear-all-marked', _('Clear all marked books'), icon='clear_left.png') + a.triggered.connect(self.clear_all_marked) + m.addSeparator() + self.mark_author_action = a = ma('mark-author', _('Mark all books by selected author(s)'), icon='plus.png') + a.triggered.connect(partial(self.mark_field, 'authors', True)) + self.mark_series_action = a = ma('mark-series', _('Mark all books in the selected series'), icon='plus.png') + a.triggered.connect(partial(self.mark_field, 'series', True)) + m.addSeparator() + self.unmark_author_action = a = ma('unmark-author', _('Clear all books by selected author(s)'), icon='minus.png') + a.triggered.connect(partial(self.mark_field, 'authors', False)) + self.unmark_series_action = a = ma('unmark-series', _('Clear all books in the selected series'), icon='minus.png') + a.triggered.connect(partial(self.mark_field, 'series', False)) + + def about_to_show_menu(self): + db = self.gui.current_db + num = len(db.data.marked_ids) + text = ngettext('Show marked book', 'Show marked books (%d)' % num, num) + self.show_marked_action.setText(text) + + def location_selected(self, loc): + enabled = loc == 'library' + self.qaction.setEnabled(enabled) + + def toolbar_triggered(self): + mods = QApplication.keyboardModifiers() + if mods & Qt.ControlModifier or mods & Qt.ShiftModifier: + self.show_marked() + else: + self.toggle_selected() + + def toggle_selected(self): + book_ids = self._get_selected_ids() + if book_ids: + self.toggle_ids(book_ids) + + def _get_selected_ids(self): + rows = self.gui.library_view.selectionModel().selectedRows() + if not rows or len(rows) == 0: + d = error_dialog(self.gui, _('Cannot mark'), _('No books selected')) + d.exec_() + return set([]) + return set(map(self.gui.library_view.model().id, rows)) + + def toggle_ids(self, book_ids): + self.gui.current_db.data.toggle_marked_ids(book_ids) + + def show_marked(self): + self.gui.search.set_search_string('marked:true') + + def clear_all_marked(self): + self.gui.current_db.data.set_marked_ids(()) + if unicode(self.gui.search.text()).startswith('marked:'): + self.gui.search.set_search_string('') + + def mark_field(self, field, add): + book_ids = self._get_selected_ids() + if not book_ids: + return + db = self.gui.current_db + items = set() + for book_id in book_ids: + items |= set(db.new_api.field_ids_for(field, book_id)) + book_ids = set() + for item_id in items: + book_ids |= db.new_api.books_for_field(field, item_id) + mids = db.data.marked_ids.copy() + for book_id in book_ids: + if add: + mids[book_id] = True + else: + mids.pop(book_id, None) + db.data.set_marked_ids(mids) +