1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3 4 5__license__ = 'GPL v3' 6__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' 7 8from functools import partial 9 10from qt.core import QTimer, QApplication, Qt, QEvent 11 12from calibre.gui2 import error_dialog 13from calibre.gui2.actions import InterfaceAction 14 15 16class MarkBooksAction(InterfaceAction): 17 18 name = 'Mark Books' 19 action_spec = (_('Mark books'), 'marked.png', _('Temporarily mark books for easy access'), 'Ctrl+M') 20 action_type = 'current' 21 action_add_menu = True 22 dont_add_to = frozenset([ 23 'context-menu-device', 'menubar-device', 'context-menu-cover-browser']) 24 action_menu_clone_qaction = _('Toggle mark for selected books') 25 26 accepts_drops = True 27 28 def accept_enter_event(self, event, mime_data): 29 if mime_data.hasFormat("application/calibre+from_library"): 30 return True 31 return False 32 33 def accept_drag_move_event(self, event, mime_data): 34 if mime_data.hasFormat("application/calibre+from_library"): 35 return True 36 return False 37 38 def drop_event(self, event, mime_data): 39 mime = 'application/calibre+from_library' 40 if mime_data.hasFormat(mime): 41 self.dropped_ids = tuple(map(int, mime_data.data(mime).data().split())) 42 QTimer.singleShot(1, self.do_drop) 43 return True 44 return False 45 46 def do_drop(self): 47 book_ids = self.dropped_ids 48 del self.dropped_ids 49 if book_ids: 50 self.toggle_ids(book_ids) 51 52 def genesis(self): 53 self.qaction.triggered.connect(self.toggle_selected) 54 self.menu = m = self.qaction.menu() 55 m.aboutToShow.connect(self.about_to_show_menu) 56 ma = partial(self.create_menu_action, m) 57 self.show_marked_action = a = ma('show-marked', _('Show marked books'), icon='search.png', shortcut='Shift+Ctrl+M') 58 a.triggered.connect(self.show_marked) 59 self.clear_marked_action = a = ma('clear-all-marked', _('Clear all marked books'), icon='clear_left.png') 60 a.triggered.connect(self.clear_all_marked) 61 m.addSeparator() 62 self.mark_author_action = a = ma('mark-author', _('Mark all books by selected author(s)'), icon='plus.png') 63 connect_lambda(a.triggered, self, lambda self: self.mark_field('authors', True)) 64 self.mark_series_action = a = ma('mark-series', _('Mark all books in the selected series'), icon='plus.png') 65 connect_lambda(a.triggered, self, lambda self: self.mark_field('series', True)) 66 m.addSeparator() 67 self.unmark_author_action = a = ma('unmark-author', _('Clear all books by selected author(s)'), icon='minus.png') 68 connect_lambda(a.triggered, self, lambda self: self.mark_field('authors', False)) 69 self.unmark_series_action = a = ma('unmark-series', _('Clear all books in the selected series'), icon='minus.png') 70 connect_lambda(a.triggered, self, lambda self: self.mark_field('series', False)) 71 72 def gui_layout_complete(self): 73 for x in self.gui.bars_manager.main_bars + self.gui.bars_manager.child_bars: 74 try: 75 w = x.widgetForAction(self.qaction) 76 w.installEventFilter(self) 77 except: 78 continue 79 80 def eventFilter(self, obj, ev): 81 if ev.type() == QEvent.Type.MouseButtonPress and ev.button() == Qt.MouseButton.LeftButton: 82 mods = QApplication.keyboardModifiers() 83 if mods & Qt.KeyboardModifier.ControlModifier or mods & Qt.KeyboardModifier.ShiftModifier: 84 self.show_marked() 85 return True 86 return False 87 88 def about_to_show_menu(self): 89 db = self.gui.current_db 90 num = len(frozenset(db.data.marked_ids).intersection(db.new_api.all_book_ids())) 91 text = _('Show marked book') if num == 1 else (_('Show marked books') + (' (%d)' % num)) 92 self.show_marked_action.setText(text) 93 94 def location_selected(self, loc): 95 enabled = loc == 'library' 96 self.qaction.setEnabled(enabled) 97 self.menuless_qaction.setEnabled(enabled) 98 for action in self.menu.actions(): 99 action.setEnabled(enabled) 100 101 def toggle_selected(self): 102 book_ids = self._get_selected_ids() 103 if book_ids: 104 self.toggle_ids(book_ids) 105 106 def _get_selected_ids(self): 107 rows = self.gui.library_view.selectionModel().selectedRows() 108 if not rows or len(rows) == 0: 109 d = error_dialog(self.gui, _('Cannot mark'), _('No books selected')) 110 d.exec() 111 return set() 112 return set(map(self.gui.library_view.model().id, rows)) 113 114 def toggle_ids(self, book_ids): 115 self.gui.current_db.data.toggle_marked_ids(book_ids) 116 117 def show_marked(self): 118 self.gui.search.set_search_string('marked:true') 119 120 def clear_all_marked(self): 121 self.gui.current_db.data.set_marked_ids(()) 122 if str(self.gui.search.text()).startswith('marked:'): 123 self.gui.search.set_search_string('') 124 125 def mark_field(self, field, add): 126 book_ids = self._get_selected_ids() 127 if not book_ids: 128 return 129 db = self.gui.current_db 130 items = set() 131 for book_id in book_ids: 132 items |= set(db.new_api.field_ids_for(field, book_id)) 133 book_ids = set() 134 for item_id in items: 135 book_ids |= db.new_api.books_for_field(field, item_id) 136 mids = db.data.marked_ids.copy() 137 for book_id in book_ids: 138 if add: 139 mids[book_id] = True 140 else: 141 mids.pop(book_id, None) 142 db.data.set_marked_ids(mids) 143