This commit is contained in:
Kovid Goyal 2022-04-15 07:24:26 +05:30
commit f75f4db165
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 129 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)
if not hasattr(id_dict, 'items'):
# Simple list. Make it a dict of string 'true'
self.marked_ids = dict.fromkeys(id_dict, 'true')
# Simple list. Make it a dict entry of string 'true'
self.marked_ids = {k: (self.marked_ids[k] if k in self.marked_ids else 'true')
for k in id_dict}
else:
# Ensure that all the items in the dict are text
self.marked_ids = {k: str(v) for k, v in iteritems(id_dict)}

View File

@ -198,7 +198,16 @@ class GoogleBooks(Source):
goog = identifiers.get('google', None)
if goog is not None:
return ('google', goog, 'https://books.google.com/books?id=%s' % goog)
# }}}
def id_from_url(self, url): # {{{
url_pattern = '(?:http|https)://books\.google\.com/books\?id=(?P<id_>.+)'
url_pattern = re.compile(url_pattern)
id_ = url_pattern.search(url).group('id_')
if id_:
return ('google', id_)
else:
return None
# }}}
def create_query(self, title=None, authors=None, identifiers={}, capitalize_isbn=False): # {{{

View File

@ -4,12 +4,81 @@
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from collections import defaultdict
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.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):
@ -49,12 +118,18 @@ class MarkBooksAction(InterfaceAction):
self.toggle_ids(book_ids)
def genesis(self):
self.search_icon = QIcon.ic('search.png')
self.qaction.triggered.connect(self.toggle_selected)
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('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')
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')
a.triggered.connect(self.clear_all_marked)
m.addSeparator()
@ -86,9 +161,22 @@ class MarkBooksAction(InterfaceAction):
def about_to_show_menu(self):
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))
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):
enabled = loc == 'library'
@ -116,6 +204,9 @@ class MarkBooksAction(InterfaceAction):
def show_marked(self):
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):
self.gui.current_db.data.set_marked_ids(())
if str(self.gui.search.text()).startswith('marked:'):
@ -139,3 +230,18 @@ class MarkBooksAction(InterfaceAction):
else:
mids.pop(book_id, None)
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,
# anyway)
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.row_decoration = None
self.device_connected = False
@ -1072,7 +1073,12 @@ class BooksModel(QAbstractTableModel): # {{{
return (section+1)
if role == Qt.ItemDataRole.DecorationRole:
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):
pass
return None

View File

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