Tag browser: Add options in Preferences->Look & feel->Tab browser to hide empty categories and als to have the Find in the Tb browser show all matches instead of jumping from match to match.

Tag browser: The Find function for searching for items in the Tag
browser can now do exact matching by using = as a prefix.

Manage authors dialog: Add a checkbox to show only authors from the
current Virtual library. Fixes #1878940 [[Enhancement] Add Show only available items in current Virtual library option in the Manage authors screen](https://bugs.launchpad.net/calibre/+bug/1878940)

Manage authors dialog: Show a modified icon next to entries that have
changed

Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
Kovid Goyal 2020-05-17 15:04:23 +05:30
commit db14695c6e
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
10 changed files with 261 additions and 129 deletions

View File

@ -150,6 +150,7 @@ def create_defs():
defs['ui_style'] = 'calibre' if iswindows or isosx else 'system' defs['ui_style'] = 'calibre' if iswindows or isosx else 'system'
defs['tag_browser_old_look'] = False defs['tag_browser_old_look'] = False
defs['tag_browser_hide_empty_categories'] = False defs['tag_browser_hide_empty_categories'] = False
defs['tag_browser_always_autocollapse'] = False
defs['book_list_tooltips'] = True defs['book_list_tooltips'] = True
defs['show_layout_buttons'] = False defs['show_layout_buttons'] = False
defs['bd_show_cover'] = True defs['bd_show_cover'] = True

View File

