mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
f75f4db165
BIN
resources/images/marked-text.png
Normal file
BIN
resources/images/marked-text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
@ -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)}
|
||||||
|
@ -198,7 +198,16 @@ class GoogleBooks(Source):
|
|||||||
goog = identifiers.get('google', None)
|
goog = identifiers.get('google', None)
|
||||||
if goog is not None:
|
if goog is not None:
|
||||||
return ('google', goog, 'https://books.google.com/books?id=%s' % goog)
|
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): # {{{
|
def create_query(self, title=None, authors=None, identifiers={}, capitalize_isbn=False): # {{{
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user