From a240ef62ec6ffde1e0a3f1202e9fbda5fff88016 Mon Sep 17 00:00:00 2001
From: Charles Haley
Date: Sat, 9 Sep 2023 11:38:00 +0100
Subject: [PATCH] Add editing notes to manage categories, and to the context
menu of the tag browser.
Still to do: adding notes to manage authors.
---
src/calibre/gui2/dialogs/tag_list_editor.py | 118 +++++++++++++++-----
src/calibre/gui2/dialogs/tag_list_editor.ui | 11 ++
src/calibre/gui2/tag_browser/view.py | 11 ++
3 files changed, 112 insertions(+), 28 deletions(-)
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index 49727aa0a0..df5749ff10 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -4,15 +4,16 @@
from functools import partial
from qt.core import (
- QAbstractItemView, QAction, QApplication, QColor, QDialog, QDialogButtonBox, QFrame,
- QIcon, QItemDelegate, QLabel, QMenu, QSize, Qt, QTableWidgetItem, QTimer,
- pyqtSignal, sip,
+ QAbstractItemView, QAction, QApplication, QCheckBox, QColor, QDialog,
+ QDialogButtonBox, QFrame, QHBoxLayout, QIcon, QItemDelegate, QLabel, QMenu,
+ QSize, Qt, QTableWidgetItem, QTimer, QToolButton, QWidget, pyqtSignal, sip,
)
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
@@ -144,8 +145,54 @@ class EditColumnDelegate(QItemDelegate):
QItemDelegate.destroyEditor(self, editor, index)
+class NotesItemWidget(QWidget):
+
+ clicked = pyqtSignal(object, object, object, object)
+ icon = QIcon.ic('edit_input.png')
+
+ def __init__(self, db, field, item_id, row):
+ super().__init__()
+ self.row = row
+ self.field = field
+ self.item_id = item_id
+ self.db = db
+
+ l = QHBoxLayout()
+ l.setContentsMargins(2, 0, 0, 0)
+ self.setLayout(l)
+ cb = self.cb = QCheckBox()
+ cb.setEnabled(False)
+ l.addWidget(cb)
+ tb = self.tb = QToolButton()
+ tb.setIcon(self.icon)
+ tb.clicked.connect(self.tb_clicked)
+ l.addWidget(tb)
+ l.addStretch(3)
+ self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+ self.set_checked()
+
+ def keyPressEvent(self, ev):
+ # Use this instead of focusProxy() because fp() changes selection behavior
+ if ev.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return, Qt.Key.Key_Space):
+ ev.accept()
+ self.tb_clicked()
+ return
+ return super().keyPressEvent(ev)
+
+ def tb_clicked(self):
+ self.clicked.emit(self, self.field, self.item_id, self.db)
+
+ def set_checked(self):
+ t = self.db.notes_for(self.field, self.item_id)
+ self.cb.setChecked(bool(t))
+
+
class TagListEditor(QDialog, Ui_TagListEditor):
+ VALUE_COLUMN = 0
+ LINK_COLUMN = 3
+ NOTES_COLUMN = 4
+
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):
QDialog.__init__(self, window)
@@ -228,7 +275,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def show_context_menu(self, point):
idx = self.table.indexAt(point)
- if idx.column() != 0:
+ if idx.column() != self.VALUE_COLUMN:
return
m = self.au_context_menu = QMenu(self)
@@ -339,8 +386,8 @@ class TagListEditor(QDialog, Ui_TagListEditor):
return
for _ in range(0, self.table.rowCount()):
r = self.search_item_row = (self.search_item_row + 1) % self.table.rowCount()
- if self.string_contains(find_text, self.table.item(r, 0).text()):
- self.table.setCurrentItem(self.table.item(r, 0))
+ if self.string_contains(find_text, self.table.item(r, self.VALUE_COLUMN).text()):
+ self.table.setCurrentItem(self.table.item(r, self.VALUE_COLUMN))
self.table.setFocus(Qt.FocusReason.OtherFocusReason)
return
# Nothing found. Pop up the little dialog for 1.5 seconds
@@ -385,13 +432,13 @@ class TagListEditor(QDialog, Ui_TagListEditor):
vh.setDefaultSectionSize(gprefs.get('general_category_editor_row_height', vh.defaultSectionSize()))
vh.sectionResized.connect(self.row_height_changed)
- self.table.setColumnCount(4)
+ self.table.setColumnCount(5)
self.edit_delegate = EditColumnDelegate(self.table, self.check_for_deleted_items)
self.edit_delegate.editing_finished.connect(self.stop_editing)
self.edit_delegate.editing_started.connect(self.start_editing)
- self.table.setItemDelegateForColumn(0, self.edit_delegate)
- self.table.setItemDelegateForColumn(3, self.edit_delegate)
+ self.table.setItemDelegateForColumn(self.VALUE_COLUMN, self.edit_delegate)
+ self.table.setItemDelegateForColumn(self.LINK_COLUMN, self.edit_delegate)
self.table.delete_pressed.connect(self.delete_pressed)
self.table.itemDoubleClicked.connect(self._rename_tag)
@@ -449,13 +496,15 @@ class TagListEditor(QDialog, Ui_TagListEditor):
select_item = None
self.table.blockSignals(True)
self.name_col = QTableWidgetItem(self.category_name)
- self.table.setHorizontalHeaderItem(0, self.name_col)
+ self.table.setHorizontalHeaderItem(self.VALUE_COLUMN, self.name_col)
self.count_col = QTableWidgetItem(_('Count'))
self.table.setHorizontalHeaderItem(1, self.count_col)
self.was_col = QTableWidgetItem(_('Was'))
self.table.setHorizontalHeaderItem(2, self.was_col)
self.link_col = QTableWidgetItem(_('Link'))
- self.table.setHorizontalHeaderItem(3, self.link_col)
+ self.table.setHorizontalHeaderItem(self.LINK_COLUMN, self.link_col)
+ self.link_col = QTableWidgetItem(_('Notes'))
+ self.table.setHorizontalHeaderItem(4, self.link_col)
self.table.setRowCount(len(tags))
for row,tag in enumerate(tags):
@@ -476,7 +525,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
_("This is not one of this column's permitted values ({0})"
).format(', '.join(self.enum_permitted_values)) + '
')
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEditable)
- self.table.setItem(row, 0, item)
+ self.table.setItem(row, self.VALUE_COLUMN, item)
if select_item is None:
if ttm_is_first_letter:
if primary_startswith(tag, tag_to_match):
@@ -506,7 +555,13 @@ class TagListEditor(QDialog, Ui_TagListEditor):
item.setFlags(item.flags() | (Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable))
item.setIcon(QIcon())
item.setText(self.link_map.get(tag, ''))
- self.table.setItem(row, 3, item)
+ self.table.setItem(row, self.LINK_COLUMN, item)
+
+ if self.category is not None:
+ from calibre.gui2.ui import get_gui
+ nw = NotesItemWidget(get_gui().current_db.new_api, self.category, _id, row)
+ nw.clicked.connect(self.notes_button_clicked)
+ self.table.setCellWidget(row, 4, nw)
# re-sort the table
column = self.sort_names.index(self.last_sorted_by)
@@ -523,6 +578,10 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.start_find_pos = -1
self.table.blockSignals(False)
+ def notes_button_clicked(self, w, field, item_id, db):
+ EditNoteDialog(field, item_id, db).exec()
+ w.set_checked()
+
def not_found_label_timer_event(self):
self.not_found_label.setVisible(False)
@@ -565,7 +624,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
current_column = self.table.currentItem().column()
# We don't support editing multiple link rows at the same time. Use
# the current cell.
- if current_column != 0:
+ if current_column != self.VALUE_COLUMN:
self.table.setCurrentItem(self.table.item(on_row, current_column))
items = self.table.selectedItems()
self.table.blockSignals(True)
@@ -589,7 +648,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.blockSignals(False)
def finish_editing(self, edited_item):
- if edited_item.column() != 0:
+ if edited_item.column() != self.VALUE_COLUMN:
# Nothing to do for link fields
return
if not edited_item.text():
@@ -621,7 +680,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.blockSignals(False)
def undo_edit(self):
- col_zero_items = (self.table.item(item.row(), 0) for item in self.table.selectedItems())
+ col_zero_items = (self.table.item(item.row(), self.VALUE_COLUMN) for item in self.table.selectedItems())
if not col_zero_items:
error_dialog(self, _('No item selected'),
_('You must select one item from the list of available items.')).exec()
@@ -639,7 +698,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.to_rename.pop(int(col_zero_item.data(Qt.ItemDataRole.UserRole)), None)
row = col_zero_item.row()
self.table.item(row, 2).setData(Qt.ItemDataRole.DisplayRole, '')
- item = self.table.item(row, 3)
+ item = self.table.item(row, self.LINK_COLUMN)
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsSelectable)
item.setIcon(QIcon())
self.table.blockSignals(False)
@@ -648,12 +707,15 @@ class TagListEditor(QDialog, Ui_TagListEditor):
if self.table.currentIndex().isValid():
col = self.table.currentIndex().column()
self.table.blockSignals(True)
- for itm in (item for item in self.table.selectedItems() if item.column() != col):
- itm.setSelected(False)
+ if col == self.NOTES_COLUMN:
+ self.table.setCurrentIndex(self.table.currentIndex())
+ else:
+ for itm in (item for item in self.table.selectedItems() if item.column() != col):
+ itm.setSelected(False)
self.table.blockSignals(False)
def check_for_deleted_items(self, show_error=False):
- for col_zero_item in (self.table.item(item.row(), 0) for item in self.table.selectedItems()):
+ for col_zero_item in (self.table.item(item.row(), self.VALUE_COLUMN) for item in self.table.selectedItems()):
if col_zero_item.is_deleted:
if show_error:
error_dialog(self, _('Selection contains deleted items'),
@@ -664,9 +726,9 @@ class TagListEditor(QDialog, Ui_TagListEditor):
return False
def rename_tag(self):
- if self.table.currentColumn() != 0:
+ if self.table.currentColumn() != self.VALUE_COLUMN:
return
- item = self.table.item(self.table.currentRow(), 0)
+ item = self.table.item(self.table.currentRow(), self.VALUE_COLUMN)
self._rename_tag(item)
def _rename_tag(self, item):
@@ -680,7 +742,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
'to do this?')+'
'):
return
self.table.blockSignals(True)
- for col_zero_item in (self.table.item(item.row(), 0) for item in self.table.selectedItems()):
+ for col_zero_item in (self.table.item(item.row(), self.VALUE_COLUMN) for item in self.table.selectedItems()):
# undelete any deleted items
if col_zero_item.is_deleted:
col_zero_item.set_is_deleted(False)
@@ -691,7 +753,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.editItem(item)
def delete_pressed(self):
- if self.table.currentColumn() == 0:
+ if self.table.currentColumn() == self.VALUE_COLUMN:
self.delete_tags()
return
if not confirm(
@@ -704,7 +766,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def delete_tags(self):
# This check works because we ensure that the selection is in only one column
- if self.table.currentItem().column() != 0:
+ if self.table.currentItem().column() != self.VALUE_COLUMN:
return
# We know the selected items are in column zero
deletes = self.table.selectedItems()
@@ -733,14 +795,14 @@ class TagListEditor(QDialog, Ui_TagListEditor):
row = item.row()
orig = self.table.item(row, 2)
orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text())
- link = self.table.item(row, 3)
+ link = self.table.item(row, self.LINK_COLUMN)
link.setFlags(link.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable))
link.setIcon(QIcon.ic('trash.png'))
self.table.blockSignals(False)
if row >= self.table.rowCount():
row = self.table.rowCount() - 1
if row >= 0:
- self.table.scrollToItem(self.table.item(row, 0))
+ self.table.scrollToItem(self.table.item(row, self.VALUE_COLUMN))
def record_sort(self, section):
# Note what sort was done so we can redo it when the table is rebuilt
@@ -753,7 +815,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
# We don't bother with cleaning out the deleted links because the db
# interface ignores links for values that don't exist. The caller must
# process deletes and renames first so the names are correct.
- self.links = {self.table.item(r, 0).text():self.table.item(r, 3).text()
+ self.links = {self.table.item(r, self.VALUE_COLUMN).text():self.table.item(r, self.LINK_COLUMN).text()
for r in range(self.table.rowCount())}
self.save_geometry()
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.ui b/src/calibre/gui2/dialogs/tag_list_editor.ui
index 789940df40..9a309030be 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.ui
+++ b/src/calibre/gui2/dialogs/tag_list_editor.ui
@@ -127,6 +127,17 @@
+ -
+
+
+ Notes changes are immediate
+
+
+ Creating or changing a note has immediate effect. They are not
+reverted by undo or canceling this dialog
+
+
+
-
-
diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py
index 967f2583ad..d413a48703 100644
--- a/src/calibre/gui2/tag_browser/view.py
+++ b/src/calibre/gui2/tag_browser/view.py
@@ -24,6 +24,7 @@ from calibre.gui2 import (
FunctionDispatcher, choose_files, config, empty_index, gprefs, pixmap_to_data,
question_dialog, rating_font,
)
+from calibre.gui2.dialogs.edit_category_notes import EditNoteDialog
from calibre.gui2.complete2 import EditWithComplete
from calibre.gui2.tag_browser.model import (
COUNT_ROLE, DRAG_IMAGE_ROLE, TAG_SEARCH_STATES, TagsModel, TagTreeItem,
@@ -522,6 +523,9 @@ class TagsView(QTreeView): # {{{
return
from calibre.gui2.ui import get_gui
try:
+ if action == 'edit_note':
+ EditNoteDialog(category, extra, self.db).exec()
+ return
if action == 'dont_collapse_category':
if key not in extra:
extra.append(key)
@@ -844,6 +848,13 @@ class TagsView(QTreeView): # {{{
_('Remove %s from selected books') % display_name(tag),
partial(self.context_menu_handler, action='remove_tag', index=index))
+ item_id = self.db.new_api.get_item_id(tag.category, tag.original_name)
+ has_note = bool(self.db.new_api.notes_for(tag.category, item_id))
+ self.context_menu.addAction(self.edit_metadata_icon,
+ (_('Edit note for %s') if has_note else _('Create note for %s'))%display_name(tag),
+ partial(self.context_menu_handler, action='edit_note',
+ index=index, extra=item_id, category=tag.category))
+
elif key == 'search' and tag.is_searchable:
self.context_menu.addAction(self.rename_icon,
_('Rename %s')%display_name(tag),