@ -47,10 +47,11 @@ class EditColumnDelegate(QItemDelegate):
class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog): 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) QDialog.__init__(self, parent)
Ui_EditAuthorsDialog.__init__(self) Ui_EditAuthorsDialog.__init__(self)
self.setupUi(self) self.setupUi(self)
# Remove help icon on title bar # Remove help icon on title bar
icon = self.windowIcon() icon = self.windowIcon()
self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) 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.Ok).setText(_('&OK'))
self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel')) self.buttonBox.button(QDialogButtonBox.Cancel).setText(_('&Cancel'))
self.buttonBox.accepted.connect(self.accepted) 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.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setColumnCount(3)
self.down_arrow_icon = QIcon(I('arrow-down.png')) self.down_arrow_icon = QIcon(I('arrow-down.png'))
self.up_arrow_icon = QIcon(I('arrow-up.png')) self.up_arrow_icon = QIcon(I('arrow-up.png'))
self.blank_icon = QIcon(I('blank.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() self.table.resizeColumnsToContents()
if self.table.columnWidth(2) < 200: if self.table.columnWidth(2) < 200:
self.table.setColumnWidth(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 # set up the cellChanged signal only after the table is filled
self.table.cellChanged.connect(self.cell_changed) 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.recalc_author_sort.clicked.connect(self.do_recalc_author_sort)
self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author) self.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author)
# Position on the desired item # Capture clicks on the horizontal header to sort the table columns
if select_item is not None: hh = self.table.horizontalHeader()
self.table.setCurrentItem(select_item) hh.setSectionsClickable(True)
self.table.editItem(select_item) hh.sectionClicked.connect(self.header_clicked)
self.start_find_pos = select_item.row() * 2 + select_item.column() hh.sectionResized.connect(self.table_column_resized)
else:
self.table.setCurrentCell(0, 0)
self.start_find_pos = -1
# set up the search box # set up the search box
self.find_box.initialize('manage_authors_search') self.find_box.initialize('manage_authors_search')
@ -165,7 +118,101 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setContextMenuPolicy(Qt.CustomContextMenu) self.table.setContextMenuPolicy(Qt.CustomContextMenu)
self.table.customContextMenuRequested.connect(self.show_context_menu) 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): def save_state(self):
self.table_column_widths = [] 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_table_widths'] = self.table_column_widths
gprefs['manage_authors_dialog_geometry'] = bytearray(self.saveGeometry()) 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): def resizeEvent(self, *args):
QDialog.resizeEvent(self, *args) QDialog.resizeEvent(self, *args)
if self.table_column_widths is not None: 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) ca.triggered.connect(self.copy_au_to_aus)
m.addSeparator() m.addSeparator()
ca = m.addAction(_("Show books by author in book list")) ca = m.addAction(_("Show books by author in book list"))
ca.triggered.connect(self.search) ca.triggered.connect(self.search_in_book_list)
else: else:
ca = m.addAction(_('Copy to author')) ca = m.addAction(_('Copy to author'))
ca.triggered.connect(self.copy_aus_to_au) ca.triggered.connect(self.copy_aus_to_au)
@ -224,7 +276,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
m.addMenu(case_menu) m.addMenu(case_menu)
m.exec_(self.table.mapToGlobal(point)) m.exec_(self.table.mapToGlobal(point))
def search(self): def search_in_book_list(self):
from calibre.gui2.ui import get_gui from calibre.gui2.ui import get_gui
row = self.context_item.row() row = self.context_item.row()
get_gui().search.set_search_string(self.table.item(row, 0).text()) 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.setVisible(True)
self.not_found_label_timer.start(1500) 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): def do_sort_by_author(self):
self.last_sorted_by = 'author'
self.author_order = 1 if self.author_order == 0 else 0 self.author_order = 1 if self.author_order == 0 else 0
self.table.sortByColumn(0, self.author_order) 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 self.auth_col.setIcon(self.down_arrow_icon if self.author_order
else self.up_arrow_icon) else self.up_arrow_icon)
self.aus_col.setIcon(self.blank_icon) self.aus_col.setIcon(self.blank_icon)
self.aul_col.setIcon(self.blank_icon)
def do_sort_by_author_sort(self): 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.author_sort_order = 1 if self.author_sort_order == 0 else 0
self.table.sortByColumn(1, self.author_sort_order) 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 self.aus_col.setIcon(self.down_arrow_icon if self.author_sort_order
else self.up_arrow_icon) else self.up_arrow_icon)
self.auth_col.setIcon(self.blank_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): def accepted(self):
self.save_state() self.save_state()
self.result = [] self.result = []
for row in range(0,self.table.rowCount()): for id_, v in self.authors.items():
id = int(self.table.item(row, 0).data(Qt.UserRole)) orig = self.original_authors[id_]
aut = unicode_type(self.table.item(row, 0).text()).strip() if orig != v:
sort = unicode_type(self.table.item(row, 1).text()).strip() self.result.append((id_, orig['name'], v['name'], v['sort'], v['link']))
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))
def do_recalc_author_sort(self): def do_recalc_author_sort(self):
self.table.cellChanged.disconnect() self.table.cellChanged.disconnect()
@ -347,8 +412,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.cellChanged.connect(self.cell_changed) self.table.cellChanged.connect(self.cell_changed)
def cell_changed(self, row, col): def cell_changed(self, row, col):
id_ = int(self.table.item(row, 0).data(Qt.UserRole))
if col == 0: if col == 0:
item = self.table.item(row, 0) item = self.table.item(row, 0)
item.setIcon(self.edited_icon)
aut = unicode_type(item.text()).strip() aut = unicode_type(item.text()).strip()
aut_list = string_to_authors(aut) aut_list = string_to_authors(aut)
if len(aut_list) != 1: if len(aut_list) != 1:
@ -356,10 +423,18 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
_('You cannot change an author to multiple authors.')).exec_() _('You cannot change an author to multiple authors.')).exec_()
aut = ' % '.join(aut_list) aut = ' % '.join(aut_list)
self.table.item(row, 0).setText(aut) self.table.item(row, 0).setText(aut)
self.authors[id_]['name'] = aut
c = self.table.item(row, 1) 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 item = c
else: else:
item = self.table.item(row, col) 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.setCurrentItem(item)
self.table.scrollToItem(item) self.table.scrollToItem(item)

View File

@ -62,6 +62,18 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QCheckBox" name="apply_vl_checkbox">
<property name="toolTip">
<string>&lt;p&gt;Show authors only if they appear in the
current Virtual library. Edits already done may be hidden but will
not be forgotten.&lt;/p&gt;</string>
</property>
<property name="text">
<string>&amp;Show only available items in current Virtual library</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -79,20 +91,6 @@
</item> </item>
<item> <item>
<layout class="QGridLayout"> <layout class="QGridLayout">
<item row="0" column="0">
<widget class="QPushButton" name="sort_by_author">
<property name="text">
<string>Sort by &amp;author</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="sort_by_author_sort">
<property name="text">
<string>Sort by author &amp;sort</string>
</property>
</widget>
</item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QPushButton" name="recalc_author_sort"> <widget class="QPushButton" name="recalc_author_sort">
<property name="toolTip"> <property name="toolTip">

