Several changes:

1. Improve performance by caching all icons.
2. Use different icons instead of a check mark for notes. Sorting uses the icon.
3. In manage tags, make the edit button on the left work on all editable cells. Correct the tooltip.
4. In manage tags, put the button press shortcut descriptions into the tooltips.
5. In manage authors, add an "Edit cell" button that works with all editable cells.
This commit is contained in:
Charles Haley 2023-11-06 12:56:19 +00:00 committed by Kovid Goyal
parent 07ceef1927
commit 27bea0a999
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 109 additions and 50 deletions

View File

@ -15,7 +15,7 @@ from qt.core import (
from calibre.ebooks.metadata import author_to_author_sort, string_to_authors from calibre.ebooks.metadata import author_to_author_sort, string_to_authors
from calibre.gui2 import error_dialog, gprefs from calibre.gui2 import error_dialog, gprefs
from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog
from calibre.gui2.dialogs.tag_list_editor import CHECK_MARK, NotesUtilities from calibre.gui2.dialogs.tag_list_editor import NotesUtilities, NotesTableWidgetItem
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.utils.config_base import tweaks from calibre.utils.config_base import tweaks
from calibre.utils.icu import ( from calibre.utils.icu import (
@ -116,6 +116,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.apply_vl_checkbox.toggled.connect(self.use_vl_changed) self.apply_vl_checkbox.toggled.connect(self.use_vl_changed)
self.apply_selection_checkbox.setContentsMargins(0, 0, 0, 0) self.apply_selection_checkbox.setContentsMargins(0, 0, 0, 0)
self.apply_selection_checkbox.toggled.connect(self.apply_selection_box_changed) self.apply_selection_checkbox.toggled.connect(self.apply_selection_box_changed)
self.edit_current_cell.clicked.connect(self.edit_cell)
self.table.setAlternatingRowColors(True) self.table.setAlternatingRowColors(True)
self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
@ -205,6 +206,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.notes_utilities, self.get_item_id)) self.notes_utilities, self.get_item_id))
self.show_table(id_to_select, select_sort, select_link, is_first_letter) self.show_table(id_to_select, select_sort, select_link, is_first_letter)
def edit_cell(self):
if self.table.currentIndex().isValid():
self.table.editItem(self.table.currentItem())
def get_item_id(self, item): def get_item_id(self, item):
return int(self.table.item(item.row(), AUTHOR_COLUMN).data(Qt.ItemDataRole.UserRole)) return int(self.table.item(item.row(), AUTHOR_COLUMN).data(Qt.ItemDataRole.UserRole))
@ -252,8 +257,6 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
row = 0 row = 0
from calibre.gui2.ui import get_gui 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') all_items_that_have_notes = get_gui().current_db.new_api.get_all_items_that_have_notes('authors')
yes, yes_skey = CHECK_MARK, sort_key(CHECK_MARK)
no, no_skey = '', sort_key('')
for id_, v in self.authors.items(): for id_, v in self.authors.items():
if id_ not in auts_to_show: if id_ not in auts_to_show:
continue continue
@ -268,16 +271,13 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setItem(row, AUTHOR_COLUMN, name_item) self.table.setItem(row, AUTHOR_COLUMN, name_item)
self.table.setItem(row, AUTHOR_SORT_COLUMN, sort_item) self.table.setItem(row, AUTHOR_SORT_COLUMN, sort_item)
self.table.setItem(row, LINK_COLUMN, link_item) self.table.setItem(row, LINK_COLUMN, link_item)
if id_ in all_items_that_have_notes: note_item = NotesTableWidgetItem()
note_item = TableItem(yes, yes_skey)
else:
note_item = TableItem(no, no_skey)
self.table.setItem(row, NOTES_COLUMN, note_item) self.table.setItem(row, NOTES_COLUMN, note_item)
self.set_icon(name_item, id_) self.set_icon(name_item, id_)
self.set_icon(sort_item, id_) self.set_icon(sort_item, id_)
self.set_icon(link_item, id_) self.set_icon(link_item, id_)
self.set_icon(note_item, id_) self.notes_utilities.set_icon(note_item, id_, id_ in all_items_that_have_notes)
row += 1 row += 1
self.table.setHorizontalHeaderLabels([_('Author'), _('Author sort'), _('Link'), _('Notes')]) self.table.setHorizontalHeaderLabels([_('Author'), _('Author sort'), _('Link'), _('Notes')])
@ -569,6 +569,8 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setFocus(Qt.FocusReason.OtherFocusReason) self.table.setFocus(Qt.FocusReason.OtherFocusReason)
def set_icon(self, item, id_): def set_icon(self, item, id_):
if item.column() == NOTES_COLUMN:
raise ValueError('got set_icon on notes column')
modified = self.item_is_modified(item, id_) modified = self.item_is_modified(item, id_)
item.setIcon(self.edited_icon if modified else QIcon()) item.setIcon(self.edited_icon if modified else QIcon())
@ -596,10 +598,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
item = c item = c
else: else:
item = self.table.item(row, col) item = self.table.item(row, col)
item.set_sort_key()
self.set_icon(item, id_)
name = self.get_column_name(col) name = self.get_column_name(col)
if name != 'notes': if name != 'notes':
item.set_sort_key()
self.set_icon(item, id_)
self.authors[id_][self.get_column_name(col)] = str(item.text()) self.authors[id_][self.get_column_name(col)] = str(item.text())
self.table.setCurrentItem(item) self.table.setCurrentItem(item)
self.table.scrollToItem(item) self.table.scrollToItem(item)

