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