View File

@ -281,7 +281,7 @@ class Quickview(QDialog, Ui_Quickview):
def item_doubleclicked(self, item): def item_doubleclicked(self, item):
tb = self.gui.stack.tb_widget tb = self.gui.stack.tb_widget
tb.set_focus_to_find_box() tb.set_focus_to_find_box()
tb.item_search.lineEdit().setText(self.current_key + ':' + item.text()) tb.item_search.lineEdit().setText(self.current_key + ':=' + item.text())
tb.do_find() tb.do_find()
def show_context_menu(self, point): def show_context_menu(self, point):

View File

@ -65,9 +65,9 @@
<item row="1" column="0" colspan="3"> <item row="1" column="0" colspan="3">
<widget class="QCheckBox" name="apply_vl_checkbox"> <widget class="QCheckBox" name="apply_vl_checkbox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;p&gt;Show items in the Available items box only if they appear in the <string>&lt;p&gt;Show items only if they appear in the
current Virtual library. Applied items not in the Virtual library will be marked current Virtual library. Edits already done may be hidden but will
&quot;not on any book&quot;.&lt;/p&gt;</string> not be forgotten.&lt;/p&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Show only available items in current Virtual library</string> <string>&amp;Show only available items in current Virtual library</string>

View File

@ -412,6 +412,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('row_numbers_in_book_list', gprefs) r('row_numbers_in_book_list', gprefs)
r('tag_browser_old_look', gprefs) r('tag_browser_old_look', gprefs)
r('tag_browser_hide_empty_categories', gprefs) r('tag_browser_hide_empty_categories', gprefs)
r('tag_browser_always_autocollapse', gprefs)
r('tag_browser_show_tooltips', gprefs) r('tag_browser_show_tooltips', gprefs)
r('bd_show_cover', gprefs) r('bd_show_cover', gprefs)
r('bd_overlay_cover_size', gprefs) r('bd_overlay_cover_size', gprefs)

View File

@ -995,6 +995,18 @@ then the tags will be displayed each on their own line.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="13" column="0" colspan="2">
<widget class="QCheckBox" name="opt_tag_browser_always_autocollapse">
<property name="toolTip">
<string>When checked, Find in the Tag browser will show all items
that match the search instead of the first one. If Hide empty categories is
also checked then only categories containing a matched item will be shown.</string>
</property>
<property name="text">
<string>Find &amp;shows all items that match in the Tag browser</string>
</property>
</widget>
</item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QComboBox" name="opt_tags_browser_partition_method"> <widget class="QComboBox" name="opt_tags_browser_partition_method">
<property name="toolTip"> <property name="toolTip">

View File

