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): 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

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

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