diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 243f30331e..013dbebfc7 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -137,6 +137,27 @@ class Base: def connect_data_changed(self, slot): pass + def values_changed(self): + return self.getter() != self.initial_val and (self.getter() or self.initial_val) + + def edit(self): + if self.values_changed(): + d = _save_dialog(self.parent, _('Values changed'), + _('You have changed the values. In order to use this ' + 'editor, you must either discard or apply these ' + 'changes. Apply changes?')) + if d == QMessageBox.StandardButton.Cancel: + return + if d == QMessageBox.StandardButton.Yes: + self.commit(self.book_id) + self.db.commit() + self.initial_val = self.current_val + else: + self.setter(self.initial_val) + from calibre.gui2.ui import get_gui + get_gui().do_tags_list_edit(None, self.key) + self.initialize(self.book_id) + class SimpleText(Base): @@ -448,16 +469,22 @@ class Comments(Base): class MultipleWidget(QWidget): - def __init__(self, parent): + def __init__(self, parent, only_manage_items=False, widget=EditWithComplete, name=None): QWidget.__init__(self, parent) layout = QHBoxLayout() layout.setSpacing(5) layout.setContentsMargins(0, 0, 0, 0) - self.tags_box = EditWithComplete(parent) - layout.addWidget(self.tags_box, stretch=1000) + self.edit_widget = widget(parent) + layout.addWidget(self.edit_widget, stretch=1000) self.editor_button = QToolButton(self) - self.editor_button.setToolTip(_('Open Item editor. If CTRL or SHIFT is pressed, open Manage items')) + if name is None: + name = _('items') + if only_manage_items: + self.editor_button.setToolTip(_('Open the {} Category editor').format(name)) + else: + self.editor_button.setToolTip(_('Open the {} editor. If CTRL or SHIFT ' + 'is pressed, open the {} Category editor').format(name, name)) self.editor_button.setIcon(QIcon.ic('chapters.png')) layout.addWidget(self.editor_button) self.setLayout(layout) @@ -466,34 +493,34 @@ class MultipleWidget(QWidget): return self.editor_button def update_items_cache(self, values): - self.tags_box.update_items_cache(values) + self.edit_widget.update_items_cache(values) def clear(self): - self.tags_box.clear() + self.edit_widget.clear() def setEditText(self): - self.tags_box.setEditText() + self.edit_widget.setEditText() def addItem(self, itm): - self.tags_box.addItem(itm) + self.edit_widget.addItem(itm) def set_separator(self, sep): - self.tags_box.set_separator(sep) + self.edit_widget.set_separator(sep) def set_add_separator(self, sep): - self.tags_box.set_add_separator(sep) + self.edit_widget.set_add_separator(sep) def set_space_before_sep(self, v): - self.tags_box.set_space_before_sep(v) + self.edit_widget.set_space_before_sep(v) def setSizePolicy(self, v1, v2): - self.tags_box.setSizePolicy(v1, v2) + self.edit_widget.setSizePolicy(v1, v2) def setText(self, v): - self.tags_box.setText(v) + self.edit_widget.setText(v) def text(self): - return self.tags_box.text() + return self.edit_widget.text() def _save_dialog(parent, title, msg, det_msg=''): @@ -513,20 +540,18 @@ class Text(Base): self.parent = parent if self.col_metadata['is_multiple']: - w = MultipleWidget(parent) + w = MultipleWidget(parent, name=self.col_metadata['name']) w.set_separator(self.sep['ui_to_list']) if self.sep['ui_to_list'] == '&': w.set_space_before_sep(True) w.set_add_separator(tweaks['authors_completer_append_separator']) w.get_editor_button().clicked.connect(self.edit) - w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) - self.set_to_undefined = w.clear else: - w = EditWithComplete(parent) + w = MultipleWidget(parent, only_manage_items=True, name=self.col_metadata['name']) w.set_separator(None) - w.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) - w.setMinimumContentsLength(25) - self.set_to_undefined = w.clearEditText + w.get_editor_button().clicked.connect(super().edit) + w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + self.set_to_undefined = w.clear self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)] self.finish_ui_setup(parent, lambda parent: w) @@ -555,13 +580,12 @@ class Text(Base): self.editor.setText(self.sep['list_to_ui'].join(val)) def getter(self): + val = str(self.editor.text()).strip() if self.col_metadata['is_multiple']: - val = str(self.editor.text()).strip() ans = [x.strip() for x in val.split(self.sep['ui_to_list']) if x.strip()] if not ans: ans = None return ans - val = str(self.editor.currentText()).strip() if not val: val = None return val @@ -592,10 +616,7 @@ class Text(Base): self.setter(d.tags) def connect_data_changed(self, slot): - if self.col_metadata['is_multiple']: - s = self.editor.tags_box.currentTextChanged - else: - s = self.editor.currentTextChanged + s = self.editor.edit_widget.currentTextChanged s.connect(slot) self.signals_to_disconnect.append(s) @@ -603,14 +624,18 @@ class Text(Base): class Series(Base): def setup_ui(self, parent): - w = EditWithComplete(parent, sort_func=title_sort) + self.parent = parent + self.key = self.db.field_metadata.label_to_key(self.col_metadata['label'], + prefer_custom=True) + w = MultipleWidget(parent, only_manage_items=True, name=self.col_metadata['name']) + w.get_editor_button().clicked.connect(self.edit) + w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + self.set_to_undefined = w.clear w.set_separator(None) - w.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) - w.setMinimumContentsLength(25) - self.name_widget = w + self.name_widget = w.edit_widget self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)] self.finish_ui_setup(parent, lambda parent: w) - w.editTextChanged.connect(self.series_changed) + self.name_widget.editTextChanged.connect(self.series_changed) w = QLabel(label_string(self.col_metadata['name'])+_(' index'), parent) w.setToolTip(get_tooltip(self.col_metadata, add_index=True)) @@ -628,6 +653,7 @@ class Series(Base): self.idx_widget.setValue(1.0) def initialize(self, book_id): + self.book_id = book_id values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) @@ -660,6 +686,10 @@ class Series(Base): num=self.col_id) self.idx_widget.setValue(s_index) + def values_changed(self): + val, s_index = self.current_val + return val != self.initial_val or s_index != self.initial_index + @property def current_val(self): val, s_index = self.gui_val @@ -690,14 +720,23 @@ class Enumeration(Base): def setup_ui(self, parent): self.parent = parent + self.key = self.db.field_metadata.label_to_key(self.col_metadata['label'], + prefer_custom=True) + w = MultipleWidget(parent, only_manage_items=True, widget=QComboBox, name=self.col_metadata['name']) + w.get_editor_button().clicked.connect(self.edit) + w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + self.set_to_undefined = w.clear + self.name_widget = w.edit_widget self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)] - self.finish_ui_setup(parent, QComboBox) + self.finish_ui_setup(parent, lambda parent: w) + self.editor = self.name_widget vals = self.col_metadata['display']['enum_values'] self.editor.addItem('') for v in vals: self.editor.addItem(v) def initialize(self, book_id): + self.book_id = book_id val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) val = self.normalize_db_val(val) idx = self.editor.findText(val) @@ -710,7 +749,7 @@ class Enumeration(Base): idx = 0 self.editor.setCurrentIndex(idx) - self.initial_val = self.current_val + self.initial_val = val def setter(self, val): self.editor.setCurrentIndex(self.editor.findText(val)) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index d1130e5aa7..4e07503655 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -614,6 +614,7 @@ class SeriesEdit(EditWithComplete, ToMetadataMixin): LABEL = _('&Series:') FIELD_NAME = 'series' data_changed = pyqtSignal() + editor_requested = pyqtSignal() def __init__(self, parent): EditWithComplete.__init__(self, parent, sort_func=title_sort) @@ -651,9 +652,40 @@ class SeriesEdit(EditWithComplete, ToMetadataMixin): if series != self.original_val: self.books_to_refresh |= db.set_series(id_, series, notify=False, commit=True, allow_case_change=True) + @property + def changed(self): + return self.current_val != self.original_val + def break_cycles(self): self.dialog = None + def edit(self, db, id_): + if self.changed: + d = save_dialog(self, _('Series changed'), + _('You have changed the series. In order to use the category' + ' editor, you must either discard or apply these ' + 'changes. Apply changes?')) + if d == QMessageBox.StandardButton.Cancel: + return + if d == QMessageBox.StandardButton.Yes: + self.commit(db, id_) + db.commit() + self.original_val = self.current_val + else: + self.current_val = self.original_val + from calibre.gui2.ui import get_gui + get_gui().do_tags_list_edit(self.current_val, 'series') + db = get_gui().current_db + self.update_items_cache(db.new_api.all_field_names('series')) + self.initialize(db, id_) + + def keyPressEvent(self, ev): + if ev.key() == Qt.Key.Key_F2: + self.editor_requested.emit() + ev.accept() + return + return EditWithComplete.keyPressEvent(self, ev) + class SeriesIndexEdit(make_undoable(QDoubleSpinBox), ToMetadataMixin): @@ -679,7 +711,6 @@ class SeriesIndexEdit(make_undoable(QDoubleSpinBox), ToMetadataMixin): @property def current_val(self): - return self.value() @current_val.setter @@ -1817,6 +1848,7 @@ class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{ LABEL = _('&Publisher:') FIELD_NAME = 'publisher' data_changed = pyqtSignal() + editor_requested = pyqtSignal() def __init__(self, parent): EditWithComplete.__init__(self, parent) @@ -1833,7 +1865,6 @@ class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{ @property def current_val(self): - return clean_text(str(self.currentText())) @current_val.setter @@ -1846,13 +1877,46 @@ class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{ def initialize(self, db, id_): self.books_to_refresh = set() self.update_items_cache(db.new_api.all_field_names('publisher')) - self.original_val = self.current_val = db.new_api.field_for('publisher', id_) + self.current_val = db.new_api.field_for('publisher', id_) + # having this as a separate assignment ensures that original_val is not None + self.original_val = self.current_val def commit(self, db, id_): self.books_to_refresh |= db.set_publisher(id_, self.current_val, notify=False, commit=False, allow_case_change=True) return True + @property + def changed(self): + return self.original_val != self.current_val + + def edit(self, db, id_): + if self.changed: + d = save_dialog(self, _('Publisher changed'), + _('You have changed the publisher. In order to use the category' + ' editor, you must either discard or apply these ' + 'changes. Apply changes?')) + if d == QMessageBox.StandardButton.Cancel: + return + if d == QMessageBox.StandardButton.Yes: + self.commit(db, id_) + db.commit() + self.original_val = self.current_val + else: + self.current_val = self.original_val + from calibre.gui2.ui import get_gui + get_gui().do_tags_list_edit(self.current_val, 'publisher') + db = get_gui().current_db + self.update_items_cache(db.new_api.all_field_names('publisher')) + self.initialize(db, id_) + + def keyPressEvent(self, ev): + if ev.key() == Qt.Key.Key_F2: + self.editor_requested.emit() + ev.accept() + return + return EditWithComplete.keyPressEvent(self, ev) + # }}} # DateEdit {{{ diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 041c5c883c..dd93d1ef30 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -191,14 +191,18 @@ class MetadataSingleDialogBase(QDialog): self.manage_authors_button = QToolButton(self) self.manage_authors_button.setIcon(QIcon.ic('user_profile.png')) self.manage_authors_button.setToolTip('

' + _( - 'Manage authors. Use to rename authors and correct ' + 'Open the Authors Category editor. Use to rename authors and correct ' 'individual author\'s sort values') + '

') self.manage_authors_button.clicked.connect(self.authors.manage_authors) + self.series_editor_button = QToolButton(self) + self.series_editor_button.setToolTip(_('Open the Series Category editor')) + self.series_editor_button.setIcon(QIcon.ic('chapters.png')) + self.series_editor_button.clicked.connect(self.series_editor) self.series = SeriesEdit(self) + self.series.editor_requested.connect(self.series_editor) self.clear_series_button = QToolButton(self) - self.clear_series_button.setToolTip( - _('Clear series')) + self.clear_series_button.setToolTip(_('Clear series')) self.clear_series_button.clicked.connect(self.series.clear) self.series_index = SeriesIndexEdit(self, self.series) self.basic_metadata_widgets.extend([self.series, self.series_index]) @@ -229,7 +233,7 @@ class MetadataSingleDialogBase(QDialog): self.tags = TagsEdit(self) self.tags_editor_button = QToolButton(self) - self.tags_editor_button.setToolTip(_('Open Tag editor')) + self.tags_editor_button.setToolTip(_('Open the Tag editor. If CTRL or SHIFT is pressed, open the Tags Category editor')) self.tags_editor_button.setIcon(QIcon.ic('chapters.png')) self.tags_editor_button.clicked.connect(self.tags_editor) self.tags.tag_editor_requested.connect(self.tags_editor) @@ -256,7 +260,12 @@ class MetadataSingleDialogBase(QDialog): b.setMenu(QMenu(b)) self.update_paste_identifiers_menu() + self.publisher_editor_button = QToolButton(self) + self.publisher_editor_button.setToolTip(_('Open the Publishers Category editor')) + self.publisher_editor_button.setIcon(QIcon.ic('chapters.png')) + self.publisher_editor_button.clicked.connect(self.publisher_editor) self.publisher = PublisherEdit(self) + self.publisher.editor_requested.connect(self.publisher_editor) self.basic_metadata_widgets.append(self.publisher) self.timestamp = DateEdit(self) @@ -409,6 +418,12 @@ class MetadataSingleDialogBase(QDialog): def tags_editor(self, *args): self.tags.edit(self.db, self.book_id) + def publisher_editor(self, *args): + self.publisher.edit(self.db, self.book_id) + + def series_editor(self, *args): + self.series.edit(self.db, self.book_id) + def metadata_from_format(self, *args): mi, ext = self.formats_manager.get_selected_format_metadata(self.db, self.book_id) @@ -791,7 +806,9 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ sto(self.title_sort, self.manage_authors_button) sto(self.manage_authors_button, self.authors) create_row(1, self.authors, self.deduce_author_sort_button, self.author_sort) - sto(self.author_sort, self.series) + tl.addWidget(self.series_editor_button, 2, 0, 1, 1) + sto(self.author_sort, self.series_editor_button) + sto(self.series_editor_button, self.series) create_row(2, self.series, self.clear_series_button, self.series_index, icon='trash.png') @@ -851,8 +868,9 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ create_row2(4, self.timestamp, self.timestamp.clear_button) sto(self.timestamp.clear_button, self.pubdate) create_row2(5, self.pubdate, self.pubdate.clear_button) - sto(self.pubdate.clear_button, self.publisher) - create_row2(6, self.publisher, self.publisher.clear_button) + sto(self.pubdate.clear_button, self.publisher_editor_button) + sto(self.publisher_editor_button, self.publisher) + create_row2(6, self.publisher, self.publisher.clear_button, front_button=self.publisher_editor_button) sto(self.publisher.clear_button, self.languages) create_row2(7, self.languages) self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Policy.Expanding, @@ -957,8 +975,10 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{ tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) tl.addWidget(self.manage_authors_button, 2, 0, 1, 1) - tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) + tl.addWidget(self.series_editor_button, 6, 0, 1, 1) tl.addWidget(self.tags_editor_button, 6, 0, 1, 1) + tl.addWidget(self.publisher_editor_button, 9, 0, 1, 1) + tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) create_row(0, self.title, self.title_sort, button=self.deduce_title_sort_button, span=2, @@ -983,8 +1003,10 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{ button=self.clear_identifiers_button, icon='trash.png') sto(self.clear_identifiers_button, self.swap_title_author_button) sto(self.swap_title_author_button, self.manage_authors_button) - sto(self.manage_authors_button, self.tags_editor_button) - sto(self.tags_editor_button, self.paste_isbn_button) + sto(self.manage_authors_button, self.series_editor_button) + sto(self.series_editor_button, self.tags_editor_button) + sto(self.tags_editor_button, self.publisher_editor_button) + sto(self.publisher_editor_button, self.paste_isbn_button) tl.addItem(QSpacerItem(1, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding), 13, 1, 1 ,1) @@ -1111,8 +1133,10 @@ class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{ tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) tl.addWidget(self.manage_authors_button, 2, 0, 2, 1) - tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) + tl.addWidget(self.series_editor_button, 4, 0, 1, 1) tl.addWidget(self.tags_editor_button, 6, 0, 1, 1) + tl.addWidget(self.publisher_editor_button, 9, 0, 1, 1) + tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) create_row(0, self.title, self.title_sort, button=self.deduce_title_sort_button, span=2, @@ -1138,8 +1162,10 @@ class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{ button=self.clear_identifiers_button, icon='trash.png') sto(self.clear_identifiers_button, self.swap_title_author_button) sto(self.swap_title_author_button, self.manage_authors_button) - sto(self.manage_authors_button, self.tags_editor_button) - sto(self.tags_editor_button, self.paste_isbn_button) + sto(self.manage_authors_button, self.series_editor_button) + sto(self.series_editor_button, self.tags_editor_button) + sto(self.tags_editor_button, self.publisher_editor_button) + sto(self.publisher_editor_button, self.paste_isbn_button) tl.addItem(QSpacerItem(1, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding), 13, 1, 1 ,1)