Enhancement #1878940: allow restricting manage authors to the current virtual library.

Other improvements:
- Get rid of sort buttons, instead sorting by clicking on the header
- Support sorting by the author link column
- Add an "edited" icon to any cell that has been changed
This commit is contained in:
Charles Haley 2020-05-16 15:39:00 +01:00
parent 2ff634034e
commit b2e108042b
4 changed files with 177 additions and 102 deletions

View File

@ -47,10 +47,11 @@ class EditColumnDelegate(QItemDelegate):
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)
Ui_EditAuthorsDialog.__init__(self)
self.setupUi(self)
# Remove help icon on title bar
icon = self.windowIcon()
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.Cancel).setText(_('&Cancel'))
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.setColumnCount(3)
self.down_arrow_icon = QIcon(I('arrow-down.png'))
self.up_arrow_icon = QIcon(I('arrow-up.png'))
self.blank_icon = QIcon(I('blank.png'))
self.auth_col = QTableWidgetItem(_('Author'))
self.table.setHorizontalHeaderItem(0, self.auth_col)
self.auth_col.setIcon(self.blank_icon)
self.aus_col = QTableWidgetItem(_('Author sort'))
self.table.setHorizontalHeaderItem(1, self.aus_col)
self.aus_col.setIcon(self.up_arrow_icon)
self.aul_col = QTableWidgetItem(_('Link'))
self.table.setHorizontalHeaderItem(2, self.aul_col)
self.aus_col.setIcon(self.blank_icon)
# Add the data
self.authors = {}
auts = db.get_authors_with_ids()
self.table.setRowCount(len(auts))
select_item = None
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()
if self.table.columnWidth(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
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.auth_sort_to_author.clicked.connect(self.do_auth_sort_to_author)
# Position on the desired item
if select_item is not None:
self.table.setCurrentItem(select_item)
self.table.editItem(select_item)
self.start_find_pos = select_item.row() * 2 + select_item.column()
else:
self.table.setCurrentCell(0, 0)
self.start_find_pos = -1
# 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)
# set up the search box
self.find_box.initialize('manage_authors_search')
@ -165,7 +118,101 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
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):
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_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):
QDialog.resizeEvent(self, *args)
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)
m.addSeparator()
ca = m.addAction(_("Show books by author in book list"))
ca.triggered.connect(self.search)
ca.triggered.connect(self.search_in_book_list)
else:
ca = m.addAction(_('Copy to author'))
ca.triggered.connect(self.copy_aus_to_au)
@ -224,7 +276,7 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
m.addMenu(case_menu)
m.exec_(self.table.mapToGlobal(point))
def search(self):
def search_in_book_list(self):
from calibre.gui2.ui import get_gui
row = self.context_item.row()
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_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):
self.last_sorted_by = 'author'
self.author_order = 1 if self.author_order == 0 else 0
self.table.sortByColumn(0, self.author_order)
self.sort_by_author.setChecked(True)
self.sort_by_author_sort.setChecked(False)
self.auth_col.setIcon(self.down_arrow_icon if self.author_order
else self.up_arrow_icon)
self.aus_col.setIcon(self.blank_icon)
self.aul_col.setIcon(self.blank_icon)
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.table.sortByColumn(1, self.author_sort_order)
self.sort_by_author.setChecked(False)
self.sort_by_author_sort.setChecked(True)
self.aus_col.setIcon(self.down_arrow_icon if self.author_sort_order
else self.up_arrow_icon)
self.auth_col.setIcon(self.blank_icon)
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):
self.save_state()
self.result = []
for row in range(0,self.table.rowCount()):
id = int(self.table.item(row, 0).data(Qt.UserRole))
aut = unicode_type(self.table.item(row, 0).text()).strip()
sort = unicode_type(self.table.item(row, 1).text()).strip()
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))
for id_, v in self.authors.items():
orig = self.original_authors[id_]
if orig != v:
self.result.append((id_, orig['name'], v['name'], v['sort'], v['link']))
def do_recalc_author_sort(self):
self.table.cellChanged.disconnect()
@ -347,8 +412,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.cellChanged.connect(self.cell_changed)
def cell_changed(self, row, col):
id_ = int(self.table.item(row, 0).data(Qt.UserRole))
if col == 0:
item = self.table.item(row, 0)
item.setIcon(self.edited_icon)
aut = unicode_type(item.text()).strip()
aut_list = string_to_authors(aut)
if len(aut_list) != 1:
@ -356,10 +423,18 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
_('You cannot change an author to multiple authors.')).exec_()
aut = ' % '.join(aut_list)
self.table.item(row, 0).setText(aut)
self.authors[id_]['name'] = aut
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
else:
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.scrollToItem(item)

View File

@ -62,6 +62,18 @@
</property>
</spacer>
</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>
</item>
<item>
@ -79,20 +91,6 @@
</item>
<item>
<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">
<widget class="QPushButton" name="recalc_author_sort">
<property name="toolTip">

View File

@ -65,9 +65,9 @@
<item row="1" column="0" colspan="3">
<widget class="QCheckBox" name="apply_vl_checkbox">
<property name="toolTip">
<string>&lt;p&gt;Show items in the Available items box only if they appear in the
current Virtual library. Applied items not in the Virtual library will be marked
&quot;not on any book&quot;.&lt;/p&gt;</string>
<string>&lt;p&gt;Show items 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>

View File

@ -231,6 +231,15 @@ class TagBrowserMixin(object): # {{{
self.tags_view.recount()
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):
'''
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
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':
key = lambda x:sort_key(title_sort(x))
else:
key = sort_key
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_()
if d.result() == d.Accepted:
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
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:
# Save and restore the current selections. Note that some changes
# will cause sort orders to change, so don't bother with attempting