Enhancement #1968810: provide an interface ot mark books with arbitrary text.

This commit is contained in:
Charles Haley 2022-04-13 21:23:56 +01:00
parent 02368d2888
commit a93e6b663f
5 changed files with 120 additions and 5 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -370,8 +370,9 @@ class View:
''' '''
old_marked_ids = set(self.marked_ids) old_marked_ids = set(self.marked_ids)
if not hasattr(id_dict, 'items'): if not hasattr(id_dict, 'items'):
# Simple list. Make it a dict of string 'true' # Simple list. Make it a dict entry of string 'true'
self.marked_ids = dict.fromkeys(id_dict, 'true') self.marked_ids = {k: (self.marked_ids[k] if k in self.marked_ids else 'true')
for k in id_dict}
else: else:
# Ensure that all the items in the dict are text # Ensure that all the items in the dict are text
self.marked_ids = {k: str(v) for k, v in iteritems(id_dict)} self.marked_ids = {k: str(v) for k, v in iteritems(id_dict)}

View File

@ -4,12 +4,81 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from collections import defaultdict
from functools import partial from functools import partial
from qt.core import QTimer, QApplication, Qt, QEvent from qt.core import (QTimer, QApplication, Qt, QEvent, QDialog, QMenu, QIcon,
QDialogButtonBox, QPushButton, QLabel, QGridLayout)
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2.widgets2 import HistoryComboBox
from calibre.utils.icu import sort_key
class MyHistoryComboBox(HistoryComboBox):
# This is here so we can change the following two class variables without
# affecting other users of the HistoryComboBox class
max_history_items = 10
min_history_entry_length = 1
class MarkWithTextDialog(QDialog):
def __init__(self, gui):
QDialog.__init__(self, parent=gui)
self.gui = gui
self.setWindowTitle(_('Mark books with text label'))
layout = QGridLayout()
layout.setColumnStretch(1, 10)
self.setLayout(layout)
self.text_box = textbox = MyHistoryComboBox()
textbox.initialize('mark_with_text')
history = textbox.all_items
button_rows = min(4, len(history))
for i in range(0, button_rows):
if i == 0:
layout.addWidget(QLabel(_('Recently used values:')), 0, 0, 1, 2)
button = QPushButton()
text = history[i]
button.setText(text)
button.clicked.connect(partial(self.button_pushed, text=text))
row = i + 1
layout.addWidget(button, row, 1)
label = QLabel('&' + str(row))
label.setBuddy(button)
layout.addWidget(label, row, 0)
if button_rows > 0:
layout.addWidget(QLabel(_('Enter a value:')), button_rows+1, 0, 1, 2)
label = QLabel('&' + str(button_rows+1))
else:
label = QLabel('')
label.setBuddy(textbox)
layout.addWidget(label, button_rows+2, 0, 1, 1)
layout.addWidget(textbox, button_rows+2, 1)
textbox.setFocus()
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok |
QDialogButtonBox.StandardButton.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box, button_rows+3, 0, 1, 2)
def text(self):
return self.text_box.text()
def button_pushed(self, checked, text=''):
self.text_box.setText(text)
self.text_box.save_history()
self.accept()
def accept(self):
if not self.text_box.text():
d = error_dialog(self.gui, _('Value cannot be empty'), _('You must provide a value'))
d.exec_()
else:
super().accept()
class MarkBooksAction(InterfaceAction): class MarkBooksAction(InterfaceAction):
@ -49,12 +118,18 @@ class MarkBooksAction(InterfaceAction):
self.toggle_ids(book_ids) self.toggle_ids(book_ids)
def genesis(self): def genesis(self):
self.search_icon = QIcon.ic('search.png')
self.qaction.triggered.connect(self.toggle_selected) self.qaction.triggered.connect(self.toggle_selected)
self.menu = m = self.qaction.menu() self.menu = m = self.qaction.menu()
m.aboutToShow.connect(self.about_to_show_menu) m.aboutToShow.connect(self.about_to_show_menu)
ma = partial(self.create_menu_action, m) ma = partial(self.create_menu_action, m)
self.show_marked_action = a = ma('mark_with_text', _('Mark books with text label'), icon='marked-text.png')
a.triggered.connect(self.mark_with_text)
self.show_marked_action = a = ma('show-marked', _('Show marked books'), icon='search.png', shortcut='Shift+Ctrl+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) a.triggered.connect(self.show_marked)
self.show_marked_with_text = QMenu(_('Show marked books with text label'))
self.show_marked_with_text.setIcon(self.search_icon)
m.addMenu(self.show_marked_with_text)
self.clear_marked_action = a = ma('clear-all-marked', _('Clear all marked books'), icon='clear_left.png') self.clear_marked_action = a = ma('clear-all-marked', _('Clear all marked books'), icon='clear_left.png')
a.triggered.connect(self.clear_all_marked) a.triggered.connect(self.clear_all_marked)
m.addSeparator() m.addSeparator()
@ -86,9 +161,22 @@ class MarkBooksAction(InterfaceAction):
def about_to_show_menu(self): def about_to_show_menu(self):
db = self.gui.current_db db = self.gui.current_db
num = len(frozenset(db.data.marked_ids).intersection(db.new_api.all_book_ids())) marked_ids = db.data.marked_ids
num = len(frozenset(marked_ids).intersection(db.new_api.all_book_ids()))
text = _('Show marked book') if num == 1 else (_('Show marked books') + (' (%d)' % num)) text = _('Show marked book') if num == 1 else (_('Show marked books') + (' (%d)' % num))
self.show_marked_action.setText(text) self.show_marked_action.setText(text)
counts = dict()
for v in marked_ids.values():
counts[v] = counts.get(v, 0) + 1
labels = sorted(counts.keys(), key=sort_key)
self.show_marked_with_text.clear()
if len(labels):
self.show_marked_with_text.setEnabled(True)
for t in labels:
ac = self.show_marked_with_text.addAction(self.search_icon, f'{t} ({counts[t]})')
ac.triggered.connect(partial(self.show_marked_text, txt=t))
else:
self.show_marked_with_text.setEnabled(False)
def location_selected(self, loc): def location_selected(self, loc):
enabled = loc == 'library' enabled = loc == 'library'
@ -116,6 +204,9 @@ class MarkBooksAction(InterfaceAction):
def show_marked(self): def show_marked(self):
self.gui.search.set_search_string('marked:true') self.gui.search.set_search_string('marked:true')
def show_marked_text(self, txt=None):
self.gui.search.set_search_string(f'marked:"={txt}"')
def clear_all_marked(self): def clear_all_marked(self):
self.gui.current_db.data.set_marked_ids(()) self.gui.current_db.data.set_marked_ids(())
if str(self.gui.search.text()).startswith('marked:'): if str(self.gui.search.text()).startswith('marked:'):
@ -139,3 +230,18 @@ class MarkBooksAction(InterfaceAction):
else: else:
mids.pop(book_id, None) mids.pop(book_id, None)
db.data.set_marked_ids(mids) db.data.set_marked_ids(mids)
def mark_with_text(self):
book_ids = self._get_selected_ids()
if not book_ids:
return
dialog = MarkWithTextDialog(self.gui)
if dialog.exec_() != QDialog.DialogCode.Accepted:
return
txt = dialog.text()
txt = txt if txt else 'true'
db = self.gui.current_db
mids = db.data.marked_ids.copy()
for book_id in book_ids:
mids[book_id] = txt
db.data.set_marked_ids(mids)

View File

@ -232,6 +232,7 @@ class BooksModel(QAbstractTableModel): # {{{
# remember that the cover grid view needs a larger version of the icon, # remember that the cover grid view needs a larger version of the icon,
# anyway) # anyway)
self.marked_icon = QIcon(I('marked.png')) self.marked_icon = QIcon(I('marked.png'))
self.marked_text_icon = QIcon(I('marked-text.png'))
self.bool_blank_icon_as_icon = QIcon(self.bool_blank_icon) self.bool_blank_icon_as_icon = QIcon(self.bool_blank_icon)
self.row_decoration = None self.row_decoration = None
self.device_connected = False self.device_connected = False
@ -1072,7 +1073,12 @@ class BooksModel(QAbstractTableModel): # {{{
return (section+1) return (section+1)
if role == Qt.ItemDataRole.DecorationRole: if role == Qt.ItemDataRole.DecorationRole:
try: try:
return self.marked_icon if self.db.data.get_marked(self.db.data.index_to_id(section)) else self.row_decoration m = self.db.data.get_marked(self.db.data.index_to_id(section))
if m:
i = self.marked_icon if m == 'true' else self.marked_text_icon
else:
i = self.row_decoration
return i
except (ValueError, IndexError): except (ValueError, IndexError):
pass pass
return None return None

View File

@ -946,6 +946,8 @@ class BooksView(QTableView): # {{{
# This is needed otherwise Qt does not always update the # This is needed otherwise Qt does not always update the
# viewport correctly. See https://bugs.launchpad.net/bugs/1404697 # viewport correctly. See https://bugs.launchpad.net/bugs/1404697
self.row_header.viewport().update() self.row_header.viewport().update()
# refresh the rows because there might be a composite that uses marked_books()
self.model().refresh_rows(changed)
else: else:
# Marked items have either appeared or all been removed # Marked items have either appeared or all been removed
self.model().set_row_decoration(current_marked) self.model().set_row_decoration(current_marked)