1) refactor hierarchy code to build the category child map once.

2) make hierarchical items display hierarchically in user categories
3) make add item to category also copy child nodes
4) make remove node also remove child nodes
5) some basic code cleanups
This commit is contained in:
Charles Haley 2011-02-24 21:22:59 +00:00
parent 1645dc3711
commit 9eda085a3b
2 changed files with 68 additions and 41 deletions

View File

@ -32,6 +32,9 @@ from calibre.gui2.dialogs.tag_list_editor import TagListEditor
from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog
from calibre.gui2.widgets import HistoryLineEdit from calibre.gui2.widgets import HistoryLineEdit
def original_name(t):
return getattr(t, 'original_name', t.name)
class TagDelegate(QItemDelegate): # {{{ class TagDelegate(QItemDelegate): # {{{
def paint(self, painter, option, index): def paint(self, painter, option, index):
@ -228,9 +231,13 @@ class TagsView(QTreeView): # {{{
self._toggle(index, set_to=search_state) self._toggle(index, set_to=search_state)
return return
if action == 'add_to_category': if action == 'add_to_category':
self.add_item_to_user_cat.emit(category, tag = index.tag
getattr(index, 'original_name', index.name), if len(index.children) > 0:
index.category) for c in index.children:
self.add_item_to_user_cat.emit(category, original_name(c.tag),
c.tag.category)
self.add_item_to_user_cat.emit(category, original_name(tag),
tag.category)
return return
if action == 'add_subcategory': if action == 'add_subcategory':
self.add_subcategory.emit(key) self.add_subcategory.emit(key)
@ -242,8 +249,12 @@ class TagsView(QTreeView): # {{{
self.delete_user_category.emit(key) self.delete_user_category.emit(key)
return return
if action == 'delete_item_from_user_category': if action == 'delete_item_from_user_category':
self.del_item_from_user_cat.emit(key, tag = index.tag
getattr(index, 'original_name', index.name), index.category) if len(index.children) > 0:
for c in index.children:
self.del_item_from_user_cat.emit(key, original_name(c.tag),
c.tag.category)
self.del_item_from_user_cat.emit(key, original_name(tag), tag.category)
return return
if action == 'manage_searches': if action == 'manage_searches':
self.saved_search_edit.emit(category) self.saved_search_edit.emit(category)
@ -278,8 +289,8 @@ class TagsView(QTreeView): # {{{
tag = None tag = None
if item.type == TagTreeItem.TAG: if item.type == TagTreeItem.TAG:
tag_item = item
tag = item.tag tag = item.tag
can_edit = getattr(tag, 'can_edit', True)
while item.type != TagTreeItem.CATEGORY: while item.type != TagTreeItem.CATEGORY:
item = item.parent item = item.parent
@ -297,7 +308,7 @@ class TagsView(QTreeView): # {{{
if tag: if tag:
# If the user right-clicked on an editable item, then offer # If the user right-clicked on an editable item, then offer
# the possibility of renaming that item. # the possibility of renaming that item.
if can_edit: if tag.is_editable:
# Add the 'rename' items # 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', partial(self.context_menu_handler, action='edit_item',
@ -317,8 +328,7 @@ class TagsView(QTreeView): # {{{
m.addAction(self.user_category_icon, n, m.addAction(self.user_category_icon, n,
partial(self.context_menu_handler, partial(self.context_menu_handler,
'add_to_category', 'add_to_category',
category='.'.join(p), category='.'.join(p), index=tag_item))
index=tag))
if len(tree_dict[k]): if len(tree_dict[k]):
tm = m.addMenu(self.user_category_icon, tm = m.addMenu(self.user_category_icon,
_('Children of %s')%n) _('Children of %s')%n)
@ -331,7 +341,7 @@ class TagsView(QTreeView): # {{{
_('Remove %s from category %s')%(tag.name, item.py_name), _('Remove %s from category %s')%(tag.name, item.py_name),
partial(self.context_menu_handler, partial(self.context_menu_handler,
action='delete_item_from_user_category', action='delete_item_from_user_category',
key = key, index = tag)) key = key, index = tag_item))
# Add the search for value items # Add the search for value items
self.context_menu.addAction(self.search_icon, self.context_menu.addAction(self.search_icon,
_('Search for %s')%tag.name, _('Search for %s')%tag.name,
@ -345,7 +355,7 @@ class TagsView(QTreeView): # {{{
index=index)) index=index))
self.context_menu.addSeparator() self.context_menu.addSeparator()
elif key.startswith('@') and not item.is_gst: elif key.startswith('@') and not item.is_gst:
if item.can_edit: if item.can_be_edited:
self.context_menu.addAction(self.user_category_icon, self.context_menu.addAction(self.user_category_icon,
_('Rename %s')%item.py_name, _('Rename %s')%item.py_name,
partial(self.context_menu_handler, action='edit_item', partial(self.context_menu_handler, action='edit_item',
@ -386,8 +396,8 @@ class TagsView(QTreeView): # {{{
self.db.field_metadata[key]['is_custom']: self.db.field_metadata[key]['is_custom']:
self.context_menu.addAction(_('Manage %s')%category, self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='open_editor', partial(self.context_menu_handler, action='open_editor',
category=getattr(tag, 'original_name', tag.name) category=original_name(tag) if tag else None,
if tag else None, key=key)) key=key))
elif key == 'authors': elif key == 'authors':
self.context_menu.addAction(_('Manage %s')%category, self.context_menu.addAction(_('Manage %s')%category,
partial(self.context_menu_handler, action='edit_author_sort')) partial(self.context_menu_handler, action='edit_author_sort'))
@ -597,8 +607,8 @@ class TagTreeItem(object): # {{{
p = self p = self
while p.parent.type != self.ROOT: while p.parent.type != self.ROOT:
p = p.parent p = p.parent
if p.category_key.startswith('@'): if not tag.is_hierarchical:
name = getattr(tag, 'original_name', tag.name) name = original_name(tag)
else: else:
name = tag.name name = tag.name
tt_author = False tt_author = False
@ -608,7 +618,7 @@ class TagTreeItem(object): # {{{
else: else:
return QVariant('[%d] %s'%(tag.count, name)) return QVariant('[%d] %s'%(tag.count, name))
if role == Qt.EditRole: if role == Qt.EditRole:
return QVariant(getattr(tag, 'original_name', tag.name)) return QVariant(original_name(tag))
if role == Qt.DecorationRole: if role == Qt.DecorationRole:
return self.icon_state_map[tag.state] return self.icon_state_map[tag.state]
if role == Qt.ToolTipRole: if role == Qt.ToolTipRole:
@ -708,7 +718,7 @@ class TagsModel(QAbstractItemModel): # {{{
last_category_node = node last_category_node = node
category_node_map[path] = node category_node_map[path] = node
self.category_nodes.append(node) self.category_nodes.append(node)
node.can_edit = (not is_gst) and (i == (len(path_parts)-1)) node.can_be_edited = (not is_gst) and (i == (len(path_parts)-1))
node.is_gst = is_gst node.is_gst = is_gst
if not is_gst: if not is_gst:
tree_root[p] = {} tree_root[p] = {}
@ -748,8 +758,8 @@ class TagsModel(QAbstractItemModel): # {{{
p = node p = node
while p.type != TagTreeItem.CATEGORY: while p.type != TagTreeItem.CATEGORY:
p = p.parent p = p.parent
d = (node.type, p.category_key, p.is_gst, d = (node.type, p.category_key, p.is_gst, original_name(t),
getattr(t, 'original_name', t.name), t.category, t.id) t.category, t.id)
data.append(d) data.append(d)
else: else:
data.append(None) data.append(None)
@ -794,6 +804,7 @@ class TagsModel(QAbstractItemModel): # {{{
dest is the TagTreeItem node to receive the items dest is the TagTreeItem node to receive the items
action is Qt.CopyAction or Qt.MoveAction action is Qt.CopyAction or Qt.MoveAction
''' '''
##### TODO: must handle children of item being copied
user_cats = self.db.prefs.get('user_categories', {}) user_cats = self.db.prefs.get('user_categories', {})
parent_node = None parent_node = None
copied_node = None copied_node = None
@ -1056,6 +1067,7 @@ class TagsModel(QAbstractItemModel): # {{{
if cat_len <= 0: if cat_len <= 0:
return ((collapse_letter, collapse_letter_sk)) return ((collapse_letter, collapse_letter_sk))
category_child_map = {}
fm = self.db.field_metadata[key] fm = self.db.field_metadata[key]
clear_rating = True if key not in self.categories_with_ratings and \ clear_rating = True if key not in self.categories_with_ratings and \
not fm['is_custom'] and \ not fm['is_custom'] and \
@ -1107,40 +1119,52 @@ class TagsModel(QAbstractItemModel): # {{{
else: else:
node_parent = category node_parent = category
components = [t for t in tag.name.split('.')] # category display order is important here. The following works
if key in ['authors', 'publisher', 'news', 'formats', 'rating'] or \ # only of all the non-user categories are displayed before the
key not in self.db.prefs.get('categories_using_hierarchy', []) or\ # user categories
len(components) == 1 or \ components = [t for t in original_name(tag).split('.')]
fm['kind'] == 'user': in_uc = fm['kind'] == 'user'
if (not tag.is_hierarchical) and (in_uc or
key in ['authors', 'publisher', 'news', 'formats', 'rating'] or
key not in self.db.prefs.get('categories_using_hierarchy', []) or
len(components) == 1):
self.beginInsertRows(category_index, 999999, 1) self.beginInsertRows(category_index, 999999, 1)
TagTreeItem(parent=node_parent, data=tag, tooltip=tt, n = TagTreeItem(parent=node_parent, data=tag, tooltip=tt,
icon_map=self.icon_state_map) icon_map=self.icon_state_map)
category_child_map[tag.name, tag.category] = n
self.endInsertRows() self.endInsertRows()
tag.can_edit = key != 'formats' and (key == 'news' or \ tag.is_editable = key != 'formats' and (key == 'news' or \
self.db.field_metadata[tag.category]['datatype'] in \ self.db.field_metadata[tag.category]['datatype'] in \
['text', 'series', 'enumeration']) ['text', 'series', 'enumeration'])
else: else:
for i,comp in enumerate(components): for i,comp in enumerate(components):
child_map = dict([(t.tag.name, t) for t in node_parent.children if i == 0:
child_map = category_child_map
else:
child_map = dict([((t.tag.name, t.tag.category), t)
for t in node_parent.children
if t.type != TagTreeItem.CATEGORY]) if t.type != TagTreeItem.CATEGORY])
if comp in child_map: if (comp,tag.category) in child_map:
node_parent = child_map[comp] node_parent = child_map[(comp,tag.category)]
node_parent.tag.count += tag.count if not in_uc:
node_parent.tag.use_prefix = True node_parent.tag.count += tag.count
node_parent.tag.is_hierarchical = True
else: else:
if i < len(components)-1: if i < len(components)-1:
t = copy.copy(tag) t = copy.copy(tag)
t.original_name = '.'.join(components[:i+1]) t.original_name = '.'.join(components[:i+1])
t.can_edit = False t.is_editable = False
else: else:
t = tag t = tag
t.original_name = t.name if not in_uc:
t.can_edit = True t.original_name = t.name
t.use_prefix = True t.is_editable = True
t.is_hierarchical = True
t.name = comp t.name = comp
self.beginInsertRows(category_index, 999999, 1) self.beginInsertRows(category_index, 999999, 1)
node_parent = TagTreeItem(parent=node_parent, data=t, node_parent = TagTreeItem(parent=node_parent, data=t,
tooltip=tt, icon_map=self.icon_state_map) tooltip=tt, icon_map=self.icon_state_map)
child_map[(comp,tag.category)] = node_parent
self.endInsertRows() self.endInsertRows()
return ((collapse_letter, collapse_letter_sk)) return ((collapse_letter, collapse_letter_sk))
@ -1219,7 +1243,7 @@ class TagsModel(QAbstractItemModel): # {{{
return True return True
key = item.tag.category key = item.tag.category
name = getattr(item.tag, 'original_name', item.tag.name) name = original_name(item.tag)
# make certain we know about the item's category # make certain we know about the item's category
if key not in self.db.field_metadata: if key not in self.db.field_metadata:
return False return False
@ -1306,7 +1330,7 @@ class TagsModel(QAbstractItemModel): # {{{
if index.isValid(): if index.isValid():
node = self.data(index, Qt.UserRole) node = self.data(index, Qt.UserRole)
if node.type == TagTreeItem.TAG: if node.type == TagTreeItem.TAG:
if getattr(node.tag, 'can_edit', True): if node.tag.is_editable:
ans |= Qt.ItemIsDragEnabled ans |= Qt.ItemIsDragEnabled
fm = self.db.metadata_for_field(node.tag.category) fm = self.db.metadata_for_field(node.tag.category)
if node.tag.category in \ if node.tag.category in \
@ -1438,8 +1462,8 @@ class TagsModel(QAbstractItemModel): # {{{
if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating if tag.name and tag.name[0] == u'\u2605': # char is a star. Assume rating
ans.append('%s%s:%s'%(prefix, category, len(tag.name))) ans.append('%s%s:%s'%(prefix, category, len(tag.name)))
else: else:
name = getattr(tag, 'original_name', tag.name) name = original_name(tag)
use_prefix = getattr(tag, 'use_prefix', False) use_prefix = tag.is_hierarchical
if category == 'tags': if category == 'tags':
if name in tags_seen: if name in tags_seen:
continue continue
@ -1477,7 +1501,7 @@ class TagsModel(QAbstractItemModel): # {{{
tag = tag_item.tag tag = tag_item.tag
if tag is None: if tag is None:
return False return False
name = getattr(tag, 'original_name', tag.name) name = original_name(tag)
if (equals_match and strcmp(name, txt) == 0) or \ if (equals_match and strcmp(name, txt) == 0) or \
(not equals_match and lower(name).find(txt) >= 0): (not equals_match and lower(name).find(txt) >= 0):
self.path_found = path self.path_found = path
@ -1703,8 +1727,9 @@ class TagBrowserMixin(object): # {{{
db = self.library_view.model().db db = self.library_view.model().db
user_cats = db.prefs.get('user_categories', {}) user_cats = db.prefs.get('user_categories', {})
if dest_category.startswith('@'): if dest_category and dest_category.startswith('@'):
dest_category = dest_category[1:] dest_category = dest_category[1:]
if dest_category not in user_cats: if dest_category not in user_cats:
return error_dialog(self.tags_view, _('Add to user category'), return error_dialog(self.tags_view, _('Add to user category'),
_('A user category %s does not exist')%dest_category, show=True) _('A user category %s does not exist')%dest_category, show=True)

View File

@ -51,6 +51,8 @@ class Tag(object):
self.id = id self.id = id
self.count = count self.count = count
self.state = state self.state = state
self.is_hierarchical = False
self.is_editable = True
self.avg_rating = avg/2.0 if avg is not None else 0 self.avg_rating = avg/2.0 if avg is not None else 0
self.sort = sort self.sort = sort
if self.avg_rating > 0: if self.avg_rating > 0: