Manage tags dialog: Searching now shows all matching tags and there is an undo button to undo changes. Fixes #1743896 [[Enhancement] Allow filtering in Tag manager search](https://bugs.launchpad.net/calibre/+bug/1743896)

Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
Kovid Goyal 2018-03-28 18:09:48 +05:30
commit c1649b383e
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 119 additions and 75 deletions

View File

@ -6,17 +6,16 @@ from PyQt5.Qt import (Qt, QDialog, QTableWidgetItem, QIcon, QByteArray, QSize,
from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2 import question_dialog, error_dialog, info_dialog, gprefs from calibre.gui2 import question_dialog, error_dialog, gprefs
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
class NameTableWidgetItem(QTableWidgetItem): class NameTableWidgetItem(QTableWidgetItem):
def __init__(self, txt): def __init__(self):
QTableWidgetItem.__init__(self, txt) QTableWidgetItem.__init__(self)
self.initial_value = txt self.initial_value = ''
self.current_value = txt self.current_value = ''
self.previous_value = txt
self.is_deleted = False self.is_deleted = False
def data(self, role): def data(self, role):
@ -34,23 +33,22 @@ class NameTableWidgetItem(QTableWidgetItem):
self.setIcon(QIcon(I('trash.png'))) self.setIcon(QIcon(I('trash.png')))
else: else:
self.setIcon(QIcon(None)) self.setIcon(QIcon(None))
self.current_value = self.previous_value = self.initial_value self.current_value = self.initial_value
self.is_deleted = to_what self.is_deleted = to_what
def setData(self, role, data): def setData(self, role, data):
if role == Qt.EditRole: if role == Qt.EditRole:
self.previous_value = self.current_value
self.current_value = data self.current_value = data
QTableWidgetItem.setData(self, role, data) QTableWidgetItem.setData(self, role, data)
def text(self): def set_initial_text(self, txt):
return self.current_value self.initial_value = txt
def initial_text(self): def initial_text(self):
return self.initial_value return self.initial_value
def previous_text(self): def text(self):
return self.previous_value return self.current_value
def setText(self, txt): def setText(self, txt):
self.current_value = txt self.current_value = txt
@ -89,14 +87,6 @@ class EditColumnDelegate(QItemDelegate):
if item.is_deleted: if item.is_deleted:
return None return None
return QItemDelegate.createEditor(self, parent, option, index) return QItemDelegate.createEditor(self, parent, option, index)
if not confirm(
_('Do you want to undo your changes?'),
'tag_list_editor_undo'):
return
item.setText(item.initial_text())
self.table.blockSignals(True)
self.table.item(index.row(), 2).setData(Qt.DisplayRole, '')
self.table.blockSignals(False)
class TagListEditor(QDialog, Ui_TagListEditor): class TagListEditor(QDialog, Ui_TagListEditor):
@ -125,31 +115,19 @@ class TagListEditor(QDialog, Ui_TagListEditor):
# initialization # initialization
self.to_rename = {} self.to_rename = {}
self.to_delete = set([]) self.to_delete = set([])
self.original_names = {}
self.all_tags = {} self.all_tags = {}
self.counts = {} self.original_names = {}
for k,v,count in data: for k,v,count in data:
self.all_tags[v] = k self.all_tags[v] = {'key': k, 'count': count, 'cur_name': v, 'is_deleted': False}
self.counts[v] = count
self.original_names[k] = v self.original_names[k] = v
self.ordered_tags = sorted(self.all_tags.keys(), key=sorter)
# Set up the column headings # Set up the column headings
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.table.setColumnCount(3)
self.name_col = QTableWidgetItem(_('Tag'))
self.table.setHorizontalHeaderItem(0, self.name_col)
self.name_col.setIcon(self.up_arrow_icon)
self.count_col = QTableWidgetItem(_('Count'))
self.table.setHorizontalHeaderItem(1, self.count_col)
self.count_col.setIcon(self.blank_icon)
self.was_col = QTableWidgetItem(_('Was'))
self.table.setHorizontalHeaderItem(2, self.was_col)
self.count_col.setIcon(self.blank_icon)
# Capture clicks on the horizontal header to sort the table columns # Capture clicks on the horizontal header to sort the table columns
hh = self.table.horizontalHeader() hh = self.table.horizontalHeader()
hh.setSectionsClickable(True) hh.setSectionsClickable(True)
@ -162,23 +140,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.setItemDelegate(EditColumnDelegate(self.table)) self.table.setItemDelegate(EditColumnDelegate(self.table))
# Add the data # Add the data
select_item = None select_item = self.fill_in_table(self.ordered_tags, tag_to_match)
self.table.setRowCount(len(self.all_tags))
for row,tag in enumerate(sorted(self.all_tags.keys(), key=sorter)):
item = NameTableWidgetItem(tag)
item.setData(Qt.UserRole, self.all_tags[tag])
item.setFlags(item.flags() | Qt.ItemIsSelectable | Qt.ItemIsEditable)
self.table.setItem(row, 0, item)
if tag == tag_to_match:
select_item = item
item = CountTableWidgetItem(self.counts[tag])
# only the name column can be selected
item.setFlags(item.flags() & ~(Qt.ItemIsSelectable|Qt.ItemIsEditable))
self.table.setItem(row, 1, item)
item = QTableWidgetItem()
# item.setFlags(item.flags() & ~(Qt.ItemIsSelectable|Qt.ItemIsEditable))
item.setFlags((item.flags() | Qt.ItemIsEditable) & ~Qt.ItemIsSelectable)
self.table.setItem(row, 2, item)
# Scroll to the selected item if there is one # Scroll to the selected item if there is one
if select_item is not None: if select_item is not None:
@ -186,6 +148,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.delete_button.clicked.connect(self.delete_tags) self.delete_button.clicked.connect(self.delete_tags)
self.rename_button.clicked.connect(self.rename_tag) self.rename_button.clicked.connect(self.rename_tag)
self.undo_button.clicked.connect(self.undo_edit)
self.table.itemDoubleClicked.connect(self._rename_tag) self.table.itemDoubleClicked.connect(self._rename_tag)
self.table.itemChanged.connect(self.finish_editing) self.table.itemChanged.connect(self.finish_editing)
@ -194,14 +157,11 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.buttonBox.accepted.connect(self.accepted) self.buttonBox.accepted.connect(self.accepted)
self.search_box.initialize('tag_list_search_box_' + cat_name) self.search_box.initialize('tag_list_search_box_' + cat_name)
self.search_box.editTextChanged.connect(self.find_text_changed) self.search_button.clicked.connect(self.all_matching_clicked)
self.search_button.clicked.connect(self.search_clicked)
self.search_button.setDefault(True) self.search_button.setDefault(True)
self.table.setEditTriggers(QTableWidget.EditKeyPressed) self.table.setEditTriggers(QTableWidget.EditKeyPressed)
self.start_find_pos = -1
try: try:
geom = gprefs.get('tag_list_editor_dialog_geometry', None) geom = gprefs.get('tag_list_editor_dialog_geometry', None)
if geom is not None: if geom is not None:
@ -211,25 +171,61 @@ class TagListEditor(QDialog, Ui_TagListEditor):
except: except:
pass pass
def find_text_changed(self): def fill_in_table(self, tags, tag_to_match):
self.start_find_pos = -1 select_item = None
self.table.blockSignals(True)
self.table.clear()
self.table.setColumnCount(3)
self.name_col = QTableWidgetItem(_('Tag'))
self.table.setHorizontalHeaderItem(0, self.name_col)
self.name_col.setIcon(self.up_arrow_icon)
self.count_col = QTableWidgetItem(_('Count'))
self.table.setHorizontalHeaderItem(1, self.count_col)
self.count_col.setIcon(self.blank_icon)
self.was_col = QTableWidgetItem(_('Was'))
self.table.setHorizontalHeaderItem(2, self.was_col)
self.count_col.setIcon(self.blank_icon)
def search_clicked(self): self.table.setRowCount(len(tags))
for row,tag in enumerate(tags):
item = NameTableWidgetItem()
item.set_is_deleted(self.all_tags[tag]['is_deleted'])
item.setText(self.all_tags[tag]['cur_name'])
item.set_initial_text(tag)
item.setData(Qt.UserRole, self.all_tags[tag]['key'])
item.setFlags(item.flags() | Qt.ItemIsSelectable | Qt.ItemIsEditable)
self.table.setItem(row, 0, item)
if 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))
self.table.setItem(row, 1, item)
item = QTableWidgetItem()
item.setFlags(item.flags() & ~(Qt.ItemIsSelectable|Qt.ItemIsEditable))
if tag != self.all_tags[tag]['cur_name'] or self.all_tags[tag]['is_deleted']:
item.setData(Qt.DisplayRole, tag)
self.table.setItem(row, 2, item)
self.table.blockSignals(False)
return select_item
def all_matching_clicked(self):
for i in range(0, self.table.rowCount()):
item = self.table.item(i, 0)
tag = item.initial_text()
self.all_tags[tag]['cur_name'] = item.text()
self.all_tags[tag]['is_deleted'] = item.is_deleted
search_for = icu_lower(unicode(self.search_box.text())) search_for = icu_lower(unicode(self.search_box.text()))
if not search_for: if len(search_for) == 0:
error_dialog(self, _('Find'), _('You must enter some text to search for'), self.fill_in_table(self.ordered_tags, None)
show=True, show_copy_button=False) result = []
return for k in self.ordered_tags:
rows = self.table.rowCount() if search_for in icu_lower(unicode(self.all_tags[k]['cur_name'])):
for i in range(0, rows): result.append(k)
self.start_find_pos += 1 self.fill_in_table(result, None)
if self.start_find_pos >= rows:
self.start_find_pos = 0
item = self.table.item(self.start_find_pos, 0)
if search_for in icu_lower(unicode(item.text())):
self.table.setCurrentItem(item)
return
info_dialog(self, _('Find'), _('No tag found'), show=True, show_copy_button=False)
def table_column_resized(self, col, old, new): def table_column_resized(self, col, old, new):
self.table_column_widths = [] self.table_column_widths = []
@ -268,6 +264,28 @@ class TagListEditor(QDialog, Ui_TagListEditor):
orig.setData(Qt.DisplayRole, item.initial_text()) orig.setData(Qt.DisplayRole, item.initial_text())
self.table.blockSignals(False) self.table.blockSignals(False)
def undo_edit(self):
indexes = self.table.selectionModel().selectedRows()
if not indexes:
error_dialog(self, _('No item selected'),
_('You must select one item from the list of Available items.')).exec_()
return
if not confirm(
_('Do you really want to undo your changes?'),
'tag_list_editor_undo'):
return
self.table.blockSignals(True)
for idx in indexes:
row = idx.row()
item = self.table.item(row, 0)
item.setText(item.initial_text())
item.set_is_deleted(False)
self.to_delete.discard(int(item.data(Qt.UserRole)))
self.to_rename.pop(int(item.data(Qt.UserRole)), None)
self.table.item(row, 2).setData(Qt.DisplayRole, '')
self.table.blockSignals(False)
def rename_tag(self): def rename_tag(self):
item = self.table.item(self.table.currentRow(), 0) item = self.table.item(self.table.currentRow(), 0)
self._rename_tag(item) self._rename_tag(item)

View File

@ -46,7 +46,7 @@
<item> <item>
<widget class="QPushButton" name="search_button"> <widget class="QPushButton" name="search_button">
<property name="toolTip"> <property name="toolTip">
<string>Find the first/next matching item</string> <string>Display items containing the search string</string>
</property> </property>
<property name="text"> <property name="text">
<string>&amp;Find</string> <string>&amp;Find</string>
@ -79,6 +79,9 @@
<height>32</height> <height>32</height>
</size> </size>
</property> </property>
<property name="shortcut">
<string>Ctrl+D</string>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -100,7 +103,30 @@
</size> </size>
</property> </property>
<property name="shortcut"> <property name="shortcut">
<string>Ctrl+S</string> <string>Ctrl+R</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="undo_button">
<property name="toolTip">
<string>Undo any deletes or edits on the selected lines</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/edit-undo.png</normaloff>:/images/edit-undo.png</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="shortcut">
<string>Ctrl+U</string>
</property> </property>
</widget> </widget>
</item> </item>