@ -125,6 +125,8 @@ class CreateVirtualLibrary(QDialog): # {{{
self.vl_text.textChanged.connect(self.search_text_changed) self.vl_text.textChanged.connect(self.search_text_changed)
la2.setBuddy(self.vl_text) la2.setBuddy(self.vl_text)
gl.addWidget(self.vl_text, 1, 1) gl.addWidget(self.vl_text, 1, 1)
# Trigger the textChanged signal to initialize the saved searches box
self.vl_text.setText(' ')
self.vl_text.setText(_build_full_search_string(self.gui)) self.vl_text.setText(_build_full_search_string(self.gui))
self.sl = sl = QLabel('<p>'+_('Create a Virtual library based on: ')+ self.sl = sl = QLabel('<p>'+_('Create a Virtual library based on: ')+
@ -213,10 +215,7 @@ class CreateVirtualLibrary(QDialog): # {{{
txt = '' txt = ''
else: else:
txt = '' txt = ''
if len(searches) > 1: self.saved_searches_label.setPlainText('\n'.join(searches))
self.saved_searches_label.setPlainText('\n'.join(searches))
else:
self.saved_searches_label.setPlainText('')
def name_text_edited(self, new_name): def name_text_edited(self, new_name):
self.new_name = unicode_type(new_name) self.new_name = unicode_type(new_name)

View File

@ -994,9 +994,19 @@ class TagsModel(QAbstractItemModel): # {{{
self.restriction_error.emit() self.restriction_error.emit()
if self.filter_categories_by: if self.filter_categories_by:
if self.filter_categories_by.startswith('='):
use_exact_match = True
filter_by = self.filter_categories_by[1:]
else:
use_exact_match = False
filter_by = self.filter_categories_by
for category in data.keys(): for category in data.keys():
data[category] = [t for t in data[category] if use_exact_match:
if lower(t.name).find(self.filter_categories_by) >= 0] data[category] = [t for t in data[category]
if lower(t.name) == filter_by]
else:
data[category] = [t for t in data[category]
if lower(t.name).find(filter_by) >= 0]
# Build a dict of the keys that have data # Build a dict of the keys that have data
tb_categories = self.db.field_metadata tb_categories = self.db.field_metadata

View File

@ -11,7 +11,7 @@ from functools import partial
from PyQt5.Qt import ( from PyQt5.Qt import (
Qt, QIcon, QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QLabel, QFrame, Qt, QIcon, QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QLabel, QFrame,
QTimer, QMenu, QActionGroup, QAction, QSizePolicy) QTimer, QMenu, QActionGroup, QAction, QSizePolicy, pyqtSignal)
from calibre.gui2 import error_dialog, question_dialog, gprefs from calibre.gui2 import error_dialog, question_dialog, gprefs
from calibre.gui2.widgets import HistoryLineEdit from calibre.gui2.widgets import HistoryLineEdit
@ -231,6 +231,15 @@ class TagBrowserMixin(object): # {{{
self.tags_view.recount() self.tags_view.recount()
self.user_categories_edited() 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): def do_tags_list_edit(self, tag, category):
''' '''
Open the 'manage_X' dialog where X == category. If tag is not None, the 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 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': if category == 'series':
key = lambda x:sort_key(title_sort(x)) key = lambda x:sort_key(title_sort(x))
else: else:
key = sort_key key = sort_key
d = TagListEditor(self, cat_name=db.field_metadata[category]['name'], 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_() d.exec_()
if d.result() == d.Accepted: if d.result() == d.Accepted:
to_rename = d.to_rename # dict of old id to new name 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 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: if editor.exec_() == editor.Accepted:
# Save and restore the current selections. Note that some changes # Save and restore the current selections. Note that some changes
# will cause sort orders to change, so don't bother with attempting # will cause sort orders to change, so don't bother with attempting
@ -404,6 +406,8 @@ class FindBox(HistoryLineEdit): # {{{
class TagBrowserBar(QWidget): # {{{ class TagBrowserBar(QWidget): # {{{
clear_find = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred)
@ -429,13 +433,16 @@ class TagBrowserBar(QWidget): # {{{
self.item_search.setSizeAdjustPolicy(self.item_search.AdjustToMinimumContentsLengthWithIcon) self.item_search.setSizeAdjustPolicy(self.item_search.AdjustToMinimumContentsLengthWithIcon)
self.item_search.initialize('tag_browser_search') self.item_search.initialize('tag_browser_search')
self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive) self.item_search.completer().setCaseSensitivity(Qt.CaseSensitive)
self.item_search.setToolTip(_( self.item_search.setToolTip(
'Search for items. This is a "contains" search; items containing the\n' '<p>' +_(
'text anywhere in the name will be found. You can limit the search\n' 'Search for items. If the text begins with equals (=) the search is '
'to particular categories using syntax similar to search. For example,\n' 'exact match, otherwise it is "contains" finding items containing '
'tags:foo will find foo in any tag, but not in authors etc. Entering\n' 'the text anywhere in the item name. Both exact and contains '
'*foo will filter all categories at once, showing only those items\n' 'searches ignore case. You can limit the search to particular '
'containing the text "foo"')) 'categories using syntax similar to search. For example, '
'tags:foo will find foo in any tag, but not in authors etc. Entering '
'*foo will collapse all categories then showing only those categories '
'with items containing the text "foo"') + '</p')
ac = QAction(parent) ac = QAction(parent)
parent.addAction(ac) parent.addAction(ac)
parent.keyboard.register_shortcut('tag browser find box', parent.keyboard.register_shortcut('tag browser find box',
@ -472,6 +479,7 @@ class TagBrowserBar(QWidget): # {{{
self.item_search.setCurrentIndex(0) self.item_search.setCurrentIndex(0)
self.item_search.setCurrentText('') self.item_search.setCurrentText('')
self.toggle_search_button.click() self.toggle_search_button.click()
self.clear_find.emit()
def set_focus_to_find_box(self): def set_focus_to_find_box(self):
self.toggle_search_button.setChecked(True) self.toggle_search_button.setChecked(True)
@ -517,6 +525,7 @@ class TagBrowserWidget(QFrame): # {{{
# Set up the find box & button # Set up the find box & button
self.tb_bar = tbb = TagBrowserBar(self) self.tb_bar = tbb = TagBrowserBar(self)
tbb.clear_find.connect(self.reset_find)
self.alter_tb, self.item_search, self.search_button = tbb.alter_tb, tbb.item_search, tbb.search_button self.alter_tb, self.item_search, self.search_button = tbb.alter_tb, tbb.item_search, tbb.search_button
self.toggle_search_button = tbb.toggle_search_button self.toggle_search_button = tbb.toggle_search_button
self._layout.addWidget(tbb) self._layout.addWidget(tbb)
@ -636,11 +645,42 @@ class TagBrowserWidget(QFrame): # {{{
def find_text(self): def find_text(self):
return unicode_type(self.item_search.currentText()).strip() return unicode_type(self.item_search.currentText()).strip()
def reset_find(self):
model = self.tags_view.model()
if model.get_categories_filter():
model.set_categories_filter(None)
self.tags_view.recount()
self.current_find_position = None
def find(self): def find(self):
model = self.tags_view.model() model = self.tags_view.model()
model.clear_boxed() model.clear_boxed()
txt = self.find_text
# When a key is specified don't use the auto-collapsing search.
# A colon separates the lookup key from the search string.
# A leading colon says not to use autocollapsing search but search all keys
txt = self.find_text
colon = txt.find(':')
if colon >= 0:
key = self._parent.library_view.model().db.\
field_metadata.search_term_to_field_key(txt[:colon])
if key in self._parent.library_view.model().db.field_metadata:
txt = txt[colon+1:]
else:
key = ''
txt = txt[1:] if colon == 0 else txt
else:
key = None
# key is None indicates that no colon was found.
# key == '' means either a leading : was found or the key is invalid
# At this point the txt might have a leading =, in which case do an
# exact match search
if (gprefs.get('tag_browser_always_autocollapse', False) and
key is None and not txt.startswith('*')):
txt = '*' + txt
if txt.startswith('*'): if txt.startswith('*'):
self.tags_view.collapseAll() self.tags_view.collapseAll()
model.set_categories_filter(txt[1:]) model.set_categories_filter(txt[1:])
@ -659,18 +699,14 @@ class TagBrowserWidget(QFrame): # {{{
self.search_button.setFocus(True) self.search_button.setFocus(True)
self.item_search.lineEdit().blockSignals(False) self.item_search.lineEdit().blockSignals(False)
key = None if txt.startswith('='):
colon = txt.find(':') if len(txt) > 2 else 0 equals_match = True
if colon > 0: txt = txt[1:]
key = self._parent.library_view.model().db.\ else:
field_metadata.search_term_to_field_key(txt[:colon]) equals_match = False
if key in self._parent.library_view.model().db.field_metadata:
txt = txt[colon+1:]
else:
key = None
self.current_find_position = \ self.current_find_position = \
model.find_item_node(key, txt, self.current_find_position) model.find_item_node(key, txt, self.current_find_position,
equals_match=equals_match)
if self.current_find_position: if self.current_find_position:
self.tags_view.show_item_at_path(self.current_find_position, box=True) self.tags_view.show_item_at_path(self.current_find_position, box=True)