Tag browser: Allow Manage Tags to open to a selected letter when clicking on a letter. Fixes #1880175 [[Enhancement] Allow Manage Tags to open to a selected letter vs selected tag](https://bugs.launchpad.net/calibre/+bug/1880175)

Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
Kovid Goyal 2020-05-23 20:00:48 +05:30
commit 64d4b1c52d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 123 additions and 69 deletions

View File

@ -13,7 +13,8 @@ from calibre.ebooks.metadata import author_to_author_sort, string_to_authors
from calibre.gui2 import error_dialog, gprefs
from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog
from calibre.utils.config import prefs
from calibre.utils.icu import sort_key, primary_contains, contains
from calibre.utils.config_base import tweaks
from calibre.utils.icu import sort_key, primary_contains, contains, primary_startswith
from polyglot.builtins import unicode_type
QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction'
@ -50,7 +51,8 @@ class EditColumnDelegate(QItemDelegate):
class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
def __init__(self, parent, db, id_to_select, select_sort, select_link, find_aut_func):
def __init__(self, parent, db, id_to_select, select_sort, select_link,
find_aut_func, is_first_letter=False):
QDialog.__init__(self, parent)
Ui_EditAuthorsDialog.__init__(self)
self.setupUi(self)
@ -153,19 +155,19 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.author_order = 1
self.author_sort_order = 0
self.link_order = 1
self.show_table(id_to_select, select_sort, select_link)
self.show_table(id_to_select, select_sort, select_link, is_first_letter)
def use_vl_changed(self, x):
self.show_table(None, None, None)
self.show_table(None, None, None, False)
def clear_filter(self):
self.filter_box.setText('')
self.show_table(None, None, None)
self.show_table(None, None, None, False)
def do_filter(self):
self.show_table(None, None, None)
self.show_table(None, None, None, False)
def show_table(self, id_to_select, select_sort, select_link):
def show_table(self, id_to_select, select_sort, select_link, is_first_letter):
filter_text = icu_lower(unicode_type(self.filter_box.text()))
auts_to_show = []
for t in self.find_aut_func(use_virtual_library=self.apply_vl_checkbox.isChecked()):
@ -176,7 +178,6 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.table.setColumnCount(3)
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:
@ -198,14 +199,6 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
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):
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))
@ -222,10 +215,28 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
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()
if id_to_select:
select_item = None
use_as = tweaks['categories_use_field_for_author_name']
for row in range(0, len(auts_to_show)):
name_item = self.table.item(row, 1) if use_as else self.table.item(row, 0)
if is_first_letter:
if primary_startswith(name_item.text(), id_to_select):
select_item = self.table.item(row, 1)
break
elif id_to_select == self.table.item(row, 0).data(Qt.UserRole):
if select_sort:
select_item = self.table.item(row, 1)
elif select_link:
select_item = self.table.item(row, 2)
else:
select_item = name_item
break
if select_item:
self.table.setCurrentItem(select_item)
if select_sort or select_link:
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

View File

