From 36f65e918725f9a9d9124ef193d20e71a5349a78 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 10 Sep 2023 12:38:57 +0100 Subject: [PATCH] Some refactoring and improvements: 1) Add a context menu to the notes widget. Remove the keyboard handler as it isn't needed. 2) Allow passing an item string value instead of an item_id to __init__ 3) Make the notes widget self contained, making the signal advisory instead of mandatory 4) Add a delete note button to the widget. --- .../gui2/dialogs/edit_authors_dialog.py | 8 +- src/calibre/gui2/dialogs/tag_list_editor.py | 107 +++++++++++++----- 2 files changed, 81 insertions(+), 34 deletions(-) diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index 7eb8ed94da..9041fa3aed 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -230,9 +230,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.table.setItem(row, 1, sort_item) self.table.setItem(row, 2, link_item) - item_id = db.get_item_id('authors', name) - nw = NotesItemWidget(get_gui().current_db.new_api, 'authors', item_id, row) - nw.clicked.connect(self.notes_button_clicked) + nw = NotesItemWidget(get_gui().current_db, 'authors', name) self.table.setCellWidget(row, 3, nw) self.set_icon(name_item, id_) @@ -285,10 +283,6 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): 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 row_height_changed(self, row, old, new): self.table.verticalHeader().blockSignals(True) self.table.verticalHeader().setDefaultSectionSize(new) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index df5749ff10..0ff614447d 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -146,16 +146,40 @@ class EditColumnDelegate(QItemDelegate): 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. + ''' - clicked = pyqtSignal(object, object, object, object) - icon = QIcon.ic('edit_input.png') + ''' + This signal is emitted when a note is edited, after the notes editor + returns, or deleted. It is provided in case the using class wants to know if + a note has possibly changed. If not then using this signal isn't required. + Parameters: self (this widget), field, item_id, note, db (new_api) + ''' + note_edited = pyqtSignal(object, object, object, object, object) - def __init__(self, db, field, item_id, row): + edit_icon = QIcon.ic('edit_input.png') + delete_icon = QIcon.ic('trash.png') + + def __init__(self, db, field, item_id): + ''' + :param db: A database instance, either old or new api + :param field: the lookup name of a field + :param item_id: Either the numeric item_id of an item in the field or + the item's string value + ''' super().__init__() - self.row = row + self.db = db = db.new_api self.field = field - self.item_id = item_id - self.db = db + if isinstance(item_id, str): + self.item_id = db.get_item_id(field, item_id) + if self.item_id is None: + raise ValueError(f"The item {item_id} doesn't exist") + else: + self.item_id = item_id l = QHBoxLayout() l.setContentsMargins(2, 0, 0, 0) @@ -163,28 +187,62 @@ class NotesItemWidget(QWidget): 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) + + editb = self.edit_button = QToolButton() + editb.setIcon(self.edit_icon) + editb.clicked.connect(self.edit_note) + editb.setContentsMargins(0, 0, 0, 0) + l.addWidget(editb) + + delb = self.delete_button = QToolButton() + delb.setIcon(self.delete_icon) + delb.clicked.connect(self.delete_note) + delb.setContentsMargins(0, 0, 0, 0) + l.addWidget(delb) + l.addStretch(3) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.set_checked() + self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self.customContextMenuRequested.connect(self.show_context_menu) - 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() + @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.edit_button_clicked) + + ac = m.addAction(self.delete_icon, _('Delete note')) + ac.setEnabled(self.cb.isChecked()) + ac.triggered.connect(self.delete_button_clicked) + + m.exec(self.mapToGlobal(point)) + + def edit_note(self): + EditNoteDialog(self.field, self.item_id, self.db).exec() + self.set_checked() + + def delete_note(self): + if not question_dialog(self, _('Delete note?'), + '

'+_('The note will be immediately deleted. There is no undo. ' + 'Do you want to do this?')+'
'): return - return super().keyPressEvent(ev) - - def tb_clicked(self): - self.clicked.emit(self, self.field, self.item_id, self.db) + self.db.set_notes_for(self.field, self.item_id, '') + self.set_checked() def set_checked(self): - t = self.db.notes_for(self.field, self.item_id) - self.cb.setChecked(bool(t)) + notes = self.db.notes_for(self.field, self.item_id) + t = bool(notes) + self.cb.setChecked(t) + self.delete_button.setEnabled(t) + self.note_edited.emit(self, self.field, self.item_id, notes, self.db) + + 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): @@ -559,8 +617,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): 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) + nw = NotesItemWidget(get_gui().current_db, self.category, _id) self.table.setCellWidget(row, 4, nw) # re-sort the table @@ -578,10 +635,6 @@ 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)