From b2e108042b9f01516328dce45332cae12644ac5e Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 16 May 2020 15:39:00 +0100 Subject: [PATCH] Enhancement #1878940: allow restricting manage authors to the current virtual library. Other improvements: - Get rid of sort buttons, instead sorting by clicking on the header - Support sorting by the author link column - Add an "edited" icon to any cell that has been changed --- .../gui2/dialogs/edit_authors_dialog.py | 221 ++++++++++++------ .../gui2/dialogs/edit_authors_dialog.ui | 26 +-- src/calibre/gui2/dialogs/tag_list_editor.ui | 6 +- src/calibre/gui2/tag_browser/ui.py | 26 ++- 4 files changed, 177 insertions(+), 102 deletions(-) diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index 43c7faad24..edddeee05b 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -47,10 +47,11 @@ class EditColumnDelegate(QItemDelegate): class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): - def __init__(self, parent, db, id_to_select, select_sort, select_link): + def __init__(self, parent, db, id_to_select, select_sort, select_link, find_aut_func): QDialog.__init__(self, parent) Ui_EditAuthorsDialog.__init__(self) self.setupUi(self) + # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) @@ -68,49 +69,15 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) + self.apply_vl_checkbox.stateChanged.connect(self.use_vl_changed) - # Set up the column headings + # Set up the heading for sorting self.table.setSelectionMode(QAbstractItemView.SingleSelection) - self.table.setColumnCount(3) self.down_arrow_icon = QIcon(I('arrow-down.png')) self.up_arrow_icon = QIcon(I('arrow-up.png')) self.blank_icon = QIcon(I('blank.png')) - self.auth_col = QTableWidgetItem(_('Author')) - self.table.setHorizontalHeaderItem(0, self.auth_col) - self.auth_col.setIcon(self.blank_icon) - self.aus_col = QTableWidgetItem(_('Author sort')) - self.table.setHorizontalHeaderItem(1, self.aus_col) - self.aus_col.setIcon(self.up_arrow_icon) - self.aul_col = QTableWidgetItem(_('Link')) - self.table.setHorizontalHeaderItem(2, self.aul_col) - self.aus_col.setIcon(self.blank_icon) - - # Add the data - self.authors = {} - auts = db.get_authors_with_ids() - self.table.setRowCount(len(auts)) - select_item = None - completion_data = [] - for row, (_id, author, sort, link) in enumerate(auts): - author = author.replace('|', ',') - self.authors[_id] = (author, sort, link) - completion_data.append(author) - aut = tableItem(author) - aut.setData(Qt.UserRole, _id) - sort = tableItem(sort) - link = tableItem(link) - self.table.setItem(row, 0, aut) - self.table.setItem(row, 1, sort) - self.table.setItem(row, 2, link) - if id_to_select in (_id, author): - if select_sort: - select_item = sort - elif select_link: - select_item = link - else: - select_item = aut - self.table.setItemDelegate(EditColumnDelegate(completion_data)) + self.find_aut_func = find_aut_func self.table.resizeColumnsToContents() if self.table.columnWidth(2) < 200: self.table.setColumnWidth(2, 200) @@ -118,28 +85,14 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): # set up the cellChanged signal only after the table is filled self.table.cellChanged.connect(self.cell_changed) - # set up sort buttons - self.sort_by_author.setCheckable(True) - self.sort_by_author.setChecked(False) - self.sort_by_author.clicked.connect(self.do_sort_by_author) - self.author_order = 1 - - self.sort_by_author_sort.clicked.connect(self.do_sort_by_author_sort) - self.sort_by_author_sort.setCheckable(True) - self.sort_by_author_sort.setChecked(True) - self.author_sort_order = 1 - self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort) self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author) - # Position on the desired item - if select_item is not None: - self.table.setCurrentItem(select_item) - self.table.editItem(select_item) - self.start_find_pos = select_item.row() * 2 + select_item.column() - else: - self.table.setCurrentCell(0, 0) - self.start_find_pos = -1 + # Capture clicks on the horizontal header to sort the table columns + hh = self.table.horizontalHeader() + hh.setSectionsClickable(True) + hh.sectionClicked.connect(self.header_clicked) + hh.sectionResized.connect(self.table_column_resized) # set up the search box self.find_box.initialize('manage_authors_search') @@ -165,7 +118,101 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.table.setContextMenuPolicy(Qt.CustomContextMenu) self.table.customContextMenuRequested.connect(self.show_context_menu) - self.do_sort_by_author_sort() + + # Fetch the data + self.authors = {} + self.original_authors = {} + auts = db.new_api.author_data() + self.completion_data = [] + for id_, v in auts.items(): + name = v['name'] + name = name.replace('|', ',') + self.completion_data.append(name) + self.authors[id_] = {'name': name, 'sort': v['sort'], 'link': v['link']} + self.original_authors[id_] = {'name': name, 'sort': v['sort'], + 'link': v['link']} + + self.edited_icon = QIcon(I('edit_input.png')) + self.down_arrow_icon = QIcon(I('arrow-down.png')) + self.up_arrow_icon = QIcon(I('arrow-up.png')) + self.blank_icon = QIcon(I('blank.png')) + self.last_sorted_by = 'sort' + self.author_order = 1 + self.author_sort_order = 0 + self.link_order = 1 + self.show_table(id_to_select, select_sort, select_link) + + def use_vl_changed(self, x): + self.show_table(None, None, None) + + def show_table(self, id_to_select, select_sort, select_link): + auts_to_show = [t[0] for t in + self.find_aut_func(use_virtual_library = self.apply_vl_checkbox.isChecked())] + self.table.blockSignals(True) + self.table.clear() + self.table.setColumnCount(3) + self.auth_col = QTableWidgetItem(_('Author')) + self.table.setHorizontalHeaderItem(0, self.auth_col) + self.aus_col = QTableWidgetItem(_('Author sort')) + self.table.setHorizontalHeaderItem(1, self.aus_col) + self.aul_col = QTableWidgetItem(_('Link')) + self.table.setHorizontalHeaderItem(2, self.aul_col) + + self.table.setRowCount(len(auts_to_show)) + select_item = None + row = 0 + for id_, v in self.authors.items(): + if id_ not in auts_to_show: + continue + name, sort, link = (v['name'], v['sort'], v['link']) + orig = self.original_authors[id_] + name = name.replace('|', ',') + + name_item = tableItem(name) + name_item.setData(Qt.UserRole, id_) + if name != orig['name']: + name_item.setIcon(self.edited_icon) + sort_item = tableItem(sort) + if sort != orig['sort']: + sort_item.setIcon(self.edited_icon) + 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, 1, sort_item) + self.table.setItem(row, 2, link_item) + + if id_to_select and id_to_select in (id_, name): + print('id', id_to_select) + if select_sort: + select_item = sort_item + elif select_link: + select_item = link_item + else: + select_item = name_item + row += 1 + + self.table.setItemDelegate(EditColumnDelegate(self.completion_data)) + + if self.last_sorted_by == 'sort': + self.author_sort_order = 1 if self.author_sort_order == 0 else 0 + self.do_sort_by_author_sort() + elif self.last_sorted_by == 'author': + self.author_order = 1 if self.author_order == 0 else 0 + self.do_sort_by_author() + else: + self.link_order = 1 if self.link_order == 0 else 0 + self.do_sort_by_link() + + # Position on the desired item + if select_item is not None: + self.table.setCurrentItem(select_item) + self.table.editItem(select_item) + self.start_find_pos = select_item.row() * 2 + select_item.column() + else: + self.table.setCurrentCell(0, 0) + self.start_find_pos = -1 + self.table.blockSignals(False) def save_state(self): self.table_column_widths = [] @@ -174,6 +221,11 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): gprefs['manage_authors_table_widths'] = self.table_column_widths gprefs['manage_authors_dialog_geometry'] = bytearray(self.saveGeometry()) + def table_column_resized(self, col, old, new): + self.table_column_widths = [] + for c in range(0, self.table.columnCount()): + self.table_column_widths.append(self.table.columnWidth(c)) + def resizeEvent(self, *args): QDialog.resizeEvent(self, *args) if self.table_column_widths is not None: @@ -216,7 +268,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): ca.triggered.connect(self.copy_au_to_aus) m.addSeparator() ca = m.addAction(_("Show books by author in book list")) - ca.triggered.connect(self.search) + ca.triggered.connect(self.search_in_book_list) else: ca = m.addAction(_('Copy to author')) ca.triggered.connect(self.copy_aus_to_au) @@ -224,7 +276,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): m.addMenu(case_menu) m.exec_(self.table.mapToGlobal(point)) - def search(self): + def search_in_book_list(self): from calibre.gui2.ui import get_gui row = self.context_item.row() get_gui().search.set_search_string(self.table.item(row, 0).text()) @@ -294,35 +346,48 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.not_found_label.setVisible(True) self.not_found_label_timer.start(1500) + def header_clicked(self, idx): + if idx == 0: + self.do_sort_by_author() + elif idx == 1: + self.do_sort_by_author_sort() + else: + self.do_sort_by_link() + def do_sort_by_author(self): + self.last_sorted_by = 'author' self.author_order = 1 if self.author_order == 0 else 0 self.table.sortByColumn(0, self.author_order) - self.sort_by_author.setChecked(True) - self.sort_by_author_sort.setChecked(False) self.auth_col.setIcon(self.down_arrow_icon if self.author_order else self.up_arrow_icon) self.aus_col.setIcon(self.blank_icon) + self.aul_col.setIcon(self.blank_icon) def do_sort_by_author_sort(self): + self.last_sorted_by = 'sort' self.author_sort_order = 1 if self.author_sort_order == 0 else 0 self.table.sortByColumn(1, self.author_sort_order) - self.sort_by_author.setChecked(False) - self.sort_by_author_sort.setChecked(True) self.aus_col.setIcon(self.down_arrow_icon if self.author_sort_order else self.up_arrow_icon) self.auth_col.setIcon(self.blank_icon) + self.aul_col.setIcon(self.blank_icon) + + def do_sort_by_link(self): + self.last_sorted_by = 'link' + self.link_order = 1 if self.link_order == 0 else 0 + self.table.sortByColumn(2, self.link_order) + self.aul_col.setIcon(self.down_arrow_icon if self.link_order + else self.up_arrow_icon) + self.auth_col.setIcon(self.blank_icon) + self.aus_col.setIcon(self.blank_icon) def accepted(self): self.save_state() self.result = [] - for row in range(0,self.table.rowCount()): - id = int(self.table.item(row, 0).data(Qt.UserRole)) - aut = unicode_type(self.table.item(row, 0).text()).strip() - sort = unicode_type(self.table.item(row, 1).text()).strip() - link = unicode_type(self.table.item(row, 2).text()).strip() - orig_aut,orig_sort,orig_link = self.authors[id] - if orig_aut != aut or orig_sort != sort or orig_link != link: - self.result.append((id, orig_aut, aut, sort, link)) + for id_, v in self.authors.items(): + orig = self.original_authors[id_] + if orig != v: + self.result.append((id_, orig['name'], v['name'], v['sort'], v['link'])) def do_recalc_author_sort(self): self.table.cellChanged.disconnect() @@ -347,8 +412,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.table.cellChanged.connect(self.cell_changed) def cell_changed(self, row, col): + id_ = int(self.table.item(row, 0).data(Qt.UserRole)) if col == 0: item = self.table.item(row, 0) + item.setIcon(self.edited_icon) aut = unicode_type(item.text()).strip() aut_list = string_to_authors(aut) if len(aut_list) != 1: @@ -356,10 +423,18 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): _('You cannot change an author to multiple authors.')).exec_() aut = ' % '.join(aut_list) self.table.item(row, 0).setText(aut) + self.authors[id_]['name'] = aut c = self.table.item(row, 1) - c.setText(author_to_author_sort(aut)) + txt = author_to_author_sort(aut) + c.setText(txt) + self.authors[id_]['sort'] = txt item = c else: item = self.table.item(row, col) + item.setIcon(self.edited_icon) + if col == 1: + self.authors[id_]['sort'] = item.text() + else: + self.authors[id_]['link'] = item.text() self.table.setCurrentItem(item) self.table.scrollToItem(item) diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.ui b/src/calibre/gui2/dialogs/edit_authors_dialog.ui index e0f08d1b6a..8268c551ec 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.ui +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.ui @@ -62,6 +62,18 @@ + + + + <p>Show authors only if they appear in the + current Virtual library. Edits already done may be hidden but will + not be forgotten.</p> + + + &Show only available items in current Virtual library + + + @@ -79,20 +91,6 @@ - - - - Sort by &author - - - - - - - Sort by author &sort - - - diff --git a/src/calibre/gui2/dialogs/tag_list_editor.ui b/src/calibre/gui2/dialogs/tag_list_editor.ui index f9235dc69e..bd2e157ae8 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.ui +++ b/src/calibre/gui2/dialogs/tag_list_editor.ui @@ -65,9 +65,9 @@ - <p>Show items in the Available items box only if they appear in the - current Virtual library. Applied items not in the Virtual library will be marked - "not on any book".</p> + <p>Show items only if they appear in the + current Virtual library. Edits already done may be hidden but will + not be forgotten.</p> &Show only available items in current Virtual library diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index b67f643ac6..0bbd848679 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -231,6 +231,15 @@ class TagBrowserMixin(object): # {{{ self.tags_view.recount() self.user_categories_edited() + def get_book_ids(self, use_virtual_library, db, category): + book_ids = None if not use_virtual_library else self.tags_view.model().get_book_ids_to_use() + data = db.new_api.get_categories(book_ids=book_ids) + if category in data: + result = [(t.id, t.original_name, t.count) for t in data[category] if t.count > 0] + else: + result = None + return result + def do_tags_list_edit(self, tag, category): ''' Open the 'manage_X' dialog where X == category. If tag is not None, the @@ -238,23 +247,15 @@ class TagBrowserMixin(object): # {{{ ''' db = self.current_db - - def get_book_ids(use_virtual_library): - book_ids = None if not use_virtual_library else self.tags_view.model().get_book_ids_to_use() - data = db.new_api.get_categories(book_ids=book_ids) - if category in data: - result = [(t.id, t.original_name, t.count) for t in data[category] if t.count > 0] - else: - result = None - return result - if category == 'series': key = lambda x:sort_key(title_sort(x)) else: key = sort_key d = TagListEditor(self, cat_name=db.field_metadata[category]['name'], - tag_to_match=tag, get_book_ids=get_book_ids, sorter=key) + tag_to_match=tag, + get_book_ids=partial(self.get_book_ids, db=db, category=category), + sorter=key) d.exec_() if d.result() == d.Accepted: to_rename = d.to_rename # dict of old id to new name @@ -361,7 +362,8 @@ class TagBrowserMixin(object): # {{{ ''' db = self.library_view.model().db - editor = EditAuthorsDialog(parent, db, id_, select_sort, select_link) + editor = EditAuthorsDialog(parent, db, id_, select_sort, select_link, + partial(self.get_book_ids, db=db, category='authors')) if editor.exec_() == editor.Accepted: # Save and restore the current selections. Note that some changes # will cause sort orders to change, so don't bother with attempting