@ -13,7 +13,7 @@ from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.widgets import EnLineEdit
from calibre.gui2 import question_dialog, error_dialog, gprefs
from calibre.utils.config import prefs
from calibre.utils.icu import sort_key, contains, primary_contains
from calibre.utils.icu import sort_key, contains, primary_contains, primary_startswith
from polyglot.builtins import unicode_type
QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction'
@ -132,7 +132,8 @@ class EditColumnDelegate(QItemDelegate):
class TagListEditor(QDialog, Ui_TagListEditor):
def __init__(self, window, cat_name, tag_to_match, get_book_ids, sorter):
def __init__(self, window, cat_name, tag_to_match, get_book_ids, sorter,
ttm_is_first_letter=False):
QDialog.__init__(self, window)
Ui_TagListEditor.__init__(self)
self.setupUi(self)
@ -188,6 +189,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.string_contains = contains
self.delete_button.clicked.connect(self.delete_tags)
self.table.delete_pressed.connect(self.delete_pressed)
self.rename_button.clicked.connect(self.rename_tag)
self.undo_button.clicked.connect(self.undo_edit)
self.table.itemDoubleClicked.connect(self._rename_tag)
@ -242,11 +244,11 @@ class TagListEditor(QDialog, Ui_TagListEditor):
pass
# Add the data
self.search_item_row = -1
self.fill_in_table(None, tag_to_match)
self.fill_in_table(None, tag_to_match, ttm_is_first_letter)
def vl_box_changed(self):
self.search_item_row = -1
self.fill_in_table(None, None)
self.fill_in_table(None, None, False)
def do_search(self):
self.not_found_label.setVisible(False)
@ -271,7 +273,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.search_item_row = -1
self.search_box.setText('')
def fill_in_table(self, tags, tag_to_match):
def fill_in_table(self, tags, tag_to_match, ttm_is_first_letter):
data = self.get_book_ids(self.apply_vl_checkbox.isChecked())
self.all_tags = {}
filter_text = icu_lower(unicode_type(self.filter_box.text()))
@ -310,8 +312,12 @@ class TagListEditor(QDialog, Ui_TagListEditor):
item.setText(tag)
item.setFlags(item.flags() | Qt.ItemIsSelectable | Qt.ItemIsEditable)
self.table.setItem(row, 0, item)
if tag == tag_to_match:
select_item = item
if select_item is None:
if ttm_is_first_letter:
if primary_startswith(tag, tag_to_match):
select_item = item
elif tag == tag_to_match:
select_item = item
item = CountTableWidgetItem(self.all_tags[tag]['count'])
# only the name column can be selected
item.setFlags(item.flags() & ~(Qt.ItemIsSelectable|Qt.ItemIsEditable))
@ -343,10 +349,10 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def clear_filter(self):
self.filter_box.setText('')
self.fill_in_table(None, None)
self.fill_in_table(None, None, False)
def do_filter(self):
self.fill_in_table(None, None)
self.fill_in_table(None, None, False)
def table_column_resized(self, col, old, new):
self.table_column_widths = []
@ -452,6 +458,10 @@ class TagListEditor(QDialog, Ui_TagListEditor):
else:
self.table.editItem(item)
def delete_pressed(self):
if self.table.currentColumn() == 0:
self.delete_tags()
def delete_tags(self):
deletes = self.table.selectedItems()
if not deletes:
@ -460,41 +470,26 @@ class TagListEditor(QDialog, Ui_TagListEditor):
return
to_del = []
to_undel = []
for item in deletes:
if item.is_deleted:
to_undel.append(item)
else:
if not item.is_deleted:
to_del.append(item)
if to_del:
ct = ', '.join([unicode_type(item.text()) for item in to_del])
if not confirm(
'<p>'+_('Are you sure you want to delete the following items?')+'<br>'+ct,
'tag_list_editor_delete'):
return
if to_undel:
ct = ', '.join([unicode_type(item.text()) for item in to_undel])
if not confirm(
'<p>'+_('Are you sure you want to undelete the following items?')+'<br>'+ct,
'tag_list_editor_undelete'):
return
row = self.table.row(deletes[0])
self.table.blockSignals(True)
for item in deletes:
if item.is_deleted:
item.set_is_deleted(False)
self.to_delete.discard(int(item.data(Qt.UserRole)))
orig = self.table.item(item.row(), 2)
self.table.blockSignals(True)
orig.setData(Qt.DisplayRole, '')
self.table.blockSignals(False)
else:
id = int(item.data(Qt.UserRole))
self.to_delete.add(id)
item.set_is_deleted(True)
orig = self.table.item(item.row(), 2)
self.table.blockSignals(True)
orig.setData(Qt.DisplayRole, item.initial_text())
self.table.blockSignals(False)
id_ = int(item.data(Qt.UserRole))
self.to_delete.add(id_)
item.set_is_deleted(True)
orig = self.table.item(item.row(), 2)
orig.setData(Qt.DisplayRole, item.initial_text())
self.table.blockSignals(False)
if row >= self.table.rowCount():
row = self.table.rowCount() - 1
if row >= 0:

View File

@ -197,7 +197,7 @@
</layout>
</item>
<item row="2" column="1" colspan="4">
<widget class="QTableWidget" name="table">
<widget class="TleTableWidget" name="table">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
@ -224,6 +224,12 @@
<extends>QLineEdit</extends>
<header>calibre/gui2/widgets.h</header>
</customwidget>
<customwidget>
<class>TleTableWidget</class>
<extends>QTableWidget</extends>
<header>calibre/gui2/dialogs/tag_list_editor_table_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../../resources/images.qrc"/>

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2008, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
from PyQt5.Qt import (Qt, QTableWidget, pyqtSignal)
class TleTableWidget(QTableWidget):
delete_pressed = pyqtSignal()
def __init__(self, parent=None):
QTableWidget.__init__(self, parent=parent)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Delete:
self.delete_pressed.emit()
event.accept()
return
return QTableWidget.keyPressEvent(self, event)

View File

@ -240,7 +240,7 @@ class TagBrowserMixin(object): # {{{
result = None
return result
def do_tags_list_edit(self, tag, category):
def do_tags_list_edit(self, tag, category, is_first_letter=False):
'''
Open the 'manage_X' dialog where X == category. If tag is not None, the
dialog will position the editor on that item.
@ -255,7 +255,7 @@ class TagBrowserMixin(object): # {{{
d = TagListEditor(self, cat_name=db.field_metadata[category]['name'],
tag_to_match=tag,
get_book_ids=partial(self.get_book_ids, db=db, category=category),
sorter=key)
sorter=key, ttm_is_first_letter=is_first_letter)
d.exec_()
if d.result() == d.Accepted:
to_rename = d.to_rename # dict of old id to new name
@ -356,14 +356,16 @@ class TagBrowserMixin(object): # {{{
self.library_view.select_rows(ids)
# refreshing the tags view happens at the emit()/call() site
def do_author_sort_edit(self, parent, id_, select_sort=True, select_link=False):
def do_author_sort_edit(self, parent, id_, select_sort=True,
select_link=False, is_first_letter=False):
'''
Open the manage authors dialog
'''
db = self.library_view.model().db
editor = EditAuthorsDialog(parent, db, id_, select_sort, select_link,
partial(self.get_book_ids, db=db, category='authors'))
partial(self.get_book_ids, db=db, category='authors'),
is_first_letter)
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

View File

@ -145,10 +145,10 @@ class TagsView(QTreeView): # {{{
del_item_from_user_cat = pyqtSignal(object, object, object)
add_item_to_user_cat = pyqtSignal(object, object, object)
add_subcategory = pyqtSignal(object)
tags_list_edit = pyqtSignal(object, object)
tags_list_edit = pyqtSignal(object, object, object)
saved_search_edit = pyqtSignal(object)
rebuild_saved_searches = pyqtSignal()
author_sort_edit = pyqtSignal(object, object, object, object)
author_sort_edit = pyqtSignal(object, object, object, object, object)
tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal()
drag_drop_finished = pyqtSignal(object)
@ -390,7 +390,7 @@ class TagsView(QTreeView): # {{{
def context_menu_handler(self, action=None, category=None,
key=None, index=None, search_state=None,
use_vl=None):
use_vl=None, is_first_letter=False):
if not action:
return
try:
@ -447,7 +447,7 @@ class TagsView(QTreeView): # {{{
self.tag_item_delete.emit(key, index.id, index.original_name, None)
return
if action == 'open_editor':
self.tags_list_edit.emit(category, key)
self.tags_list_edit.emit(category, key, is_first_letter)
return
if action == 'manage_categories':
self.edit_user_category.emit(category)
@ -492,11 +492,14 @@ class TagsView(QTreeView): # {{{
if action == 'manage_searches':
self.saved_search_edit.emit(category)
return
if action == 'edit_authors':
self.author_sort_edit.emit(self, index, False, False, is_first_letter)
return
if action == 'edit_author_sort':
self.author_sort_edit.emit(self, index, True, False)
self.author_sort_edit.emit(self, index, True, False, is_first_letter)
return
if action == 'edit_author_link':
self.author_sort_edit.emit(self, index, False, True)
self.author_sort_edit.emit(self, index, False, True, False)
return
reset_filter_categories = True
@ -557,9 +560,9 @@ class TagsView(QTreeView): # {{{
if index.isValid():
item = index.data(Qt.UserRole)
tag = None
tag_item = item
if item.type == TagTreeItem.TAG:
tag_item = item
tag = item.tag
while item.type != TagTreeItem.CATEGORY:
item = item.parent
@ -726,13 +729,29 @@ class TagsView(QTreeView): # {{{
self.context_menu.addSeparator()
if key in ['tags', 'publisher', 'series'] or (
self.db.field_metadata[key]['is_custom'] and self.db.field_metadata[key]['datatype'] != 'composite'):
self.context_menu.addAction(_('Manage %s')%category,
if tag_item.type == TagTreeItem.CATEGORY and tag_item.temporary:
self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='open_editor',
category=tag_item.name,
key=key, is_first_letter=True))
else:
self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='open_editor',
category=tag.original_name if tag else None,
key=key))
elif key == 'authors':
self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='edit_author_sort'))
if tag_item.type == TagTreeItem.CATEGORY:
if tag_item.temporary:
self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='edit_authors',
index=tag_item.name, is_first_letter=True))
else:
self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='edit_authors'))
else:
self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='edit_authors',
index=tag.id))
elif key == 'search':
self.context_menu.addAction(_('Manage Saved searches'),
partial(self.context_menu_handler, action='manage_searches',