View File

@ -182,7 +182,19 @@ after changing Preferences->Advanced->Tweaks->Author sort name algorith
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2"> <item row="1" column="3">
<widget class="QPushButton" name="edit_current_cell">
<property name="toolTip">
<string>&lt;p&gt;Edit the currently selected cell. If an author is edited then it is renamed
in every book where it is used. Double-clicking and pressing the edit
key also work.&lt;/p&gt;</string>
</property>
<property name="text">
<string>Edi&amp;t current cell</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed">

View File

@ -33,6 +33,9 @@ CHECK_MARK = '✓'
class NameTableWidgetItem(QTableWidgetItem): class NameTableWidgetItem(QTableWidgetItem):
empty_icon = QIcon()
trash_icon = QIcon.ic('trash.png')
def __init__(self, sort_key): def __init__(self, sort_key):
QTableWidgetItem.__init__(self) QTableWidgetItem.__init__(self)
self.initial_value = '' self.initial_value = ''
@ -53,9 +56,9 @@ class NameTableWidgetItem(QTableWidgetItem):
def set_is_deleted(self, to_what): def set_is_deleted(self, to_what):
if to_what: if to_what:
self.setIcon(QIcon.ic('trash.png')) self.setIcon(self.trash_icon)
else: else:
self.setIcon(QIcon()) self.setIcon(self.empty_icon)
self.current_value = self.initial_value self.current_value = self.initial_value
self.is_deleted = to_what self.is_deleted = to_what
@ -116,8 +119,39 @@ class CountTableWidgetItem(QTableWidgetItem):
return self._count < other._count return self._count < other._count
class NotesTableWidgetItem(QTableWidgetItem):
# These define the sort order for notes columns
EMPTY = 0
UNCHANGED = 1
EDITED = 2
DELETED = 3
def __init__(self):
QTableWidgetItem.__init__(self, '')
self.set_sort_val(self.EMPTY)
def set_sort_val(self, val):
self._sort_val = val
def __ge__(self, other):
return self._sort_val >= other._sort_val
def __lt__(self, other):
return self._sort_val < other._sort_val
class NotesUtilities(): class NotesUtilities():
edit_icon = QIcon.ic('edit_input.png')
edited_icon = QIcon.ic('modified.png')
empty_icon = QIcon()
export_icon = QIcon.ic('forward.png')
import_icon = QIcon.ic('back.png')
pencil_icon = QIcon.ic('notes.png')
trash_icon = QIcon.ic('trash.png')
undo_delete_icon = QIcon.ic('edit-undo.png')
def __init__(self, table, category, item_id_getter): def __init__(self, table, category, item_id_getter):
self.table = table self.table = table
self.modified_notes = {} self.modified_notes = {}
@ -141,9 +175,25 @@ class NotesUtilities():
db.set_notes_for(self.category, item_id, '') db.set_notes_for(self.category, item_id, '')
self.modified_notes.clear() self.modified_notes.clear()
def change_text(self, item, val): def set_icon(self, item, id_, has_value):
with block_signals(self.table): with block_signals(self.table):
item.setText(CHECK_MARK if bool(val) else '') if id_ not in self.modified_notes:
if not has_value:
item.setIcon(self.empty_icon)
item.set_sort_val(NotesTableWidgetItem.EMPTY)
else:
item.setIcon(self.pencil_icon)
item.set_sort_val(NotesTableWidgetItem.UNCHANGED)
else:
if has_value:
item.setIcon(self.edited_icon)
item.set_sort_val(NotesTableWidgetItem.EDITED)
elif not bool(self.modified_notes[id_]):
item.setIcon(self.empty_icon)
item.set_sort_val(NotesTableWidgetItem.EMPTY)
else:
item.setIcon(self.trash_icon)
item.set_sort_val(NotesTableWidgetItem.DELETED)
self.table.cellChanged.emit(item.row(), item.column()) self.table.cellChanged.emit(item.row(), item.column())
self.table.itemChanged.emit(item) self.table.itemChanged.emit(item)
@ -158,7 +208,7 @@ class NotesUtilities():
after = db.notes_for(self.category, item_id) after = db.notes_for(self.category, item_id)
if item_id not in self.modified_notes: if item_id not in self.modified_notes:
self.modified_notes[item_id] = note self.modified_notes[item_id] = note
self.change_text(item, after) self.set_icon(item, item_id, bool(after))
def undo_note_edit(self, item): def undo_note_edit(self, item):
item_id = self.item_id_getter(item) item_id = self.item_id_getter(item)
@ -169,7 +219,7 @@ class NotesUtilities():
db.import_note(self.category, item_id, before.encode('utf-8'), path_is_data=True) db.import_note(self.category, item_id, before.encode('utf-8'), path_is_data=True)
else: else:
db.set_notes_for(self.category, item_id, '') db.set_notes_for(self.category, item_id, '')
self.change_text(item, before) self.set_icon(item, item_id, bool(before))
def delete_note(self, item): def delete_note(self, item):
item_id = self.item_id_getter(item) item_id = self.item_id_getter(item)
@ -177,7 +227,7 @@ class NotesUtilities():
if item_id not in self.modified_notes: if item_id not in self.modified_notes:
self.modified_notes[item_id] = db.notes_for(self.category, item_id) self.modified_notes[item_id] = db.notes_for(self.category, item_id)
db.set_notes_for(self.category, item_id, '') db.set_notes_for(self.category, item_id, '')
self.change_text(item, False) self.set_icon(item, item_id, False)
def do_export(self, item, item_name): def do_export(self, item, item_name):
item_id = self.item_id_getter(item) item_id = self.item_id_getter(item)
@ -202,13 +252,7 @@ class NotesUtilities():
self.modified_notes[item_id] = before self.modified_notes[item_id] = before
db.import_note(self.category, item_id, src[0]) db.import_note(self.category, item_id, src[0])
after = db.notes_for(self.category, item_id) after = db.notes_for(self.category, item_id)
self.change_text(item, after) self.set_icon(item, item_id, bool(after))
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')
def context_menu(self, menu, item, item_name): def context_menu(self, menu, item, item_name):
m = menu m = menu
@ -224,7 +268,7 @@ class NotesUtilities():
ac = m.addAction(self.edit_icon, _('Edit note') if has_note else _('Create note')) ac = m.addAction(self.edit_icon, _('Edit note') if has_note else _('Create note'))
ac.triggered.connect(partial(self.table.editItem, item)) ac.triggered.connect(partial(self.table.editItem, item))
ac = m.addAction(self.delete_icon, _('Delete note')) ac = m.addAction(self.trash_icon, _('Delete note'))
ac.setEnabled(has_note) ac.setEnabled(has_note)
ac.triggered.connect(partial(self.delete_note, item)) ac.triggered.connect(partial(self.delete_note, item))
@ -297,6 +341,7 @@ def block_signals(widget):
class TagListEditor(QDialog, Ui_TagListEditor): class TagListEditor(QDialog, Ui_TagListEditor):
edited_icon = QIcon.ic('modified.png') edited_icon = QIcon.ic('modified.png')
empty_icon = QIcon()
def __init__(self, window, cat_name, tag_to_match, get_book_ids, sorter, def __init__(self, window, cat_name, tag_to_match, get_book_ids, sorter,
ttm_is_first_letter=False, category=None, fm=None, link_map=None): ttm_is_first_letter=False, category=None, fm=None, link_map=None):
@ -348,7 +393,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.string_contains = self.case_insensitive_compare self.string_contains = self.case_insensitive_compare
self.delete_button.clicked.connect(self.delete_tags) self.delete_button.clicked.connect(self.delete_tags)
self.rename_button.clicked.connect(self.rename_tag) self.rename_button.clicked.connect(self.edit_button_clicked)
self.undo_button.clicked.connect(self.undo_edit) self.undo_button.clicked.connect(self.undo_edit)
self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK'))
@ -449,7 +494,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
ca = m.addAction(_('Edit')) ca = m.addAction(_('Edit'))
ca.setIcon(QIcon.ic('edit_input.png')) ca.setIcon(QIcon.ic('edit_input.png'))
ca.triggered.connect(self.rename_tag) ca.triggered.connect(self.edit_button_clicked)
ca.setEnabled(not item.is_deleted) ca.setEnabled(not item.is_deleted)
ca = m.addAction(_('Delete')) ca = m.addAction(_('Delete'))
@ -622,7 +667,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.setItemDelegateForColumn(NOTES_COLUMN, self.edit_delegate) self.table.setItemDelegateForColumn(NOTES_COLUMN, self.edit_delegate)
self.table.delete_pressed.connect(self.delete_pressed) self.table.delete_pressed.connect(self.delete_pressed)
self.table.itemDoubleClicked.connect(self._rename_tag) self.table.itemDoubleClicked.connect(self.edit_item)
self.table.itemChanged.connect(self.finish_editing) self.table.itemChanged.connect(self.finish_editing)
self.table.itemSelectionChanged.connect(self.selection_changed) self.table.itemSelectionChanged.connect(self.selection_changed)
@ -665,7 +710,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
if self.link_is_edited(id_): if self.link_is_edited(id_):
item.setIcon(self.edited_icon) item.setIcon(self.edited_icon)
else: else:
item.setIcon(QIcon()) item.setIcon(self.empty_icon)
def fill_in_table(self, tags, tag_to_match, ttm_is_first_letter): def fill_in_table(self, tags, tag_to_match, ttm_is_first_letter):
self.create_table() self.create_table()
@ -757,13 +802,12 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.setItem(row, LINK_COLUMN, item) self.table.setItem(row, LINK_COLUMN, item)
if self.supports_notes: if self.supports_notes:
item = QTableWidgetItem() item = NotesTableWidgetItem()
self.notes_utilities.change_text(item, id_ in all_items_that_have_notes) self.notes_utilities.set_icon(item, id_, id_ in all_items_that_have_notes)
if is_deleted: if is_deleted:
item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable))
else: else:
item.setFlags(item.flags() | (Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) item.setFlags(item.flags() | (Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable))
item.setIcon(self.edited_icon if id_ in self.notes_utilities.modified_notes else QIcon())
self.table.setItem(row, NOTES_COLUMN, item) self.table.setItem(row, NOTES_COLUMN, item)
# re-sort the table # re-sort the table
@ -851,9 +895,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
return return
if edited_item.column() == NOTES_COLUMN: if edited_item.column() == NOTES_COLUMN:
id_ = self.get_item_id(edited_item) # Done elsewhere
with block_signals(self.table):
edited_item.setIcon(self.edited_icon if id_ in self.notes_utilities.modified_notes else QIcon())
return return
# Item value column # Item value column
@ -884,7 +926,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
item.setIcon(self.edited_icon) item.setIcon(self.edited_icon)
orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text())
else: else:
item.setIcon(QIcon()) item.setIcon(self.empty_icon)
orig.setData(Qt.ItemDataRole.DisplayRole, '') orig.setData(Qt.ItemDataRole.DisplayRole, '')
def undo_link_edit(self, item, item_id): def undo_link_edit(self, item, item_id):
@ -896,7 +938,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
item = self.table.item(item.row(), LINK_COLUMN) item = self.table.item(item.row(), LINK_COLUMN)
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsSelectable) item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsSelectable)
item.setText(link_txt) item.setText(link_txt)
item.setIcon(QIcon()) item.setIcon(self.empty_icon)
def undo_value_edit(self, item, item_id): def undo_value_edit(self, item, item_id):
with block_signals(self.table): with block_signals(self.table):
@ -904,7 +946,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.to_rename.pop(item_id, None) self.to_rename.pop(item_id, None)
row = item.row() row = item.row()
self.table.item(row, WAS_COLUMN).setData(Qt.ItemDataRole.DisplayRole, '') self.table.item(row, WAS_COLUMN).setData(Qt.ItemDataRole.DisplayRole, '')
item.setIcon(self.edited_icon if item.text_is_modified() else QIcon()) item.setIcon(self.edited_icon if item.text_is_modified() else self.empty_icon)
def undo_edit(self): def undo_edit(self):
col_zero_items = (self.table.item(item.row(), VALUE_COLUMN) for item in self.table.selectedItems()) col_zero_items = (self.table.item(item.row(), VALUE_COLUMN) for item in self.table.selectedItems())
@ -934,7 +976,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsSelectable) item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsSelectable)
if id_ in self.notes_utilities.modified_notes: if id_ in self.notes_utilities.modified_notes:
self.notes_utilities.undo_note_edit(item) self.notes_utilities.undo_note_edit(item)
item.setIcon(QIcon()) item.setIcon(self.empty_icon)
def selection_changed(self): def selection_changed(self):
if self.table.currentIndex().isValid(): if self.table.currentIndex().isValid():
@ -957,13 +999,10 @@ class TagListEditor(QDialog, Ui_TagListEditor):
return True return True
return False return False
def rename_tag(self): def edit_button_clicked(self):
if self.table.currentColumn() != VALUE_COLUMN: self.edit_item(self.table.currentItem())
return
item = self.table.item(self.table.currentRow(), VALUE_COLUMN)
self._rename_tag(item)
def _rename_tag(self, item): def edit_item(self, item):
if item is None: if item is None:
error_dialog(self, _('No item selected'), error_dialog(self, _('No item selected'),
_('You must select one item from the list of available items.')).exec() _('You must select one item from the list of available items.')).exec()

