mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
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:
commit
db14695c6e
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -62,6 +62,18 @@
|
|||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="apply_vl_checkbox">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><p>Show authors only if they appear in the
|
||||||
|
current Virtual library. Edits already done may be hidden but will
|
||||||
|
not be forgotten.</p></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&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 &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 &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">
|
||||||
|
@ -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):
|
||||||
|
@ -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><p>Show items in the Available items box only if they appear in the
|
<string><p>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
|
||||||
"not on any book".</p></string>
|
not be forgotten.</p></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>&Show only available items in current Virtual library</string>
|
<string>&Show only available items in current Virtual library</string>
|
||||||
|
@ -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)
|
||||||
|
@ -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 &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">
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user