Fix sorting by notes column in manage category dialogs

Also remove the notesitemwidget as its performance is unacceptable
This commit is contained in:
Kovid Goyal 2023-10-31 07:48:49 +05:30
parent b9e24ffb07
commit 823ec12f02
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 28 additions and 171 deletions

View File

@ -15,7 +15,6 @@ from qt.core import (
from calibre.ebooks.metadata import author_to_author_sort, string_to_authors
from calibre.gui2 import error_dialog, gprefs
from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog
from calibre.gui2.dialogs.tag_list_editor import NotesItemWidget
from calibre.utils.config import prefs
from calibre.utils.config_base import tweaks
from calibre.utils.icu import (
@ -28,9 +27,9 @@ QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction'
class tableItem(QTableWidgetItem):
def __init__(self, txt):
def __init__(self, txt, skey=None):
QTableWidgetItem.__init__(self, txt)
self.sort_key = sort_key(str(txt))
self.sort_key = sort_key(str(txt)) if skey is None else skey
def setText(self, txt):
self.sort_key = sort_key(str(txt))
@ -177,6 +176,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.author_order = 1
self.author_sort_order = 0
self.link_order = 1
self.notes_order = 1
self.show_table(id_to_select, select_sort, select_link, is_first_letter)
@contextmanager
@ -214,6 +214,8 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
row = 0
from calibre.gui2.ui import get_gui
all_items_that_have_notes = get_gui().current_db.new_api.get_all_items_that_have_notes('authors')
yes, yes_skey = '', sort_key('')
no, no_skey = '', sort_key('')
for id_, v in self.authors.items():
if id_ not in auts_to_show:
continue
@ -228,9 +230,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setItem(row, 0, name_item)
self.table.setItem(row, 1, sort_item)
self.table.setItem(row, 2, link_item)
nw = NotesItemWidget('authors', id_, id_ in all_items_that_have_notes)
self.table.setCellWidget(row, 3, nw)
if id_ in all_items_that_have_notes:
self.table.setItem(row, 3, tableItem(yes, yes_skey))
else:
self.table.setItem(row, 3, tableItem(no, no_skey))
self.set_icon(name_item, id_)
self.set_icon(sort_item, id_)
@ -246,9 +249,12 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
elif self.last_sorted_by == 'author':
self.author_order = 1 - self.author_order
self.do_sort_by_author()
else:
elif self.last_sorted_by == 'link':
self.link_order = 1 - self.link_order
self.do_sort_by_link()
else:
self.notes_order = 1 - self.notes_order
self.do_sort_by_notes()
# Position on the desired item
select_item = None
@ -443,7 +449,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.not_found_label_timer.start(1500)
def do_sort(self, section):
(self.do_sort_by_author, self.do_sort_by_author_sort, self.do_sort_by_link)[section]()
(self.do_sort_by_author, self.do_sort_by_author_sort, self.do_sort_by_link, self.do_sort_by_notes)[section]()
def do_sort_by_author(self):
self.last_sorted_by = 'author'
@ -460,6 +466,11 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.link_order = 1 - self.link_order
self.table.sortByColumn(2, Qt.SortOrder(self.link_order))
def do_sort_by_notes(self):
self.last_sorted_by = 'notes'
self.notes_order = 1 - self.notes_order
self.table.sortByColumn(3, Qt.SortOrder(self.notes_order))
def accepted(self):
self.save_state()
self.result = []

View File

@ -5,16 +5,14 @@
from functools import partial
from qt.core import (
QAbstractItemView, QAction, QApplication, QCheckBox, QColor, QDialog,
QDialogButtonBox, QEvent, QFrame, QHBoxLayout, QIcon, QItemDelegate, QLabel, QMenu,
QSize, Qt, QTableWidgetItem, QTimer, QToolButton, QWidget, pyqtSignal, sip,
QDialogButtonBox, QEvent, QFrame, QIcon, QItemDelegate, QLabel, QMenu,
QSize, Qt, QTableWidgetItem, QTimer, QToolButton, pyqtSignal, sip,
)
from calibre import sanitize_file_name
from calibre.gui2 import error_dialog, gprefs, question_dialog, choose_files, choose_save_file
from calibre.gui2 import error_dialog, gprefs, question_dialog
from calibre.gui2.actions.show_quickview import get_quickview_action_plugin
from calibre.gui2.complete2 import EditWithComplete
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.edit_category_notes import EditNoteDialog
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
from calibre.gui2.dialogs.tag_list_editor_table_widget import TleTableWidget
from calibre.gui2.widgets import EnLineEdit
@ -172,158 +170,6 @@ class MyCheckBox(QCheckBox):
self.event = partial(event, me=self, super_class=super(), context_menu_handler=context_menu_handler)
class NotesItemWidget(QWidget):
'''
This is a self-contained widget for manipulating notes. It can be used in a
table (as a cellWidget) or in a layout. It currently contains a check box
indicating that the item has a note, and buttons to edit/create or delete a
note, or undo a deletion.
'''
edit_icon = QIcon.ic('edit_input.png')
delete_icon = QIcon.ic('trash.png')
undo_delete_icon = QIcon.ic('edit-undo.png')
export_icon = QIcon.ic('forward.png')
import_icon = QIcon.ic('back.png')
@property
def db(self):
from calibre.gui2.ui import get_gui
return get_gui().current_db.new_api
@property
def item_val(self):
if self._item_val is None:
self._item_val = self.db.get_item_name(self.field, self._item_id)
return self._item_val
@property
def item_id(self):
if self._item_id is None:
self._item_id = self.db.get_item_id(self.field, self._item_val)
if self._item_id is None:
raise KeyError(f'The value: {self._item_val} is not found in the field: {self.field}')
return self._item_id
def __init__(self, field, item_id_or_val, has_notes):
'''
:param db: A database instance, either old or new api
:param field: the lookup name of a field
:param item_id_or_val: Either the numeric item_id of an item in the field or
the item's string value
'''
super().__init__()
self.field = field
self._item_val = self._item_id = None
self.has_notes = has_notes
if isinstance(item_id_or_val, str):
self._item_val = item_id_or_val
else:
self._item_id = item_id_or_val
self.can_undo = False
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
l = QHBoxLayout()
l.setContentsMargins(2, 0, 0, 0)
self.setLayout(l)
cb = self.cb = MyCheckBox(self.show_context_menu)
cb.setEnabled(False)
l.addWidget(cb)
self.buttons = {}
for button_data in (('edit', 'Edit or create the note. Changes cannot be undone or cancelled'),
('delete', 'Delete the note'),
('undo_delete', 'Undo the deletion')):
button_name = button_data[0]
tool_tip = button_data[1]
b = self.buttons[button_name] = MyToolButton(self.show_context_menu)
b.setIcon(getattr(self, button_name + '_icon'))
b.setToolTip(tool_tip)
b.clicked.connect(getattr(self, 'do_' + button_name))
b.setContentsMargins(0, 0, 0, 0)
l.addWidget(b)
l.addStretch(3)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.set_checked(refresh=False)
self.customContextMenuRequested.connect(self.show_context_menu)
@classmethod
def get_item_id(cls, db, field: str, value: str):
return db.new_api.get_item_id(field, value)
def show_context_menu(self, point):
m = QMenu()
ac = m.addAction(self.edit_icon, _('Edit note') if self.cb.isChecked() else _('Create note'))
ac.triggered.connect(self.do_edit)
ac = m.addAction(self.delete_icon, _('Delete note'))
ac.setEnabled(self.cb.isChecked())
ac.triggered.connect(self.do_delete)
ac = m.addAction(self.undo_delete_icon, _('Undo delete'))
ac.setEnabled(self.can_undo)
ac.triggered.connect(self.do_undo_delete)
ac = m.addAction(self.export_icon, _('Export note to a file'))
ac.setEnabled(self.cb.isChecked())
ac.triggered.connect(self.do_export)
ac = m.addAction(self.import_icon, _('Import note from a file'))
ac.setEnabled(not self.cb.isChecked())
ac.triggered.connect(self.do_import)
m.exec(self.mapToGlobal(point))
def do_edit(self):
accepted = EditNoteDialog(self.field, self.item_id, self.db).exec()
# Continue to allow an undo if it was allowed before and the dialog was cancelled.
self.can_undo = not accepted and self.can_undo
self.set_checked()
def do_delete(self):
self.db.set_notes_for(self.field, self.item_id, '')
self.can_undo = True
self.set_checked()
def do_undo_delete(self):
if self.can_undo:
self.db.unretire_note_for(self.field, self.item_id)
self.can_undo = False
self.set_checked()
def do_export(self):
dest = choose_save_file(self, 'save-exported-note', _('Export note to a file'),
filters=[(_('HTML files'), ['html'])],
initial_filename=f'{sanitize_file_name(self.item_val)}.html',
all_files=False)
if dest:
html = self.db.export_note(self.field, self.item_id)
with open(dest, 'wb') as f:
f.write(html.encode('utf-8'))
def do_import(self):
src = choose_files(self, 'load-imported-note', _('Import note from a file'),
filters=[(_('HTML files'), ['html'])],
all_files=False, select_only_single_file=True)
if src:
self.db.import_note(self.field, self.item_id, src[0])
self.can_undo = False
self.set_checked()
def set_checked(self, refresh=True):
if refresh:
self.has_notes = bool(self.db.notes_for(self.field, self.item_id))
self.cb.setChecked(self.has_notes)
self.buttons['delete'].setEnabled(self.has_notes)
self.buttons['undo_delete'].setEnabled(self.can_undo)
def is_checked(self):
# returns True if the checkbox is checked, meaning the note contains text
return self.cb.isChecked()
class TagListEditor(QDialog, Ui_TagListEditor):
VALUE_COLUMN = 0
@ -361,9 +207,9 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.get_book_ids = get_book_ids
self.text_before_editing = ''
self.sort_names = ('name', 'count', 'was', 'link')
self.sort_names = ('name', 'count', 'was', 'link', 'notes')
self.last_sorted_by = 'name'
self.name_order = self.count_order = self.was_order = self.link_order = 0
self.name_order = self.count_order = self.was_order = self.link_order = self.notes_order = 0
if prefs['case_sensitive']:
self.string_contains = contains
@ -644,8 +490,9 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.setHorizontalHeaderItem(4, self.link_col)
self.table.setRowCount(len(tags))
from calibre.gui2.ui import get_gui
all_items_that_have_notes = get_gui().current_db.new_api.get_all_items_that_have_notes(self.category)
if self.category is not None:
from calibre.gui2.ui import get_gui
all_items_that_have_notes = get_gui().current_db.new_api.get_all_items_that_have_notes(self.category)
for row,tag in enumerate(tags):
item = NameTableWidgetItem(self.sorter)
is_deleted = self.all_tags[tag]['is_deleted']
@ -697,8 +544,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.setItem(row, self.LINK_COLUMN, item)
if self.category is not None:
nw = NotesItemWidget(self.category, _id, _id in all_items_that_have_notes)
self.table.setCellWidget(row, 4, nw)
self.table.setItem(row, self.NOTES_COLUMN, QTableWidgetItem('' if _id in all_items_that_have_notes else ''))
# re-sort the table
column = self.sort_names.index(self.last_sorted_by)