%PDF- %PDF-
Direktori : /lib/calibre/calibre/gui2/dialogs/ |
Current File : //lib/calibre/calibre/gui2/dialogs/edit_authors_dialog.py |
#!/usr/bin/env python3 __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __license__ = 'GPL v3' from functools import partial from qt.core import (Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon, QDialogButtonBox, QFrame, QLabel, QTimer, QMenu, QApplication, QByteArray, QItemDelegate, QAction) from calibre.ebooks.metadata import author_to_author_sort, string_to_authors from calibre.gui2 import error_dialog, gprefs from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog from calibre.utils.config import prefs from calibre.utils.config_base import tweaks from calibre.utils.icu import sort_key, primary_contains, contains, primary_startswith QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction' class tableItem(QTableWidgetItem): def __init__(self, txt): QTableWidgetItem.__init__(self, txt) self.sort_key = sort_key(str(txt)) def setText(self, txt): self.sort_key = sort_key(str(txt)) QTableWidgetItem.setText(self, txt) def set_sort_key(self): self.sort_key = sort_key(str(self.text())) def __ge__(self, other): return self.sort_key >= other.sort_key def __lt__(self, other): return self.sort_key < other.sort_key class EditColumnDelegate(QItemDelegate): def __init__(self, completion_data): QItemDelegate.__init__(self) self.completion_data = completion_data def createEditor(self, parent, option, index): if index.column() == 0: if self.completion_data: from calibre.gui2.complete2 import EditWithComplete editor = EditWithComplete(parent) editor.set_separator(None) editor.update_items_cache(self.completion_data) else: from calibre.gui2.widgets import EnLineEdit editor = EnLineEdit(parent) return editor return QItemDelegate.createEditor(self, parent, option, index) class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): def __init__(self, parent, db, id_to_select, select_sort, select_link, find_aut_func, is_first_letter=False): 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.WindowType.WindowContextHelpButtonHint)) self.setWindowIcon(icon) try: self.table_column_widths = \ gprefs.get('manage_authors_table_widths', None) geom = gprefs.get('manage_authors_dialog_geometry', None) if geom: QApplication.instance().safe_restore_geometry(self, QByteArray(geom)) except Exception: pass self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) self.apply_vl_checkbox.stateChanged.connect(self.use_vl_changed) # Set up the heading for sorting self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) self.find_aut_func = find_aut_func self.table.resizeColumnsToContents() if self.table.columnWidth(2) < 200: self.table.setColumnWidth(2, 200) # set up the cellChanged signal only after the table is filled self.table.cellChanged.connect(self.cell_changed) self.recalc_author_sort.clicked.connect(self.do_recalc_author_sort) self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author) # Capture clicks on the horizontal header to sort the table columns hh = self.table.horizontalHeader() hh.sectionResized.connect(self.table_column_resized) hh.setSectionsClickable(True) hh.sectionClicked.connect(self.do_sort) hh.setSortIndicatorShown(True) # set up the search & filter boxes self.find_box.initialize('manage_authors_search') le = self.find_box.lineEdit() ac = le.findChild(QAction, QT_HIDDEN_CLEAR_ACTION) if ac is not None: ac.triggered.connect(self.clear_find) le.returnPressed.connect(self.do_find) self.find_box.editTextChanged.connect(self.find_text_changed) self.find_button.clicked.connect(self.do_find) self.find_button.setDefault(True) self.filter_box.initialize('manage_authors_filter') le = self.filter_box.lineEdit() ac = le.findChild(QAction, QT_HIDDEN_CLEAR_ACTION) if ac is not None: ac.triggered.connect(self.clear_filter) self.filter_box.lineEdit().returnPressed.connect(self.do_filter) self.filter_button.clicked.connect(self.do_filter) self.not_found_label = l = QLabel(self.table) l.setFrameStyle(QFrame.Shape.StyledPanel) l.setAutoFillBackground(True) l.setText(_('No matches found')) l.setAlignment(Qt.AlignmentFlag.AlignVCenter) l.resize(l.sizeHint()) l.move(10, 2) l.setVisible(False) self.not_found_label_timer = QTimer() self.not_found_label_timer.setSingleShot(True) self.not_found_label_timer.timeout.connect( self.not_found_label_timer_event, type=Qt.ConnectionType.QueuedConnection) self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.table.customContextMenuRequested.connect(self.show_context_menu) # 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('modified.png')) self.empty_icon = QIcon() if prefs['use_primary_find_in_search']: self.string_contains = primary_contains else: self.string_contains = contains 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, is_first_letter) def use_vl_changed(self, x): self.show_table(None, None, None, False) def clear_filter(self): self.filter_box.setText('') self.show_table(None, None, None, False) def do_filter(self): self.show_table(None, None, None, False) def show_table(self, id_to_select, select_sort, select_link, is_first_letter): auts_to_show = {t[0] for t in self.find_aut_func(use_virtual_library=self.apply_vl_checkbox.isChecked())} filter_text = icu_lower(str(self.filter_box.text())) if filter_text: auts_to_show = {id_ for id_ in auts_to_show if self.string_contains(filter_text, icu_lower(self.authors[id_]['name']))} self.table.blockSignals(True) self.table.clear() self.table.setColumnCount(3) self.table.setRowCount(len(auts_to_show)) 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']) name = name.replace('|', ',') name_item = tableItem(name) name_item.setData(Qt.ItemDataRole.UserRole, id_) sort_item = tableItem(sort) link_item = tableItem(link) self.table.setItem(row, 0, name_item) self.table.setItem(row, 1, sort_item) self.table.setItem(row, 2, link_item) self.set_icon(name_item, id_) self.set_icon(sort_item, id_) self.set_icon(link_item, id_) row += 1 self.table.setItemDelegate(EditColumnDelegate(self.completion_data)) self.table.setHorizontalHeaderLabels([_('Author'), _('Author sort'), _('Link')]) if self.last_sorted_by == 'sort': self.author_sort_order = 1 - self.author_sort_order self.do_sort_by_author_sort() elif self.last_sorted_by == 'author': self.author_order = 1 - self.author_order self.do_sort_by_author() else: self.link_order = 1 - self.link_order self.do_sort_by_link() # Position on the desired item select_item = None if id_to_select: use_as = tweaks['categories_use_field_for_author_name'] == 'author_sort' for row in range(0, len(auts_to_show)): if is_first_letter: item_txt = str(self.table.item(row, 1).text() if use_as else self.table.item(row, 0).text()) if primary_startswith(item_txt, id_to_select): select_item = self.table.item(row, 1 if use_as else 0) break elif id_to_select == self.table.item(row, 0).data(Qt.ItemDataRole.UserRole): if select_sort: select_item = self.table.item(row, 1) elif select_link: select_item = self.table.item(row, 2) else: select_item = (self.table.item(row, 1) if use_as else self.table.item(row, 0)) break if select_item: self.table.setCurrentItem(select_item) self.table.setFocus(Qt.FocusReason.OtherFocusReason) if select_sort or select_link: self.table.editItem(select_item) self.start_find_pos = select_item.row() * 2 + select_item.column() else: self.table.setCurrentCell(0, 0) self.find_box.setFocus() self.start_find_pos = -1 self.table.blockSignals(False) def save_state(self): self.table_column_widths = [] for c in range(0, self.table.columnCount()): self.table_column_widths.append(self.table.columnWidth(c)) 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: for c,w in enumerate(self.table_column_widths): self.table.setColumnWidth(c, w) else: # the vertical scroll bar might not be rendered, so might not yet # have a width. Assume 25. Not a problem because user-changed column # widths will be remembered w = self.table.width() - 25 - self.table.verticalHeader().width() w //= self.table.columnCount() for c in range(0, self.table.columnCount()): self.table.setColumnWidth(c, w) self.save_state() def get_column_name(self, column): return ['name', 'sort', 'link'][column] def show_context_menu(self, point): self.context_item = self.table.itemAt(point) case_menu = QMenu(_('Change case')) case_menu.setIcon(QIcon(I('font_size_larger.png'))) action_upper_case = case_menu.addAction(_('Upper case')) action_lower_case = case_menu.addAction(_('Lower case')) action_swap_case = case_menu.addAction(_('Swap case')) action_title_case = case_menu.addAction(_('Title case')) action_capitalize = case_menu.addAction(_('Capitalize')) action_upper_case.triggered.connect(self.upper_case) action_lower_case.triggered.connect(self.lower_case) action_swap_case.triggered.connect(self.swap_case) action_title_case.triggered.connect(self.title_case) action_capitalize.triggered.connect(self.capitalize) m = self.au_context_menu = QMenu(self) idx = self.table.indexAt(point) id_ = int(self.table.item(idx.row(), 0).data(Qt.ItemDataRole.UserRole)) sub = self.get_column_name(idx.column()) if self.context_item.text() != self.original_authors[id_][sub]: ca = m.addAction(QIcon(I('undo.png')), _('Undo')) ca.triggered.connect(partial(self.undo_cell, old_value=self.original_authors[id_][sub])) m.addSeparator() ca = m.addAction(QIcon(I('edit-copy.png')), _('Copy')) ca.triggered.connect(self.copy_to_clipboard) ca = m.addAction(QIcon(I('edit-paste.png')), _('Paste')) ca.triggered.connect(self.paste_from_clipboard) m.addSeparator() if self.context_item is not None and self.context_item.column() == 0: ca = m.addAction(_('Copy to author sort')) ca.triggered.connect(self.copy_au_to_aus) m.addSeparator() ca = m.addAction(QIcon(I('lt.png')), _("Show books by author in book list")) ca.triggered.connect(self.search_in_book_list) else: ca = m.addAction(_('Copy to author')) ca.triggered.connect(self.copy_aus_to_au) m.addSeparator() m.addMenu(case_menu) m.exec(self.table.mapToGlobal(point)) def undo_cell(self, old_value): self.context_item.setText(old_value) def search_in_book_list(self): from calibre.gui2.ui import get_gui row = self.context_item.row() get_gui().search.set_search_string('authors:="%s"' % str(self.table.item(row, 0).text()).replace(r'"', r'\"')) def copy_to_clipboard(self): cb = QApplication.clipboard() cb.setText(str(self.context_item.text())) def paste_from_clipboard(self): cb = QApplication.clipboard() self.context_item.setText(cb.text()) def upper_case(self): self.context_item.setText(icu_upper(str(self.context_item.text()))) def lower_case(self): self.context_item.setText(icu_lower(str(self.context_item.text()))) def swap_case(self): self.context_item.setText(str(self.context_item.text()).swapcase()) def title_case(self): from calibre.utils.titlecase import titlecase self.context_item.setText(titlecase(str(self.context_item.text()))) def capitalize(self): from calibre.utils.icu import capitalize self.context_item.setText(capitalize(str(self.context_item.text()))) def copy_aus_to_au(self): row = self.context_item.row() dest = self.table.item(row, 0) dest.setText(self.context_item.text()) def copy_au_to_aus(self): row = self.context_item.row() dest = self.table.item(row, 1) dest.setText(self.context_item.text()) def not_found_label_timer_event(self): self.not_found_label.setVisible(False) def clear_find(self): self.find_box.setText('') self.start_find_pos = -1 self.do_find() def find_text_changed(self): self.start_find_pos = -1 def do_find(self): self.not_found_label.setVisible(False) # For some reason the button box keeps stealing the RETURN shortcut. # Steal it back self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setDefault(False) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setAutoDefault(False) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setDefault(False) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setAutoDefault(False) st = icu_lower(str(self.find_box.currentText())) if not st: return for _ in range(0, self.table.rowCount()*2): self.start_find_pos = (self.start_find_pos + 1) % (self.table.rowCount()*2) r = (self.start_find_pos//2) % self.table.rowCount() c = self.start_find_pos % 2 item = self.table.item(r, c) text = icu_lower(str(item.text())) if st in text: self.table.setCurrentItem(item) self.table.setFocus(Qt.FocusReason.OtherFocusReason) return # Nothing found. Pop up the little dialog for 1.5 seconds self.not_found_label.setVisible(True) self.not_found_label_timer.start(1500) def do_sort(self, section): (self.do_sort_by_author, self.do_sort_by_author_sort, self.do_sort_by_link)[section]() def do_sort_by_author(self): self.last_sorted_by = 'author' self.author_order = 1 - self.author_order self.table.sortByColumn(0, self.author_order) def do_sort_by_author_sort(self): self.last_sorted_by = 'sort' self.author_sort_order = 1 - self.author_sort_order self.table.sortByColumn(1, self.author_sort_order) def do_sort_by_link(self): self.last_sorted_by = 'link' self.link_order = 1 - self.link_order self.table.sortByColumn(2, self.link_order) def accepted(self): self.save_state() self.result = [] 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() for row in range(0,self.table.rowCount()): item_aut = self.table.item(row, 0) id_ = int(item_aut.data(Qt.ItemDataRole.UserRole)) aut = str(item_aut.text()).strip() item_aus = self.table.item(row, 1) # Sometimes trailing commas are left by changing between copy algs aus = str(author_to_author_sort(aut)).rstrip(',') item_aus.setText(aus) self.authors[id_]['sort'] = aus self.set_icon(item_aus, id_) self.table.setFocus(Qt.FocusReason.OtherFocusReason) self.table.cellChanged.connect(self.cell_changed) def do_auth_sort_to_author(self): self.table.cellChanged.disconnect() for row in range(0,self.table.rowCount()): aus = str(self.table.item(row, 1).text()).strip() item_aut = self.table.item(row, 0) id_ = int(item_aut.data(Qt.ItemDataRole.UserRole)) item_aut.setText(aus) self.authors[id_]['name'] = aus self.set_icon(item_aut, id_) self.table.setFocus(Qt.FocusReason.OtherFocusReason) self.table.cellChanged.connect(self.cell_changed) def set_icon(self, item, id_): col_name = self.get_column_name(item.column()) if str(item.text()) != self.original_authors[id_][col_name]: item.setIcon(self.edited_icon) else: item.setIcon(self.empty_icon) def cell_changed(self, row, col): id_ = int(self.table.item(row, 0).data(Qt.ItemDataRole.UserRole)) if col == 0: item = self.table.item(row, 0) aut = str(item.text()).strip() aut_list = string_to_authors(aut) if len(aut_list) != 1: error_dialog(self.parent(), _('Invalid author name'), _('You cannot change an author to multiple authors.')).exec() aut = ' % '.join(aut_list) self.table.item(row, 0).setText(aut) item.set_sort_key() self.authors[id_]['name'] = aut self.set_icon(item, id_) c = self.table.item(row, 1) txt = author_to_author_sort(aut) self.authors[id_]['sort'] = txt c.setText(txt) # This triggers another cellChanged event item = c else: item = self.table.item(row, col) item.set_sort_key() self.set_icon(item, id_) self.authors[id_][self.get_column_name(col)] = str(item.text()) self.table.setCurrentItem(item) self.table.scrollToItem(item)