diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index 0582fe22f2..752257bc5a 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -7,14 +7,17 @@ __license__ = 'GPL v3' from PyQt5.Qt import (Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon, QDialogButtonBox, QFrame, QLabel, QTimer, QMenu, QApplication, - QByteArray, QItemDelegate) + 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.icu import sort_key +from calibre.utils.config import prefs +from calibre.utils.icu import sort_key, primary_contains, contains from polyglot.builtins import unicode_type +QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction' + class tableItem(QTableWidgetItem): @@ -92,23 +95,33 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): hh.sectionClicked.connect(self.do_sort) hh.setSortIndicatorShown(True) - # set up the search box + # set up the search & filter boxes self.find_box.initialize('manage_authors_search') - self.find_box.lineEdit().returnPressed.connect(self.do_find) + 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) - l = QLabel(self.table) - self.not_found_label = l + 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.StyledPanel) l.setAutoFillBackground(True) l.setText(_('No matches found')) l.setAlignment(Qt.AlignVCenter) l.resize(l.sizeHint()) - l.move(10,20) + l.move(10, 2) 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( @@ -131,6 +144,11 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): 'link': v['link']} self.edited_icon = QIcon(I('modified.png')) + 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 @@ -140,9 +158,19 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): def use_vl_changed(self, x): self.show_table(None, None, None) + def clear_filter(self): + self.filter_box.setText('') + self.show_table(None, None, None) + + def do_filter(self): + 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())] + filter_text = icu_lower(unicode_type(self.filter_box.text())) + auts_to_show = [] + for t in self.find_aut_func(use_virtual_library=self.apply_vl_checkbox.isChecked()): + if self.string_contains(filter_text, icu_lower(t[1])): + auts_to_show.append(t[0]) self.table.blockSignals(True) self.table.clear() self.table.setColumnCount(3) @@ -184,13 +212,13 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): self.table.setHorizontalHeaderLabels([_('Author'), _('Author sort'), _('Link')]) if self.last_sorted_by == 'sort': - self.author_sort_order = 1 if self.author_sort_order == 0 else 0 + 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 if self.author_order == 0 else 0 + self.author_order = 1 - self.author_order self.do_sort_by_author() else: - self.link_order = 1 if self.link_order == 0 else 0 + self.link_order = 1 - self.link_order self.do_sort_by_link() # Position on the desired item @@ -308,6 +336,11 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): 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 @@ -319,11 +352,13 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): 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_type(self.find_box.currentText())) - for i in range(0, self.table.rowCount()*2): + st = icu_lower(unicode_type(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() + 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_type(item.text())) @@ -405,8 +440,8 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): item = self.table.item(row, col) item.setIcon(self.edited_icon) if col == 1: - self.authors[id_]['sort'] = item.text() + self.authors[id_]['sort'] = unicode_type(item.text()) else: - self.authors[id_]['link'] = item.text() + self.authors[id_]['link'] = unicode_type(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 8268c551ec..541613fef0 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.ui +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.ui @@ -20,9 +20,9 @@ Manage authors - - - + + + &Search for: @@ -32,7 +32,7 @@ - + @@ -40,16 +40,19 @@ 0 - - - - - - F&ind + + true - + + + + S&earch + + + + Qt::Horizontal @@ -62,15 +65,49 @@ - + - <p>Show authors only if they appear in the + <p>Only show authors 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 + Only show authors in the current &virtual library + + + + + + + Filter &By: + + + filter_box + + + + + + + + 200 + 0 + + + + <p>Only show authors that contain the text in this box. + The match ignores case.</p> + + + true + + + + + + + Fi&lter diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index f6f2870c29..e034bd2b88 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -5,16 +5,19 @@ from __future__ import absolute_import, division, print_function, unicode_litera from PyQt5.Qt import (Qt, QDialog, QTableWidgetItem, QIcon, QByteArray, QSize, QDialogButtonBox, QTableWidget, QItemDelegate, QApplication, - pyqtSignal) + pyqtSignal, QAction, QFrame, QLabel, QTimer) from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.widgets import EnLineEdit from calibre.gui2 import question_dialog, error_dialog, gprefs -from calibre.utils.icu import sort_key +from calibre.utils.config import prefs +from calibre.utils.icu import sort_key, contains, primary_contains from polyglot.builtins import unicode_type +QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction' + class NameTableWidgetItem(QTableWidgetItem): @@ -137,6 +140,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): # Put the category name into the title bar t = self.windowTitle() + self.category_name = cat_name self.setWindowTitle(t + ' (' + cat_name + ')') # Remove help icon on title bar icon = self.windowIcon() @@ -161,16 +165,14 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.get_book_ids = get_book_ids self.text_before_editing = '' - # 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) + hh.setSectionsClickable(True) + hh.sectionClicked.connect(self.do_sort) + hh.setSortIndicatorShown(True) + + self.last_sorted_by = 'name' self.name_order = 0 self.count_order = 1 self.was_order = 1 @@ -180,12 +182,10 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.edit_delegate.editing_started.connect(self.start_editing) self.table.setItemDelegateForColumn(0, self.edit_delegate) - # 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) + if prefs['use_primary_find_in_search']: + self.string_contains = primary_contains + else: + self.string_contains = contains self.delete_button.clicked.connect(self.delete_tags) self.rename_button.clicked.connect(self.rename_tag) @@ -198,8 +198,35 @@ class TagListEditor(QDialog, Ui_TagListEditor): 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) + le = self.search_box.lineEdit() + ac = le.findChild(QAction, QT_HIDDEN_CLEAR_ACTION) + if ac is not None: + ac.triggered.connect(self.clear_search) + le.returnPressed.connect(self.do_search) + self.search_box.textChanged.connect(self.search_text_changed) + self.search_button.clicked.connect(self.do_search) self.search_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, 0) + 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.QueuedConnection) + + self.filter_box.initialize('tag_list_filter_box_' + cat_name) + le = self.filter_box.lineEdit() + ac = le.findChild(QAction, QT_HIDDEN_CLEAR_ACTION) + if ac is not None: + ac.triggered.connect(self.clear_filter) + le.returnPressed.connect(self.do_filter) + self.filter_button.clicked.connect(self.do_filter) self.apply_vl_checkbox.clicked.connect(self.vl_box_changed) @@ -213,17 +240,46 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.resize(self.sizeHint()+QSize(150, 100)) except: pass + # Add the data + self.search_item_row = -1 + self.fill_in_table(None, tag_to_match) def vl_box_changed(self): + self.search_item_row = -1 self.fill_in_table(None, None) + def do_search(self): + self.not_found_label.setVisible(False) + find_text = icu_lower(unicode_type(self.search_box.currentText())) + if not find_text: + return + for _ in range(0, self.table.rowCount()): + r = self.search_item_row = (self.search_item_row + 1) % self.table.rowCount() + if self.string_contains(find_text, + self.all_tags[self.ordered_tags[r]]['cur_name']): + self.table.setCurrentItem(self.table.item(r, 0)) + 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 search_text_changed(self): + self.search_item_row = -1 + + def clear_search(self): + self.search_item_row = -1 + self.search_box.setText('') + def fill_in_table(self, tags, tag_to_match): data = self.get_book_ids(self.apply_vl_checkbox.isChecked()) self.all_tags = {} + filter_text = icu_lower(unicode_type(self.filter_box.text())) 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 + if not filter_text or self.string_contains(filter_text, icu_lower(v)): + self.all_tags[v] = {'key': k, 'count': count, 'cur_name': v, + 'is_deleted': k in self.to_delete} + self.original_names[k] = v self.edit_delegate.set_completion_data(self.original_names.values()) self.ordered_tags = sorted(self.all_tags.keys(), key=self.sorter) @@ -234,18 +290,14 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.blockSignals(True) self.table.clear() self.table.setColumnCount(3) - self.name_col = QTableWidgetItem(_('Tag')) + self.name_col = QTableWidgetItem(self.category_name) 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']) @@ -260,7 +312,6 @@ class TagListEditor(QDialog, Ui_TagListEditor): 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)) @@ -271,27 +322,31 @@ class TagListEditor(QDialog, Ui_TagListEditor): 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) + if self.last_sorted_by == 'name': + self.table.sortByColumn(0, self.name_order) + elif self.last_sorted_by == 'count': + self.table.sortByColumn(1, self.count_order) + else: + self.table.sortByColumn(2, self.was_order) + + if select_item is not None: + self.table.setCurrentItem(select_item) + self.start_find_pos = select_item.row() + else: + self.table.setCurrentCell(0, 0) + self.start_find_pos = -1 + self.table.blockSignals(False) + + def not_found_label_timer_event(self): + self.not_found_label.setVisible(False) + + def clear_filter(self): + self.filter_box.setText('') + self.fill_in_table(None, None) + + def do_filter(self): + self.fill_in_table(None, None) def table_column_resized(self, col, old, new): self.table_column_widths = [] @@ -445,37 +500,23 @@ class TagListEditor(QDialog, Ui_TagListEditor): 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(self, section): + (self.do_sort_by_name, self.do_sort_by_count, self.do_sort_by_was)[section]() def do_sort_by_name(self): - self.name_order = 1 if self.name_order == 0 else 0 + self.name_order = 1 - self.name_order + self.last_sorted_by = 'name' 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.count_order = 1 - self.count_order + self.last_sorted_by = 'count' 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.was_order = 1 - self.was_order + self.last_sorted_by = 'count' 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() diff --git a/src/calibre/gui2/dialogs/tag_list_editor.ui b/src/calibre/gui2/dialogs/tag_list_editor.ui index bd2e157ae8..031e0e2d39 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.ui +++ b/src/calibre/gui2/dialogs/tag_list_editor.ui @@ -18,51 +18,60 @@ :/images/chapters.png:/images/chapters.png - - - - - - &Search for: - - - search_box - - - - - - - - 100 - 0 - - - - Search for an item in the Tag column - - - true - - - - - - - Display items containing the search string - - - &Find - - - - :/images/search.png:/images/search.png - - - - + + + + &Search for: + + + search_box + + - + + + + + 200 + 0 + + + + Search for an item in the first column + + + true + + + + + + + Find items containing the search string + + + S&earch + + + + :/images/search.png:/images/search.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + <p>Show items only if they appear in the @@ -70,7 +79,47 @@ not be forgotten.</p> - &Show only available items in current Virtual library + Only show items in the current &virtual library + + + + + + + &Filter by: + + + filter_box + + + + + + + + 200 + 0 + + + + Filter items using the text in this box + + + true + + + + + + + Show only items containing this text + + + F&ilter + + + + :/images/search.png:/images/search.png @@ -147,7 +196,7 @@ - + true @@ -160,7 +209,7 @@ - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok