class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): def __init__(self, parent, db, id_to_select, select_sort, select_link): 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)) self.setWindowIcon(icon) try: self.table_column_widths = \ gprefs.get('manage_authors_table_widths', None) geom = gprefs.get('manage_authors_dialog_geometry', bytearray('')) self.restoreGeometry(QByteArray(geom)) except: pass self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) # Set up the column headings 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 for row, (id, author, sort, link) in enumerate(auts): author = author.replace('|', ',') self.authors[id] = (author, sort, link) 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.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) # 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.table.sortByColumn(1, Qt.AscendingOrder) 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 # set up the search box self.find_box.initialize('manage_authors_search') self.find_box.lineEdit().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) l = QLabel(self.table) self.not_found_label = l l.setFrameStyle(QFrame.StyledPanel) l.setAutoFillBackground(True) l.setText(_('No matches found')) l.setAlignment(Qt.AlignVCenter) l.resize(l.sizeHint()) l.move(10,20) l.setVisible(False) self.not_found_label.move(40, 40) 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.QueuedConnection) self.table.setContextMenuPolicy(Qt.CustomContextMenu) self.table.customContextMenuRequested .connect(self.show_context_menu) 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 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 show_context_menu(self, point): self.context_item = self.table.itemAt(point) case_menu = QMenu(_('Change Case')) 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() ca = m.addAction(_('Copy')) ca.triggered.connect(self.copy_to_clipboard) ca = m.addAction(_('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) 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 copy_to_clipboard(self): cb = QApplication.clipboard() cb.setText(unicode(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(unicode(self.context_item.text()))) def lower_case(self): self.context_item.setText(icu_lower(unicode(self.context_item.text()))) def swap_case(self): self.context_item.setText(unicode(self.context_item.text()).swapcase()) def title_case(self): from calibre.utils.titlecase import titlecase self.context_item.setText(titlecase(unicode(self.context_item.text()))) def capitalize(self): from calibre.utils.icu import capitalize self.context_item.setText(capitalize(unicode(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 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.Ok).setDefault(False) self.buttonBox.button(QDialogButtonBox.Ok).setAutoDefault(False) self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(False) self.buttonBox.button(QDialogButtonBox.Cancel).setAutoDefault(False) st = icu_lower(unicode(self.find_box.currentText())) for i 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(unicode(item.text())) if st in text: self.table.setCurrentItem(item) self.table.setFocus(True) 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_by_author(self): 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) def do_sort_by_author_sort(self): 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) 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(self.table.item(row, 0).text()).strip() sort = unicode(self.table.item(row, 1).text()).strip() link = unicode(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)) def do_recalc_author_sort(self): self.table.cellChanged.disconnect() for row in range(0,self.table.rowCount()): item = self.table.item(row, 0) aut = unicode(item.text()).strip() c = self.table.item(row, 1) # Sometimes trailing commas are left by changing between copy algs c.setText(author_to_author_sort(aut).rstrip(',')) self.table.setFocus(Qt.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()): item = self.table.item(row, 1) aus = unicode(item.text()).strip() c = self.table.item(row, 0) # Sometimes trailing commas are left by changing between copy algs c.setText(aus) self.table.setFocus(Qt.OtherFocusReason) self.table.cellChanged.connect(self.cell_changed) def cell_changed(self, row, col): if col == 0: item = self.table.item(row, 0) aut = unicode(item.text()).strip() amper = aut.find('&') if amper >= 0: error_dialog(self.parent(), _('Invalid author name'), _('Author names cannot contain & characters.')).exec_() aut = aut.replace('&', '%') self.table.item(row, 0).setText(aut) c = self.table.item(row, 1) c.setText(author_to_author_sort(aut)) item = c else: item = self.table.item(row, col) self.table.setCurrentItem(item) self.table.scrollToItem(item)
class TagListEditor(QDialog, Ui_TagListEditor): def __init__(self, window, cat_name, tag_to_match, get_book_ids, sorter): QDialog.__init__(self, window) Ui_TagListEditor.__init__(self) self.setupUi(self) self.search_box.setMinimumContentsLength(25) # Put the category name into the title bar t = self.windowTitle() self.setWindowTitle(t + ' (' + cat_name + ')') # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) # Get saved geometry info try: self.table_column_widths = \ gprefs.get('tag_list_editor_table_widths', None) except: pass # initialization self.to_rename = {} self.to_delete = set([]) self.all_tags = {} self.original_names = {} self.ordered_tags = [] self.sorter = sorter self.get_book_ids = get_book_ids # Set up the column headings 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')) # 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) self.name_order = 0 self.count_order = 1 self.was_order = 1 self.table.setItemDelegate(EditColumnDelegate(self.table)) # Add the data select_item = self.fill_in_table(None, tag_to_match) # Scroll to the selected item if there is one if select_item is not None: self.table.setCurrentItem(select_item) self.delete_button.clicked.connect(self.delete_tags) self.rename_button.clicked.connect(self.rename_tag) self.undo_button.clicked.connect(self.undo_edit) self.table.itemDoubleClicked.connect(self._rename_tag) self.table.itemChanged.connect(self.finish_editing) self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) self.search_box.initialize('tag_list_search_box_' + cat_name) self.search_button.clicked.connect(self.all_matching_clicked) self.search_button.setDefault(True) self.apply_vl_checkbox.clicked.connect(self.vl_box_changed) self.table.setEditTriggers(QTableWidget.EditKeyPressed) try: geom = gprefs.get('tag_list_editor_dialog_geometry', None) if geom is not None: self.restoreGeometry(QByteArray(geom)) else: self.resize(self.sizeHint()+QSize(150, 100)) except: pass def vl_box_changed(self): self.fill_in_table(None, None) def fill_in_table(self, tags, tag_to_match): data = self.get_book_ids(self.apply_vl_checkbox.isChecked()) self.all_tags = {} for k,v,count in data: self.all_tags[v] = {'key': k, 'count': count, 'cur_name': v, 'is_deleted': k in self.to_delete} self.original_names[k] = v self.ordered_tags = sorted(self.all_tags.keys(), key=self.sorter) if tags is None: tags = self.ordered_tags select_item = None self.table.blockSignals(True) self.table.clear() self.table.setColumnCount(3) self.name_col = QTableWidgetItem(_('Tag')) self.table.setHorizontalHeaderItem(0, self.name_col) self.name_col.setIcon(self.up_arrow_icon) self.count_col = QTableWidgetItem(_('Count')) self.table.setHorizontalHeaderItem(1, self.count_col) self.count_col.setIcon(self.blank_icon) self.was_col = QTableWidgetItem(_('Was')) self.table.setHorizontalHeaderItem(2, self.was_col) self.count_col.setIcon(self.blank_icon) self.table.setRowCount(len(tags)) for row,tag in enumerate(tags): item = NameTableWidgetItem() item.set_is_deleted(self.all_tags[tag]['is_deleted']) _id = self.all_tags[tag]['key'] item.setData(Qt.UserRole, _id) item.set_initial_text(tag) if _id in self.to_rename: item.setText(self.to_rename[_id]) else: item.setText(tag) item.setFlags(item.flags() | Qt.ItemIsSelectable | Qt.ItemIsEditable) self.table.setItem(row, 0, item) if tag == tag_to_match: select_item = item item = CountTableWidgetItem(self.all_tags[tag]['count']) # only the name column can be selected item.setFlags(item.flags() & ~(Qt.ItemIsSelectable|Qt.ItemIsEditable)) self.table.setItem(row, 1, item) item = QTableWidgetItem() item.setFlags(item.flags() & ~(Qt.ItemIsSelectable|Qt.ItemIsEditable)) if _id in self.to_rename or _id in self.to_delete: item.setData(Qt.DisplayRole, tag) self.table.setItem(row, 2, item) self.table.blockSignals(False) return select_item def all_matching_clicked(self): for i in range(0, self.table.rowCount()): item = self.table.item(i, 0) tag = item.initial_text() self.all_tags[tag]['cur_name'] = item.text() self.all_tags[tag]['is_deleted'] = item.is_deleted search_for = icu_lower(unicode_type(self.search_box.text())) if len(search_for) == 0: self.fill_in_table(None, None) result = [] for k in self.ordered_tags: if search_for in icu_lower(unicode_type(self.all_tags[k]['cur_name'])): result.append(k) self.fill_in_table(result, None) 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) def save_geometry(self): gprefs['tag_list_editor_table_widths'] = self.table_column_widths gprefs['tag_list_editor_dialog_geometry'] = bytearray(self.saveGeometry()) def finish_editing(self, item): if not item.text(): error_dialog(self, _('Item is blank'), _( 'An item cannot be set to nothing. Delete it instead.'), show=True) item.setText(item.initial_text()) return if item.text() != item.initial_text(): id_ = int(item.data(Qt.UserRole)) self.to_rename[id_] = unicode_type(item.text()) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, item.initial_text()) self.table.blockSignals(False) def undo_edit(self): indexes = self.table.selectionModel().selectedRows() if not indexes: error_dialog(self, _('No item selected'), _('You must select one item from the list of Available items.')).exec_() return if not confirm( _('Do you really want to undo your changes?'), 'tag_list_editor_undo'): return self.table.blockSignals(True) for idx in indexes: row = idx.row() item = self.table.item(row, 0) item.setText(item.initial_text()) item.set_is_deleted(False) self.to_delete.discard(int(item.data(Qt.UserRole))) self.to_rename.pop(int(item.data(Qt.UserRole)), None) self.table.item(row, 2).setData(Qt.DisplayRole, '') self.table.blockSignals(False) def rename_tag(self): item = self.table.item(self.table.currentRow(), 0) self._rename_tag(item) def _rename_tag(self, item): if item is None: error_dialog(self, _('No item selected'), _('You must select one item from the list of Available items.')).exec_() return col_zero_item = self.table.item(item.row(), 0) if col_zero_item.is_deleted: if not question_dialog(self, _('Undelete item?'), '<p>'+_('That item is deleted. Do you want to undelete it?')+'<br>'): return col_zero_item.set_is_deleted(False) self.to_delete.discard(int(col_zero_item.data(Qt.UserRole))) orig = self.table.item(col_zero_item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, '') self.table.blockSignals(False) else: self.table.editItem(item) def delete_tags(self): deletes = self.table.selectedItems() if not deletes: error_dialog(self, _('No items selected'), _('You must select at least one item from the list.')).exec_() return to_del = [] to_undel = [] for item in deletes: if item.is_deleted: to_undel.append(item) else: to_del.append(item) if to_del: ct = ', '.join([unicode_type(item.text()) for item in to_del]) if not confirm( '<p>'+_('Are you sure you want to delete the following items?')+'<br>'+ct, 'tag_list_editor_delete'): return if to_undel: ct = ', '.join([unicode_type(item.text()) for item in to_undel]) if not confirm( '<p>'+_('Are you sure you want to undelete the following items?')+'<br>'+ct, 'tag_list_editor_undelete'): return row = self.table.row(deletes[0]) for item in deletes: if item.is_deleted: item.set_is_deleted(False) self.to_delete.discard(int(item.data(Qt.UserRole))) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, '') self.table.blockSignals(False) else: id = int(item.data(Qt.UserRole)) self.to_delete.add(id) item.set_is_deleted(True) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, item.initial_text()) 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)) def header_clicked(self, idx): if idx == 0: self.do_sort_by_name() elif idx == 1: self.do_sort_by_count() else: self.do_sort_by_was() def do_sort_by_name(self): self.name_order = 1 if self.name_order == 0 else 0 self.table.sortByColumn(0, self.name_order) self.name_col.setIcon(self.down_arrow_icon if self.name_order else self.up_arrow_icon) self.count_col.setIcon(self.blank_icon) self.was_col.setIcon(self.blank_icon) def do_sort_by_count(self): self.count_order = 1 if self.count_order == 0 else 0 self.table.sortByColumn(1, self.count_order) self.count_col.setIcon(self.down_arrow_icon if self.count_order else self.up_arrow_icon) self.name_col.setIcon(self.blank_icon) self.was_col.setIcon(self.blank_icon) def do_sort_by_was(self): self.was_order = 1 if self.was_order == 0 else 0 self.table.sortByColumn(2, self.was_order) self.was_col.setIcon(self.down_arrow_icon if self.was_order else self.up_arrow_icon) self.name_col.setIcon(self.blank_icon) self.count_col.setIcon(self.blank_icon) def accepted(self): self.save_geometry()
class TagListEditor(QDialog, Ui_TagListEditor): def __init__(self, window, cat_name, tag_to_match, data, sorter): QDialog.__init__(self, window) Ui_TagListEditor.__init__(self) self.setupUi(self) self.search_box.setMinimumContentsLength(25) # Put the category name into the title bar t = self.windowTitle() self.setWindowTitle(t + ' (' + cat_name + ')') # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) # Get saved geometry info try: self.table_column_widths = \ gprefs.get('tag_list_editor_table_widths', None) except: pass # initialization self.to_rename = {} self.to_delete = set([]) self.original_names = {} self.all_tags = {} self.counts = {} for k,v,count in data: self.all_tags[v] = k self.counts[v] = count self.original_names[k] = v # Set up the column headings 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.table.setColumnCount(3) self.name_col = QTableWidgetItem(_('Tag')) self.table.setHorizontalHeaderItem(0, self.name_col) self.name_col.setIcon(self.up_arrow_icon) self.count_col = QTableWidgetItem(_('Count')) self.table.setHorizontalHeaderItem(1, self.count_col) self.count_col.setIcon(self.blank_icon) self.was_col = QTableWidgetItem(_('Was')) self.table.setHorizontalHeaderItem(2, self.was_col) self.count_col.setIcon(self.blank_icon) # 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) self.name_order = 0 self.count_order = 1 self.was_order = 1 # Add the data select_item = None self.table.setRowCount(len(self.all_tags)) for row,tag in enumerate(sorted(self.all_tags.keys(), key=sorter)): item = NameTableWidgetItem(tag) item.setData(Qt.UserRole, self.all_tags[tag]) item.setFlags(item.flags() | Qt.ItemIsSelectable | Qt.ItemIsEditable) self.table.setItem(row, 0, item) if tag == tag_to_match: select_item = item item = CountTableWidgetItem(self.counts[tag]) # only the name column can be selected item.setFlags(item.flags() & ~(Qt.ItemIsSelectable|Qt.ItemIsEditable)) self.table.setItem(row, 1, item) item = QTableWidgetItem('') item.setFlags(item.flags() & ~(Qt.ItemIsSelectable|Qt.ItemIsEditable)) self.table.setItem(row, 2, item) # Scroll to the selected item if there is one if select_item is not None: self.table.setCurrentItem(select_item) self.delete_button.clicked.connect(self.delete_tags) self.rename_button.clicked.connect(self.rename_tag) self.table.itemDoubleClicked.connect(self._rename_tag) self.table.itemChanged.connect(self.finish_editing) self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) self.search_box.initialize('tag_list_search_box_' + cat_name) self.search_box.editTextChanged.connect(self.find_text_changed) self.search_button.clicked.connect(self.search_clicked) self.search_button.setDefault(True) self.start_find_pos = -1 try: geom = gprefs.get('tag_list_editor_dialog_geometry', None) if geom is not None: self.restoreGeometry(QByteArray(geom)) else: self.resize(self.sizeHint()+QSize(150, 100)) except: pass def find_text_changed(self): self.start_find_pos = -1 def search_clicked(self): search_for = icu_lower(unicode(self.search_box.text())) if not search_for: error_dialog(self, _('Find'), _('You must enter some text to search for'), show=True, show_copy_button=False) return rows = self.table.rowCount() for i in range(0, rows): self.start_find_pos += 1 if self.start_find_pos >= rows: self.start_find_pos = 0 item = self.table.item(self.start_find_pos, 0) if search_for in icu_lower(unicode(item.text())): self.table.setCurrentItem(item) return info_dialog(self, _('Find'), _('No tag found'), show=True, show_copy_button=False) 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) def save_geometry(self): gprefs['tag_list_editor_table_widths'] = self.table_column_widths gprefs['tag_list_editor_dialog_geometry'] = bytearray(self.saveGeometry()) def finish_editing(self, item): if not item.text(): error_dialog(self, _('Item is blank'), _('An item cannot be set to nothing. Delete it instead.')).exec_() item.setText(item.previous_text()) return if item.text() != item.initial_text(): id_ = int(item.data(Qt.UserRole)) self.to_rename[id_] = unicode(item.text()) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, item.initial_text()) self.table.blockSignals(False) def rename_tag(self): item = self.table.item(self.table.currentRow(), 0) self._rename_tag(item) def _rename_tag(self, item): if item is None: error_dialog(self, _('No item selected'), _('You must select one item from the list of Available items.')).exec_() return self.table.editItem(item) def delete_tags(self): deletes = self.table.selectedItems() if not deletes: error_dialog(self, _('No items selected'), _('You must select at least one item from the list.')).exec_() return ct = ', '.join([unicode(item.text()) for item in deletes]) if not question_dialog(self, _('Are you sure?'), '<p>'+_('Are you sure you want to delete the following items?')+'<br>'+ct): return row = self.table.row(deletes[0]) for item in deletes: id = int(item.data(Qt.UserRole)) self.to_delete.add(id) self.table.removeRow(self.table.row(item)) if row >= self.table.rowCount(): row = self.table.rowCount() - 1 if row >= 0: self.table.scrollToItem(self.table.item(row, 0)) def header_clicked(self, idx): if idx == 0: self.do_sort_by_name() elif idx == 1: self.do_sort_by_count() else: self.do_sort_by_was() def do_sort_by_name(self): self.name_order = 1 if self.name_order == 0 else 0 self.table.sortByColumn(0, self.name_order) self.name_col.setIcon(self.down_arrow_icon if self.name_order else self.up_arrow_icon) self.count_col.setIcon(self.blank_icon) self.was_col.setIcon(self.blank_icon) def do_sort_by_count(self): self.count_order = 1 if self.count_order == 0 else 0 self.table.sortByColumn(1, self.count_order) self.count_col.setIcon(self.down_arrow_icon if self.count_order else self.up_arrow_icon) self.name_col.setIcon(self.blank_icon) self.was_col.setIcon(self.blank_icon) def do_sort_by_was(self): self.was_order = 1 if self.was_order == 0 else 0 self.table.sortByColumn(2, self.was_order) self.was_col.setIcon(self.down_arrow_icon if self.was_order else self.up_arrow_icon) self.name_col.setIcon(self.blank_icon) self.count_col.setIcon(self.blank_icon) def accepted(self): self.save_geometry()
class TagListEditor(QDialog, Ui_TagListEditor): def __init__(self, window, cat_name, tag_to_match, get_book_ids, sorter): QDialog.__init__(self, window) Ui_TagListEditor.__init__(self) self.setupUi(self) self.search_box.setMinimumContentsLength(25) # Put the category name into the title bar t = self.windowTitle() self.setWindowTitle(t + ' (' + cat_name + ')') # Remove help icon on title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags() & (~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) # Get saved geometry info try: self.table_column_widths = \ gprefs.get('tag_list_editor_table_widths', None) except: pass # initialization self.to_rename = {} self.to_delete = set() self.all_tags = {} self.original_names = {} self.ordered_tags = [] self.sorter = sorter self.get_book_ids = get_book_ids # Set up the column headings 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')) # 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) self.name_order = 0 self.count_order = 1 self.was_order = 1 self.table.setItemDelegate(EditColumnDelegate(self.table)) # Add the data select_item = self.fill_in_table(None, tag_to_match) # Scroll to the selected item if there is one if select_item is not None: self.table.setCurrentItem(select_item) self.delete_button.clicked.connect(self.delete_tags) self.rename_button.clicked.connect(self.rename_tag) self.undo_button.clicked.connect(self.undo_edit) self.table.itemDoubleClicked.connect(self._rename_tag) self.table.itemChanged.connect(self.finish_editing) self.buttonBox.button(QDialogButtonBox.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) self.search_box.initialize('tag_list_search_box_' + cat_name) self.search_button.clicked.connect(self.all_matching_clicked) self.search_button.setDefault(True) self.apply_vl_checkbox.clicked.connect(self.vl_box_changed) self.table.setEditTriggers(QTableWidget.EditKeyPressed) try: geom = gprefs.get('tag_list_editor_dialog_geometry', None) if geom is not None: self.restoreGeometry(QByteArray(geom)) else: self.resize(self.sizeHint() + QSize(150, 100)) except: pass def vl_box_changed(self): self.fill_in_table(None, None) def fill_in_table(self, tags, tag_to_match): data = self.get_book_ids(self.apply_vl_checkbox.isChecked()) self.all_tags = {} for k, v, count in data: self.all_tags[v] = { 'key': k, 'count': count, 'cur_name': v, 'is_deleted': k in self.to_delete } self.original_names[k] = v self.ordered_tags = sorted(self.all_tags.keys(), key=self.sorter) if tags is None: tags = self.ordered_tags select_item = None self.table.blockSignals(True) self.table.clear() self.table.setColumnCount(3) self.name_col = QTableWidgetItem(_('Tag')) self.table.setHorizontalHeaderItem(0, self.name_col) self.name_col.setIcon(self.up_arrow_icon) self.count_col = QTableWidgetItem(_('Count')) self.table.setHorizontalHeaderItem(1, self.count_col) self.count_col.setIcon(self.blank_icon) self.was_col = QTableWidgetItem(_('Was')) self.table.setHorizontalHeaderItem(2, self.was_col) self.count_col.setIcon(self.blank_icon) self.table.setRowCount(len(tags)) for row, tag in enumerate(tags): item = NameTableWidgetItem() item.set_is_deleted(self.all_tags[tag]['is_deleted']) _id = self.all_tags[tag]['key'] item.setData(Qt.UserRole, _id) item.set_initial_text(tag) if _id in self.to_rename: item.setText(self.to_rename[_id]) else: item.setText(tag) item.setFlags(item.flags() | Qt.ItemIsSelectable | Qt.ItemIsEditable) self.table.setItem(row, 0, item) if tag == tag_to_match: select_item = item item = CountTableWidgetItem(self.all_tags[tag]['count']) # only the name column can be selected item.setFlags(item.flags() & ~(Qt.ItemIsSelectable | Qt.ItemIsEditable)) self.table.setItem(row, 1, item) item = QTableWidgetItem() item.setFlags(item.flags() & ~(Qt.ItemIsSelectable | Qt.ItemIsEditable)) if _id in self.to_rename or _id in self.to_delete: item.setData(Qt.DisplayRole, tag) self.table.setItem(row, 2, item) self.table.blockSignals(False) return select_item def all_matching_clicked(self): for i in range(0, self.table.rowCount()): item = self.table.item(i, 0) tag = item.initial_text() self.all_tags[tag]['cur_name'] = item.text() self.all_tags[tag]['is_deleted'] = item.is_deleted search_for = icu_lower(unicode_type(self.search_box.text())) if len(search_for) == 0: self.fill_in_table(None, None) result = [] for k in self.ordered_tags: tag = self.all_tags[k] if (search_for in icu_lower(unicode_type(tag['cur_name'])) or search_for in icu_lower( unicode_type(self.original_names.get(tag['key'], '')))): result.append(k) self.fill_in_table(result, None) 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) def save_geometry(self): gprefs['tag_list_editor_table_widths'] = self.table_column_widths gprefs['tag_list_editor_dialog_geometry'] = bytearray( self.saveGeometry()) def finish_editing(self, item): if not item.text(): error_dialog( self, _('Item is blank'), _('An item cannot be set to nothing. Delete it instead.'), show=True) item.setText(item.initial_text()) return if item.text() != item.initial_text(): id_ = int(item.data(Qt.UserRole)) self.to_rename[id_] = unicode_type(item.text()) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, item.initial_text()) self.table.blockSignals(False) def undo_edit(self): indexes = self.table.selectionModel().selectedRows() if not indexes: error_dialog( self, _('No item selected'), _('You must select one item from the list of Available items.') ).exec_() return if not confirm(_('Do you really want to undo your changes?'), 'tag_list_editor_undo'): return self.table.blockSignals(True) for idx in indexes: row = idx.row() item = self.table.item(row, 0) item.setText(item.initial_text()) item.set_is_deleted(False) self.to_delete.discard(int(item.data(Qt.UserRole))) self.to_rename.pop(int(item.data(Qt.UserRole)), None) self.table.item(row, 2).setData(Qt.DisplayRole, '') self.table.blockSignals(False) def rename_tag(self): item = self.table.item(self.table.currentRow(), 0) self._rename_tag(item) def _rename_tag(self, item): if item is None: error_dialog( self, _('No item selected'), _('You must select one item from the list of Available items.') ).exec_() return col_zero_item = self.table.item(item.row(), 0) if col_zero_item.is_deleted: if not question_dialog( self, _('Undelete item?'), '<p>' + _('That item is deleted. Do you want to undelete it?') + '<br>'): return col_zero_item.set_is_deleted(False) self.to_delete.discard(int(col_zero_item.data(Qt.UserRole))) orig = self.table.item(col_zero_item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, '') self.table.blockSignals(False) else: self.table.editItem(item) def delete_tags(self): deletes = self.table.selectedItems() if not deletes: error_dialog( self, _('No items selected'), _('You must select at least one item from the list.')).exec_() return to_del = [] to_undel = [] for item in deletes: if item.is_deleted: to_undel.append(item) else: to_del.append(item) if to_del: ct = ', '.join([unicode_type(item.text()) for item in to_del]) if not confirm( '<p>' + _('Are you sure you want to delete the following items?') + '<br>' + ct, 'tag_list_editor_delete'): return if to_undel: ct = ', '.join([unicode_type(item.text()) for item in to_undel]) if not confirm( '<p>' + _('Are you sure you want to undelete the following items?') + '<br>' + ct, 'tag_list_editor_undelete'): return row = self.table.row(deletes[0]) for item in deletes: if item.is_deleted: item.set_is_deleted(False) self.to_delete.discard(int(item.data(Qt.UserRole))) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, '') self.table.blockSignals(False) else: id = int(item.data(Qt.UserRole)) self.to_delete.add(id) item.set_is_deleted(True) orig = self.table.item(item.row(), 2) self.table.blockSignals(True) orig.setData(Qt.DisplayRole, item.initial_text()) 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)) def header_clicked(self, idx): if idx == 0: self.do_sort_by_name() elif idx == 1: self.do_sort_by_count() else: self.do_sort_by_was() def do_sort_by_name(self): self.name_order = 1 if self.name_order == 0 else 0 self.table.sortByColumn(0, self.name_order) self.name_col.setIcon( self.down_arrow_icon if self.name_order else self.up_arrow_icon) self.count_col.setIcon(self.blank_icon) self.was_col.setIcon(self.blank_icon) def do_sort_by_count(self): self.count_order = 1 if self.count_order == 0 else 0 self.table.sortByColumn(1, self.count_order) self.count_col.setIcon( self.down_arrow_icon if self.count_order else self.up_arrow_icon) self.name_col.setIcon(self.blank_icon) self.was_col.setIcon(self.blank_icon) def do_sort_by_was(self): self.was_order = 1 if self.was_order == 0 else 0 self.table.sortByColumn(2, self.was_order) self.was_col.setIcon( self.down_arrow_icon if self.was_order else self.up_arrow_icon) self.name_col.setIcon(self.blank_icon) self.count_col.setIcon(self.blank_icon) def accepted(self): self.save_geometry()