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<er
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