This commit is contained in:
Kovid Goyal 2011-02-24 08:19:01 -07:00
commit a28e0b6f1d
3 changed files with 310 additions and 75 deletions

View File

@ -58,10 +58,12 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.to_rename = {}
self.to_delete = set([])
self.original_names = {}
self.all_tags = {}
for k,v in data:
self.all_tags[v] = k
self.original_names[k] = v
for tag in sorted(self.all_tags.keys(), key=key):
item = ListWidgetItem(tag)
item.setData(Qt.UserRole, self.all_tags[tag])

View File

@ -25,7 +25,7 @@ from calibre.utils.config import tweaks
from calibre.utils.icu import sort_key, upper, lower, strcmp
from calibre.utils.search_query_parser import saved_searches
from calibre.utils.formatter import eval_formatter
from calibre.gui2 import error_dialog
from calibre.gui2 import error_dialog, question_dialog
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.tag_categories import TagCategories
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
@ -70,16 +70,19 @@ TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_minus': 2}
class TagsView(QTreeView): # {{{
refresh_required = pyqtSignal()
tags_marked = pyqtSignal(object)
user_category_edit = pyqtSignal(object)
add_subcategory = pyqtSignal(object)
tag_list_edit = pyqtSignal(object, object)
saved_search_edit = pyqtSignal(object)
author_sort_edit = pyqtSignal(object, object)
tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal()
drag_drop_finished = pyqtSignal(object, object)
refresh_required = pyqtSignal()
tags_marked = pyqtSignal(object)
edit_user_category = pyqtSignal(object)
delete_user_category = pyqtSignal(object)
del_item_from_user_cat = pyqtSignal(object, object, object)
add_item_to_user_cat = pyqtSignal(object, object, object)
add_subcategory = pyqtSignal(object)
tag_list_edit = pyqtSignal(object, object)
saved_search_edit = pyqtSignal(object)
author_sort_edit = pyqtSignal(object, object)
tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal()
drag_drop_finished = pyqtSignal(object, object)
def __init__(self, parent=None):
QTreeView.__init__(self, parent=None)
@ -105,6 +108,7 @@ class TagsView(QTreeView): # {{{
else:
self.collapse_model = gprefs['tags_browser_partition_method']
self.search_icon = QIcon(I('search.png'))
self.user_category_icon = QIcon(I('tb_folder.png'))
def set_pane_is_visible(self, to_what):
pv = self.pane_is_visible
@ -218,17 +222,29 @@ class TagsView(QTreeView): # {{{
self.tag_list_edit.emit(category, key)
return
if action == 'manage_categories':
self.user_category_edit.emit(category)
return
if action == 'add_subcategory':
self.add_subcategory.emit(category)
self.edit_user_category.emit(category)
return
if action == 'search':
self._toggle(index, set_to=search_state)
return
if action == 'add_to_category':
self.add_item_to_user_cat.emit(category,
getattr(index, 'original_name', index.name),
index.category)
return
if action == 'add_subcategory':
self.add_subcategory.emit(key)
return
if action == 'search_category':
self.tags_marked.emit(key + ':' + search_state)
return
if action == 'delete_user_category':
self.delete_user_category.emit(key)
return
if action == 'delete_item_from_user_category':
self.del_item_from_user_cat.emit(key,
getattr(index, 'original_name', index.name), index.category)
return
if action == 'manage_searches':
self.saved_search_edit.emit(category)
return
@ -259,14 +275,11 @@ class TagsView(QTreeView): # {{{
if index.isValid():
item = index.internalPointer()
tag_name = ''
tag = None
if item.type == TagTreeItem.TAG:
tag_item = item
t = item.tag
tag_name = t.name
tag_id = t.id
can_edit = getattr(t, 'can_edit', True)
tag = item.tag
can_edit = getattr(tag, 'can_edit', True)
while item.type != TagTreeItem.CATEGORY:
item = item.parent
@ -281,42 +294,70 @@ class TagsView(QTreeView): # {{{
return True
# Did the user click on a leaf node?
if tag_name:
if tag:
# If the user right-clicked on an editable item, then offer
# the possibility of renaming that item.
if can_edit and \
key in ['authors', 'tags', 'series', 'publisher', 'search'] or \
(self.db.field_metadata[key]['is_custom'] and \
self.db.field_metadata[key]['datatype'] != 'rating'):
if can_edit:
# Add the 'rename' items
self.context_menu.addAction(_('Rename %s')%tag_name,
self.context_menu.addAction(_('Rename %s')%tag.name,
partial(self.context_menu_handler, action='edit_item',
category=tag_item, index=index))
index=index))
if key == 'authors':
self.context_menu.addAction(_('Edit sort for %s')%tag_name,
self.context_menu.addAction(_('Edit sort for %s')%tag.name,
partial(self.context_menu_handler,
action='edit_author_sort', index=tag_id))
action='edit_author_sort', index=tag.id))
m = self.context_menu.addMenu(self.user_category_icon,
_('Add %s to user category')%tag.name)
nt = self.model().category_node_tree
def add_node_tree(tree_dict, m, path):
p = path[:]
for k in sorted(tree_dict.keys(), key=sort_key):
p.append(k)
n = k[1:] if k.startswith('@') else k
m.addAction(self.user_category_icon, n,
partial(self.context_menu_handler,
'add_to_category',
category='.'.join(p),
index=tag))
if len(tree_dict[k]):
tm = m.addMenu(self.user_category_icon,
_('Children of %s')%n)
add_node_tree(tree_dict[k], tm, p)
p.pop()
add_node_tree(nt, m, [])
if key.startswith('@') and not item.is_gst:
self.context_menu.addAction(self.user_category_icon,
_('Remove %s from category %s')%(tag.name, item.py_name),
partial(self.context_menu_handler,
action='delete_item_from_user_category',
key = key, index = tag))
# Add the search for value items
self.context_menu.addAction(self.search_icon,
_('Search for %s')%tag_name,
_('Search for %s')%tag.name,
partial(self.context_menu_handler, action='search',
search_state=TAG_SEARCH_STATES['mark_plus'],
index=index))
self.context_menu.addAction(self.search_icon,
_('Search for everything but %s')%tag_name,
_('Search for everything but %s')%tag.name,
partial(self.context_menu_handler, action='search',
search_state=TAG_SEARCH_STATES['mark_minus'],
index=index))
self.context_menu.addSeparator()
elif key.startswith('@'):
elif key.startswith('@') and not item.is_gst:
if item.can_edit:
self.context_menu.addAction(_('Rename %s')%key[1:],
self.context_menu.addAction(self.user_category_icon,
_('Rename %s')%item.py_name,
partial(self.context_menu_handler, action='edit_item',
category=key, index=index))
self.context_menu.addAction(self.search_icon,
_('Add sub-category to %s')%key[1:],
index=index))
self.context_menu.addAction(self.user_category_icon,
_('Add sub-category to %s')%item.py_name,
partial(self.context_menu_handler,
action='add_subcategory', category=key))
action='add_subcategory', key=key))
self.context_menu.addAction(self.user_category_icon,
_('Delete user category %s')%item.py_name,
partial(self.context_menu_handler,
action='delete_user_category', key=key))
self.context_menu.addSeparator()
# Hide/Show/Restore categories
if not key.startswith('@') or key.find('.') < 0:
@ -345,14 +386,15 @@ class TagsView(QTreeView): # {{{
self.db.field_metadata[key]['is_custom']:
self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='open_editor',
category=tag_name, key=key))
category=getattr(tag, 'original_name', tag.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'))
elif key == 'search':
self.context_menu.addAction(_('Manage Saved Searches'),
partial(self.context_menu_handler, action='manage_searches',
category=tag_name))
category=tag.name if tag else None))
# Always show the user categories editor
self.context_menu.addSeparator()
@ -405,7 +447,7 @@ class TagsView(QTreeView): # {{{
if item.type == TagTreeItem.TAG and flags & Qt.ItemIsDropEnabled:
self.setDropIndicatorShown(not src_is_tb)
return
if item.type == TagTreeItem.CATEGORY:
if item.type == TagTreeItem.CATEGORY and not item.is_gst:
fm_dest = self.db.metadata_for_field(item.category_key)
if fm_dest['kind'] == 'user':
if src_is_tb:
@ -413,7 +455,8 @@ class TagsView(QTreeView): # {{{
data = str(event.mimeData().data('application/calibre+from_tag_browser'))
src = cPickle.loads(data)
for s in src:
if s[0] == TagTreeItem.TAG and not s[1].startswith('@'):
if s[0] == TagTreeItem.TAG and \
(not s[1].startswith('@') or s[2]):
return
self.setDropIndicatorShown(True)
return
@ -534,6 +577,8 @@ class TagTreeItem(object): # {{{
def category_data(self, role):
if role == Qt.DisplayRole:
return QVariant(self.py_name + ' [%d]'%len(self.child_tags()))
if role == Qt.EditRole:
return QVariant(self.py_name)
if role == Qt.DecorationRole:
return self.icon
if role == Qt.FontRole:
@ -634,11 +679,14 @@ class TagsModel(QAbstractItemModel): # {{{
last_category_node = None
category_node_map = {}
self.category_node_tree = {}
for i, r in enumerate(self.row_map):
if self.hidden_categories and self.categories[i] in self.hidden_categories:
continue
is_gst = False
if r.startswith('@') and r[1:] in gst:
tt = _(u'The grouped search term name is "{0}"').format(r[1:])
is_gst = True
elif r == 'news':
tt = ''
else:
@ -648,6 +696,7 @@ class TagsModel(QAbstractItemModel): # {{{
path_parts = [p.strip() for p in r.split('.') if p.strip()]
path = ''
last_category_node = self.root_item
tree_root = self.category_node_tree
for i,p in enumerate(path_parts):
path += p
if path not in category_node_map:
@ -659,15 +708,21 @@ class TagsModel(QAbstractItemModel): # {{{
last_category_node = node
category_node_map[path] = node
self.category_nodes.append(node)
node.can_edit = i == (len(path_parts) - 1)
node.can_edit = (not is_gst) and (i == (len(path_parts)-1))
node.is_gst = is_gst
if not is_gst:
tree_root[p] = {}
tree_root = tree_root[p]
else:
last_category_node = category_node_map[path]
tree_root = tree_root[p]
path += '.'
else:
node = TagTreeItem(parent=self.root_item,
data=self.categories[i],
category_icon=self.category_icon_map[r],
tooltip=tt, category_key=r)
node.is_gst = False
category_node_map[r] = node
last_category_node = node
self.category_nodes.append(node)
@ -693,7 +748,7 @@ class TagsModel(QAbstractItemModel): # {{{
p = node
while p.type != TagTreeItem.CATEGORY:
p = p.parent
d = (node.type, p.category_key,
d = (node.type, p.category_key, p.is_gst,
getattr(t, 'original_name', t.name), t.category, t.id)
data.append(d)
else:
@ -727,10 +782,23 @@ class TagsModel(QAbstractItemModel): # {{{
for s in src:
if s[0] != TagTreeItem.TAG:
return False
return self.move_or_copy_item_to_user_category(src, dest, action)
def move_or_copy_item_to_user_category(self, src, dest, action):
'''
src is a list of tuples representing items to copy. The tuple is
(type, containing category key, category key is global search term,
full name, category key, id)
The 'id' member is ignored, and can be None.
The type must be TagTreeItem.TAG
dest is the TagTreeItem node to receive the items
action is Qt.CopyAction or Qt.MoveAction
'''
user_cats = self.db.prefs.get('user_categories', {})
parent_node = None
copied_node = None
for s in src:
src_parent, src_name, src_cat = s[1:4]
src_parent, src_parent_is_gst, src_name, src_cat = s[1:5]
parent_node = src_parent
if src_parent.startswith('@'):
is_uc = True
@ -742,12 +810,16 @@ class TagsModel(QAbstractItemModel): # {{{
continue
new_cat = []
# delete the item if the source is a user category and action is move
if is_uc and src_parent in user_cats and action == Qt.MoveAction:
if is_uc and not src_parent_is_gst and src_parent in user_cats and \
action == Qt.MoveAction:
for tup in user_cats[src_parent]:
if src_name == tup[0] and src_cat == tup[1]:
continue
new_cat.append(list(tup))
user_cats[src_parent] = new_cat
else:
copied_node = (src_parent, src_name)
# Now add the item to the destination user category
add_it = True
if not is_uc and src_cat == 'news':
@ -757,12 +829,17 @@ class TagsModel(QAbstractItemModel): # {{{
add_it = False
if add_it:
user_cats[dest_key].append([src_name, src_cat, 0])
self.db.prefs.set('user_categories', user_cats)
self.tags_view.set_new_model()
self.tags_view.recount()
if parent_node is not None:
# Must work with the new model here
m = self.tags_view.model()
path = m.find_category_node(parent_node)
if copied_node is not None:
path = m.find_item_node(parent_node, copied_node[1], None,
equals_match=True)
else:
path = m.find_category_node(parent_node)
idx = m.index_for_path(path)
self.tags_view.setExpanded(idx, True)
m.show_item_at_index(idx)
@ -1031,15 +1108,17 @@ class TagsModel(QAbstractItemModel): # {{{
node_parent = category
components = [t for t in tag.name.split('.')]
if key in ['authors', 'publisher', 'news', 'formats'] or \
if key in ['authors', 'publisher', 'news', 'formats', 'rating'] or \
key not in self.db.prefs.get('categories_using_hierarchy', []) or\
len(components) == 1 or \
fm['kind'] == 'user' or \
fm['datatype'] not in ['text', 'series', 'enumeration']:
fm['kind'] == 'user':
self.beginInsertRows(category_index, 999999, 1)
TagTreeItem(parent=node_parent, data=tag, tooltip=tt,
icon_map=self.icon_state_map)
self.endInsertRows()
tag.can_edit = key != 'formats' and (key == 'news' or \
self.db.field_metadata[tag.category]['datatype'] in \
['text', 'series', 'enumeration'])
else:
for i,comp in enumerate(components):
child_map = dict([(t.tag.name, t) for t in node_parent.children
@ -1047,6 +1126,7 @@ class TagsModel(QAbstractItemModel): # {{{
if comp in child_map:
node_parent = child_map[comp]
node_parent.tag.count += tag.count
node_parent.tag.use_prefix = True
else:
if i < len(components)-1:
t = copy.copy(tag)
@ -1137,10 +1217,9 @@ class TagsModel(QAbstractItemModel): # {{{
p = self.tags_view.model().find_category_node('@' + nkey)
self.tags_view.model().show_item_at_path(p)
return True
itm = item.parent
while itm.type != TagTreeItem.CATEGORY:
itm = itm.parent
key = itm.category_key
key = item.tag.category
name = getattr(item.tag, 'original_name', item.tag.name)
# make certain we know about the item's category
if key not in self.db.field_metadata:
return False
@ -1171,10 +1250,54 @@ class TagsModel(QAbstractItemModel): # {{{
label=self.db.field_metadata[key]['label'])
self.tags_view.tag_item_renamed.emit()
item.tag.name = val
self.rename_item_in_all_user_categories(name, key, val)
self.refresh() # Should work, because no categories can have disappeared
self.show_item_at_path(path)
return True
def rename_item_in_all_user_categories(self, item_name, item_category, new_name):
'''
Search all user categories for items named item_name with category
item_category and rename them to new_name. The caller must arrange to
redisplay the tree as appropriate (recount or set_new_model)
'''
user_cats = self.db.prefs.get('user_categories', {})
for k in user_cats.keys():
new_contents = []
for tup in user_cats[k]:
if tup[0] == item_name and tup[1] == item_category:
new_contents.append([new_name, item_category, 0])
else:
new_contents.append(tup)
user_cats[k] = new_contents
self.db.prefs.set('user_categories', user_cats)
def delete_item_from_all_user_categories(self, item_name, item_category):
'''
Search all user categories for items named item_name with category
item_category and delete them. The caller must arrange to redisplay the
tree as appropriate (recount or set_new_model)
'''
user_cats = self.db.prefs.get('user_categories', {})
for cat in user_cats.keys():
self.delete_item_from_user_category(cat, item_name, item_category,
user_categories=user_cats)
self.db.prefs.set('user_categories', user_cats)
def delete_item_from_user_category(self, category, item_name, item_category,
user_categories=None):
if user_categories is not None:
user_cats = user_categories
else:
user_cats = self.db.prefs.get('user_categories', {})
new_contents = []
for tup in user_cats[category]:
if tup[0] != item_name or tup[1] != item_category:
new_contents.append(tup)
user_cats[category] = new_contents
if user_categories is None:
self.db.prefs.set('user_categories', user_cats)
def headerData(self, *args):
return NONE
@ -1329,19 +1452,20 @@ class TagsModel(QAbstractItemModel): # {{{
name.replace(r'"', r'\"')))
return ans
def find_item_node(self, key, txt, start_path):
def find_item_node(self, key, txt, start_path, equals_match=False):
'''
Search for an item (a node) in the tags browser list that matches both
the key (exact case-insensitive match) and txt (contains case-
insensitive match). Returns the path to the node. Note that paths are to
a location (second item, fourth item, 25 item), not to a node. If
the key (exact case-insensitive match) and txt (not equals_match =>
case-insensitive contains match; equals_match => case_insensitive
equal match). Returns the path to the node. Note that paths are to a
location (second item, fourth item, 25 item), not to a node. If
start_path is None, the search starts with the topmost node. If the tree
is changed subsequent to calling this method, the path can easily refer
to a different node or no node at all.
'''
if not txt:
return None
txt = lower(txt)
txt = lower(txt) if not equals_match else txt
self.path_found = None
if start_path is None:
start_path = []
@ -1353,9 +1477,14 @@ class TagsModel(QAbstractItemModel): # {{{
tag = tag_item.tag
if tag is None:
return False
if lower(tag.name).find(txt) >= 0:
name = getattr(tag, 'original_name', tag.name)
if (equals_match and strcmp(name, txt) == 0) or \
(not equals_match and lower(name).find(txt) >= 0):
self.path_found = path
return True
for i,c in enumerate(tag_item.children):
if process_tag(depth+1, self.createIndex(i, 0, c), c, start_path):
return True
return False
def process_level(depth, category_index, start_path):
@ -1365,15 +1494,14 @@ class TagsModel(QAbstractItemModel): # {{{
return False
if path[depth] > start_path[depth]:
start_path = path
if key and strcmp(category_index.internalPointer().category_key, key) != 0:
return False
my_key = category_index.internalPointer().category_key
for j in xrange(self.rowCount(category_index)):
tag_index = self.index(j, 0, category_index)
tag_item = tag_index.internalPointer()
if tag_item.type == TagTreeItem.CATEGORY:
if process_level(depth+1, tag_index, start_path):
return True
else:
elif not key or strcmp(key, my_key) == 0:
if process_tag(depth+1, tag_index, tag_item, start_path):
return True
return False
@ -1456,26 +1584,37 @@ class TagBrowserMixin(object): # {{{
self.tags_view.set_database(db, self.tag_match, self.sort_by)
self.tags_view.tags_marked.connect(self.search.set_search_string)
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
self.tags_view.edit_user_category.connect(self.do_edit_user_categories)
self.tags_view.delete_user_category.connect(self.do_delete_user_category)
self.tags_view.del_item_from_user_cat.connect(self.do_del_item_from_user_cat)
self.tags_view.add_subcategory.connect(self.do_add_subcategory)
self.tags_view.add_item_to_user_cat.connect(self.do_add_item_to_user_cat)
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
self.tags_view.author_sort_edit.connect(self.do_author_sort_edit)
self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
self.tags_view.search_item_renamed.connect(self.saved_searches_changed)
self.tags_view.drag_drop_finished.connect(self.drag_drop_finished)
self.edit_categories.clicked.connect(lambda x:
self.do_user_categories_edit())
self.do_edit_user_categories())
def do_add_subcategory(self, on_category=None):
def do_add_subcategory(self, on_category_key, new_category_name=None):
'''
Add a subcategory to the category 'on_category'. If new_category_name is
None, then a default name is shown and the user is offered the
opportunity to edit the name.
'''
db = self.library_view.model().db
user_cats = db.prefs.get('user_categories', {})
# Ensure that the temporary name we will use is not already there
i = 0
new_name = _('New Category').replace('.', '')
if new_category_name is not None:
new_name = new_category_name.replace('.', '')
else:
new_name = _('New Category').replace('.', '')
n = new_name
while True:
new_cat = on_category[1:] + '.' + n
new_cat = on_category_key[1:] + '.' + n
if new_cat not in user_cats:
break
i += 1
@ -1488,9 +1627,13 @@ class TagBrowserMixin(object): # {{{
idx = m.index_for_path(m.find_category_node('@' + new_cat))
m.show_item_at_index(idx)
# Open the editor on the new item to rename it
self.tags_view.edit(idx)
if new_category_name is None:
self.tags_view.edit(idx)
def do_user_categories_edit(self, on_category=None):
def do_edit_user_categories(self, on_category=None):
'''
Open the user categories editor.
'''
db = self.library_view.model().db
d = TagCategories(self, db, on_category)
if d.exec_() == d.Accepted:
@ -1500,9 +1643,89 @@ class TagBrowserMixin(object): # {{{
db.field_metadata.add_user_category('@' + k, k)
db.data.change_search_locations(db.field_metadata.get_search_terms())
self.tags_view.set_new_model()
self.tags_view.recount()
def do_delete_user_category(self, category_name):
'''
Delete the user category named category_name. Any leading '@' is removed
'''
if category_name.startswith('@'):
category_name = category_name[1:]
db = self.library_view.model().db
user_cats = db.prefs.get('user_categories', {})
cat_keys = sorted(user_cats.keys(), key=sort_key)
has_children = False
found = False
for k in cat_keys:
if k == category_name:
found = True
has_children = len(user_cats[k])
elif k.startswith(category_name + '.'):
has_children = True
if not found:
return error_dialog(self.tags_view, _('Delete user category'),
_('%s is not a user category')%category_name, show=True)
if has_children:
if not question_dialog(self.tags_view, _('Delete user category'),
_('%s contains items. Do you really '
'want to delete it?')%category_name):
return
for k in cat_keys:
if k == category_name:
del user_cats[k]
elif k.startswith(category_name + '.'):
del user_cats[k]
db.prefs.set('user_categories', user_cats)
self.tags_view.set_new_model()
def do_del_item_from_user_cat(self, user_cat, item_name, item_category):
'''
Delete the item (item_name, item_category) from the user category with
key user_cat. Any leading '@' characters are removed
'''
if user_cat.startswith('@'):
user_cat = user_cat[1:]
db = self.library_view.model().db
user_cats = db.prefs.get('user_categories', {})
if user_cat not in user_cats:
error_dialog(self.tags_view, _('Remove category'),
_('User category %s does not exist')%user_cat,
show=True)
return
self.tags_view.model().delete_item_from_user_category(user_cat,
item_name, item_category)
self.tags_view.recount()
def do_add_item_to_user_cat(self, dest_category, src_name, src_category):
'''
Add the item src_name in src_category to the user category
dest_category. Any leading '@' is removed
'''
db = self.library_view.model().db
user_cats = db.prefs.get('user_categories', {})
if dest_category.startswith('@'):
dest_category = dest_category[1:]
if dest_category not in user_cats:
return error_dialog(self.tags_view, _('Add to user category'),
_('A user category %s does not exist')%dest_category, show=True)
# Now add the item to the destination user category
add_it = True
if src_category == 'news':
src_category = 'tags'
for tup in user_cats[dest_category]:
if src_name == tup[0] and src_category == tup[1]:
add_it = False
if add_it:
user_cats[dest_category].append([src_name, src_category, 0])
db.prefs.set('user_categories', user_cats)
self.tags_view.recount()
def do_tags_list_edit(self, tag, category):
'''
Open the 'manage_X' dialog where X == category. If tag is not None, the
dialog will position the editor on that item.
'''
db=self.library_view.model().db
if category == 'tags':
result = db.get_tags_with_ids()
@ -1527,6 +1750,8 @@ class TagBrowserMixin(object): # {{{
if d.result() == d.Accepted:
to_rename = d.to_rename # dict of new text to old id
to_delete = d.to_delete # list of ids
orig_name = d.original_names # dict of id: name
rename_func = None
if category == 'tags':
rename_func = db.rename_tag
@ -1540,15 +1765,19 @@ class TagBrowserMixin(object): # {{{
else:
rename_func = partial(db.rename_custom_item, label=cc_label)
delete_func = partial(db.delete_custom_item_using_id, label=cc_label)
m = self.tags_view.model()
if rename_func:
for item in to_delete:
delete_func(item)
m.delete_item_from_all_user_categories(orig_name[item], category)
for old_id in to_rename:
rename_func(old_id, new_name=unicode(to_rename[old_id]))
m.rename_item_in_all_user_categories(orig_name[old_id],
category, unicode(to_rename[old_id]))
# Clean up the library view
self.do_tag_item_renamed()
self.tags_view.set_new_model() # does a refresh for free
self.tags_view.recount()
def do_tag_item_renamed(self):
# Clean up library view and search
@ -1564,6 +1793,9 @@ class TagBrowserMixin(object): # {{{
# refreshing the tags view happens at the emit()/call() site
def do_author_sort_edit(self, parent, id):
'''
Open the manage authors dialog
'''
db = self.library_view.model().db
editor = EditAuthorsDialog(parent, db, id)
d = editor.exec_()

View File

@ -128,7 +128,8 @@ def _match(query, value, matchkind):
if query[0] == '.':
if t.startswith(query[1:]):
ql = len(query) - 1
return (len(t) == ql) or (t[ql:ql+1] == '.')
if (len(t) == ql) or (t[ql:ql+1] == '.'):
return True
elif query == t:
return True
elif ((matchkind == REGEXP_MATCH and re.search(query, t, re.I)) or ### search unanchored