mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
1645dc3711
commit
9eda085a3b
@ -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)
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user