View File

@ -172,7 +172,9 @@
<item> <item>
<widget class="QToolButton" name="delete_button"> <widget class="QToolButton" name="delete_button">
<property name="toolTip"> <property name="toolTip">
<string>Delete selected items from the database. This will unapply the items from all books and then remove them from the database.</string> <string>&lt;p&gt;Delete selected items from the database.
This will unapply the items from all books and then remove them
from the database. This button's shortcut is Ctrl+D&lt;/p&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
@ -208,7 +210,9 @@
<item> <item>
<widget class="QToolButton" name="rename_button"> <widget class="QToolButton" name="rename_button">
<property name="toolTip"> <property name="toolTip">
<string>Rename the items in every book where they are used</string> <string>&lt;p&gt;Edit the currently selected cell. If a tag is edited then it is renamed
in every book where it is used. Double-clicking and pressing the edit
key also work. This button's shortcut is Ctrl+E&lt;/p&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>
@ -224,7 +228,7 @@
</size> </size>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string>Ctrl+R</string> <string>Ctrl+E</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -244,7 +248,9 @@
<item> <item>
<widget class="QToolButton" name="undo_button"> <widget class="QToolButton" name="undo_button">
<property name="toolTip"> <property name="toolTip">
<string>Undo any deletes or edits on the selected lines</string> <string>&lt;p&gt;Undo all deletes or edits on the selected lines.
This button's shortcut is Ctrl+U&lt;/p&gt;
</string>
</property> </property>
<property name="text"> <property name="text">
<string>...</string> <string>...</string>