Improvements to edit authors:

1) remove a linear list search
2) Don't recalculate sort keys on every comparison
3) Use set comprehensions to build the info needed to display
4) Add an Undo option to the context menu if the item has been changed
This commit is contained in:
Charles Haley 2020-06-07 13:56:18 +01:00
parent 3d9983abc0
commit 9f419f033b

View File

@ -5,6 +5,8 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__license__ = 'GPL v3' __license__ = 'GPL v3'
from functools import partial
from PyQt5.Qt import (Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon, from PyQt5.Qt import (Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon,
QDialogButtonBox, QFrame, QLabel, QTimer, QMenu, QApplication, QDialogButtonBox, QFrame, QLabel, QTimer, QMenu, QApplication,
QByteArray, QItemDelegate, QAction) QByteArray, QItemDelegate, QAction)
@ -22,11 +24,22 @@ QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction'
class tableItem(QTableWidgetItem): class tableItem(QTableWidgetItem):
def __init__(self, txt):
QTableWidgetItem.__init__(self, txt)
self.sort_key = sort_key(unicode_type(txt))
def setText(self, txt):
self.sort_key = sort_key(unicode_type(txt))
QTableWidgetItem.setText(self, txt)
def set_sort_key(self):
self.sort_key = sort_key(unicode_type(self.text()))
def __ge__(self, other): def __ge__(self, other):
return sort_key(unicode_type(self.text())) >= sort_key(unicode_type(other.text())) return self.sort_key >= other.sort_key
def __lt__(self, other): def __lt__(self, other):
return sort_key(unicode_type(self.text())) < sort_key(unicode_type(other.text())) return self.sort_key < other.sort_key
class EditColumnDelegate(QItemDelegate): class EditColumnDelegate(QItemDelegate):
@ -146,6 +159,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
'link': v['link']} 'link': v['link']}
self.edited_icon = QIcon(I('modified.png')) self.edited_icon = QIcon(I('modified.png'))
self.empty_icon = QIcon()
if prefs['use_primary_find_in_search']: if prefs['use_primary_find_in_search']:
self.string_contains = primary_contains self.string_contains = primary_contains
else: else:
@ -168,11 +182,13 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.show_table(None, None, None, False) self.show_table(None, None, None, False)
def show_table(self, id_to_select, select_sort, select_link, is_first_letter): def show_table(self, id_to_select, select_sort, select_link, is_first_letter):
auts_to_show = {t[0] for t in
self.find_aut_func(use_virtual_library=self.apply_vl_checkbox.isChecked())}
filter_text = icu_lower(unicode_type(self.filter_box.text())) filter_text = icu_lower(unicode_type(self.filter_box.text()))
auts_to_show = [] if filter_text:
for t in self.find_aut_func(use_virtual_library=self.apply_vl_checkbox.isChecked()): auts_to_show = {id_ for id_ in auts_to_show
if self.string_contains(filter_text, icu_lower(t[1])): if self.string_contains(filter_text, icu_lower(self.authors[id_]['name']))}
auts_to_show.append(t[0])
self.table.blockSignals(True) self.table.blockSignals(True)
self.table.clear() self.table.clear()
self.table.setColumnCount(3) self.table.setColumnCount(3)
@ -183,22 +199,20 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
if id_ not in auts_to_show: if id_ not in auts_to_show:
continue continue
name, sort, link = (v['name'], v['sort'], v['link']) name, sort, link = (v['name'], v['sort'], v['link'])
orig = self.original_authors[id_]
name = name.replace('|', ',') name = name.replace('|', ',')
name_item = tableItem(name) name_item = tableItem(name)
name_item.setData(Qt.UserRole, id_) name_item.setData(Qt.UserRole, id_)
if name != orig['name']:
name_item.setIcon(self.edited_icon)
sort_item = tableItem(sort) sort_item = tableItem(sort)
if sort != orig['sort']:
sort_item.setIcon(self.edited_icon)
link_item = tableItem(link) link_item = tableItem(link)
if link != orig['link']:
link_item.setIcon(self.edited_icon)
self.table.setItem(row, 0, name_item) self.table.setItem(row, 0, name_item)
self.table.setItem(row, 1, sort_item) self.table.setItem(row, 1, sort_item)
self.table.setItem(row, 2, link_item) self.table.setItem(row, 2, link_item)
self.set_icon(name_item, id_)
self.set_icon(sort_item, id_)
self.set_icon(link_item, id_)
row += 1 row += 1
self.table.setItemDelegate(EditColumnDelegate(self.completion_data)) self.table.setItemDelegate(EditColumnDelegate(self.completion_data))
@ -219,9 +233,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
select_item = None select_item = None
use_as = tweaks['categories_use_field_for_author_name'] use_as = tweaks['categories_use_field_for_author_name']
for row in range(0, len(auts_to_show)): for row in range(0, len(auts_to_show)):
name_item = self.table.item(row, 1) if use_as else self.table.item(row, 0)
if is_first_letter: if is_first_letter:
if primary_startswith(name_item.text(), id_to_select): item_txt = unicode_type(self.table.item(row, 1).text() if use_as
else self.table.item(row, 0).text())
if primary_startswith(item_txt, id_to_select):
select_item = self.table.item(row, 1) select_item = self.table.item(row, 1)
break break
elif id_to_select == self.table.item(row, 0).data(Qt.UserRole): elif id_to_select == self.table.item(row, 0).data(Qt.UserRole):
@ -230,7 +245,8 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
elif select_link: elif select_link:
select_item = self.table.item(row, 2) select_item = self.table.item(row, 2)
else: else:
select_item = name_item select_item = (self.table.item(row, 1) if use_as
else self.table.item(row, 0))
break break
if select_item: if select_item:
self.table.setCurrentItem(select_item) self.table.setCurrentItem(select_item)
@ -269,6 +285,9 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setColumnWidth(c, w) self.table.setColumnWidth(c, w)
self.save_state() self.save_state()
def get_column_name(self, column):
return ['name', 'sort', 'link'][column]
def show_context_menu(self, point): def show_context_menu(self, point):
self.context_item = self.table.itemAt(point) self.context_item = self.table.itemAt(point)
case_menu = QMenu(_('Change case')) case_menu = QMenu(_('Change case'))
@ -285,12 +304,19 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
action_capitalize.triggered.connect(self.capitalize) action_capitalize.triggered.connect(self.capitalize)
m = self.au_context_menu = QMenu() m = self.au_context_menu = QMenu()
idx = self.table.indexAt(point)
id_ = int(self.table.item(idx.row(), 0).data(Qt.UserRole))
sub = self.get_column_name(idx.column())
if self.context_item.text() != self.original_authors[id_][sub]:
ca = m.addAction(_('Undo'))
ca.triggered.connect(partial(self.undo_cell,
old_value=self.original_authors[id_][sub]))
m.addSeparator()
ca = m.addAction(_('Copy')) ca = m.addAction(_('Copy'))
ca.triggered.connect(self.copy_to_clipboard) ca.triggered.connect(self.copy_to_clipboard)
ca = m.addAction(_('Paste')) ca = m.addAction(_('Paste'))
ca.triggered.connect(self.paste_from_clipboard) ca.triggered.connect(self.paste_from_clipboard)
m.addSeparator() m.addSeparator()
if self.context_item is not None and self.context_item.column() == 0: if self.context_item is not None and self.context_item.column() == 0:
ca = m.addAction(_('Copy to author sort')) ca = m.addAction(_('Copy to author sort'))
ca.triggered.connect(self.copy_au_to_aus) ca.triggered.connect(self.copy_au_to_aus)
@ -304,6 +330,9 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
m.addMenu(case_menu) m.addMenu(case_menu)
m.exec_(self.table.mapToGlobal(point)) m.exec_(self.table.mapToGlobal(point))
def undo_cell(self, old_value):
self.context_item.setText(old_value)
def search_in_book_list(self): def search_in_book_list(self):
from calibre.gui2.ui import get_gui from calibre.gui2.ui import get_gui
row = self.context_item.row() row = self.context_item.row()
@ -416,10 +445,9 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
item_aus = self.table.item(row, 1) item_aus = self.table.item(row, 1)
# Sometimes trailing commas are left by changing between copy algs # Sometimes trailing commas are left by changing between copy algs
aus = unicode_type(author_to_author_sort(aut)).rstrip(',') aus = unicode_type(author_to_author_sort(aut)).rstrip(',')
if aus != self.original_authors[id_]['sort']:
item_aus.setIcon(self.edited_icon)
item_aus.setText(aus) item_aus.setText(aus)
self.authors[id_]['sort'] = aus self.authors[id_]['sort'] = aus
self.set_icon(item_aus, id_)
self.table.setFocus(Qt.OtherFocusReason) self.table.setFocus(Qt.OtherFocusReason)
self.table.cellChanged.connect(self.cell_changed) self.table.cellChanged.connect(self.cell_changed)
@ -429,18 +457,23 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
aus = unicode_type(self.table.item(row, 1).text()).strip() aus = unicode_type(self.table.item(row, 1).text()).strip()
item_aut = self.table.item(row, 0) item_aut = self.table.item(row, 0)
id_ = int(item_aut.data(Qt.UserRole)) id_ = int(item_aut.data(Qt.UserRole))
if aus != self.original_authors[id_]['name']:
item_aut.setIcon(self.edited_icon)
item_aut.setText(aus) item_aut.setText(aus)
self.authors[id_]['name'] = aus self.authors[id_]['name'] = aus
self.set_icon(item_aut, id_)
self.table.setFocus(Qt.OtherFocusReason) self.table.setFocus(Qt.OtherFocusReason)
self.table.cellChanged.connect(self.cell_changed) self.table.cellChanged.connect(self.cell_changed)
def set_icon(self, item, id_):
col_name = self.get_column_name(item.column())
if unicode_type(item.text()) != self.original_authors[id_][col_name]:
item.setIcon(self.edited_icon)
else:
item.setIcon(self.empty_icon)
def cell_changed(self, row, col): def cell_changed(self, row, col):
id_ = int(self.table.item(row, 0).data(Qt.UserRole)) id_ = int(self.table.item(row, 0).data(Qt.UserRole))
if col == 0: if col == 0:
item = self.table.item(row, 0) item = self.table.item(row, 0)
item.setIcon(self.edited_icon)
aut = unicode_type(item.text()).strip() aut = unicode_type(item.text()).strip()
aut_list = string_to_authors(aut) aut_list = string_to_authors(aut)
if len(aut_list) != 1: if len(aut_list) != 1:
@ -448,18 +481,18 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
_('You cannot change an author to multiple authors.')).exec_() _('You cannot change an author to multiple authors.')).exec_()
aut = ' % '.join(aut_list) aut = ' % '.join(aut_list)
self.table.item(row, 0).setText(aut) self.table.item(row, 0).setText(aut)
item.set_sort_key()
self.authors[id_]['name'] = aut self.authors[id_]['name'] = aut
self.set_icon(item, id_)
c = self.table.item(row, 1) c = self.table.item(row, 1)
txt = author_to_author_sort(aut) txt = author_to_author_sort(aut)
c.setText(txt)
self.authors[id_]['sort'] = txt self.authors[id_]['sort'] = txt
c.setText(txt) # This triggers another cellChanged event
item = c item = c
else: else:
item = self.table.item(row, col) item = self.table.item(row, col)
item.setIcon(self.edited_icon) item.set_sort_key()
if col == 1: self.set_icon(item, id_)
self.authors[id_]['sort'] = unicode_type(item.text()) self.authors[id_][self.get_column_name(col)] = unicode_type(item.text())
else:
self.authors[id_]['link'] = unicode_type(item.text())
self.table.setCurrentItem(item) self.table.setCurrentItem(item)
self.table.scrollToItem(item) self.table.scrollToItem(item)