From 40af13276eb2e4badb634d3473a4af7123686cae Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 22 Feb 2011 13:19:35 +0000 Subject: [PATCH] First pass at hierarchical tags --- src/calibre/gui2/tag_view.py | 109 ++++++++++++++++++++++------------ src/calibre/library/caches.py | 13 +++- 2 files changed, 80 insertions(+), 42 deletions(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 1660d9b8c6..8b353cd2b3 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' Browsing book collection by tags. ''' -import traceback +import traceback, copy from itertools import izip from functools import partial @@ -551,11 +551,12 @@ class TagTreeItem(object): # {{{ def child_tags(self): res = [] - for t in self.children: - if t.type == TagTreeItem.CATEGORY: - res.extend(t.child_tags()) - else: - res.append(t) + def recurse(nodes, res): + for t in nodes: + if t.type != TagTreeItem.CATEGORY: + res.append(t) + recurse(t.children, res) + recurse(self.children, res) return res # }}} @@ -603,6 +604,7 @@ class TagsModel(QAbstractItemModel): # {{{ tt = '' else: tt = _(u'The lookup/search name is "{0}"').format(r) + if r.startswith('@') and r.find('/') >= 0: path_parts = [p.strip() for p in r.split('/') if p.strip()] path = '' @@ -858,7 +860,7 @@ class TagsModel(QAbstractItemModel): # {{{ for idx,tag in enumerate(data[key]): if clear_rating: tag.avg_rating = None - tag.state = state_map.get(tag.name, 0) + tag.state = state_map.get((tag.name, tag.category), 0) if collapse_model != 'disable' and cat_len > collapse: if collapse_model == 'partition': @@ -875,7 +877,6 @@ class TagsModel(QAbstractItemModel): # {{{ data = name, tooltip = None, category_icon = category_node.icon, category_key=category_node.category_key) - sub_cat_index = self.createIndex(sub_cat.row(), 0, sub_cat) self.endInsertRows() else: ts = tag.sort @@ -897,22 +898,45 @@ class TagsModel(QAbstractItemModel): # {{{ category_icon = category_node.icon, tooltip = None, category_key=category_node.category_key) - sub_cat_index = self.createIndex(sub_cat.row(), 0, sub_cat) - self.beginInsertRows(sub_cat_index, 999999, 1) - TagTreeItem(parent=sub_cat, data=tag, tooltip=tt, - icon_map=self.icon_state_map) + node_parent = sub_cat else: + node_parent = category + + components = [t for t in tag.name.split('.')] + if key in ['authors', 'publisher', 'title'] or len(components) == 1: self.beginInsertRows(category_index, 999999, 1) - TagTreeItem(parent=category, data=tag, tooltip=tt, + TagTreeItem(parent=node_parent, data=tag, tooltip=tt, icon_map=self.icon_state_map) - self.endInsertRows() + self.endInsertRows() + else: + print components + for i,comp in enumerate(components): + children = dict([(t.tag.name, t) for t in node_parent.children + if t.type != TagTreeItem.CATEGORY]) + if comp in children: + node_parent = children[comp] + else: + if i < len(components)-1: + t = copy.copy(tag) + t.original_name = '.'.join(components[:i+1]) + t.use_prefix = True + else: + t = tag + t.original_name = t.name + t.use_prefix = False + t.name = comp + self.beginInsertRows(category_index, 999999, 1) + node_parent = TagTreeItem(parent=node_parent, data=t, + tooltip=tt, icon_map=self.icon_state_map) + self.endInsertRows() + return ((collapse_letter, collapse_letter_sk)) for category in self.category_nodes: if len(category.children) > 0: children = category.children - states = [c.tag.state for c in children if c.type != TagTreeItem.CATEGORY] - names = [c.tag.name for c in children if c.type != TagTreeItem.CATEGORY] + states = [c.tag.state for c in category.child_tags()] + names = [(c.tag.name, c.tag.category) for c in category.child_tags()] state_map = dict(izip(names, states)) ctags = [c for c in children if c.type == TagTreeItem.CATEGORY] start = len(ctags) @@ -1064,27 +1088,31 @@ class TagsModel(QAbstractItemModel): # {{{ def reset_all_states(self, except_=None): update_list = [] - def process_tag(tag_index, tag_item): - tag = tag_item.tag - if tag is except_: - self.dataChanged.emit(tag_index, tag_index) - return - if tag.state != 0 or tag in update_list: - tag.state = 0 - update_list.append(tag) - self.dataChanged.emit(tag_index, tag_index) + def process_tag(tag_item): + if tag_item.type != TagTreeItem.CATEGORY: + tag = tag_item.tag + if tag is except_: + tag_index = self.createIndex(tag_item.row(), 0, tag_item) + self.dataChanged.emit(tag_index, tag_index) + elif tag.state != 0 or tag in update_list: + tag_index = self.createIndex(tag_item.row(), 0, tag_item) + tag.state = 0 + update_list.append(tag) + self.dataChanged.emit(tag_index, tag_index) + for t in tag_item.children: + process_tag(t) - def process_level(category_index): - 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: - process_level(tag_index) - else: - process_tag(tag_index, tag_item) +# def process_level(category_index): +# 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: +# process_level(tag_index) +# else: +# process_tag(tag_index, tag_item) - for i in xrange(self.rowCount(QModelIndex())): - process_level(self.index(i, 0, QModelIndex())) + for t in self.root_item.children: + process_tag(t) def clear_state(self): self.reset_all_states() @@ -1127,15 +1155,18 @@ class TagsModel(QAbstractItemModel): # {{{ 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))) else: + name = getattr(tag, 'original_name', tag.name) + use_prefix = getattr(tag, 'use_prefix', False) if category == 'tags': - if tag.name in tags_seen: + if name in tags_seen: continue - tags_seen.add(tag.name) + tags_seen.add(name) if tag in nodes_seen: continue nodes_seen.add(tag) - ans.append('%s%s:"=%s"'%(prefix, category, - tag.name.replace(r'"', r'\"'))) + ans.append('%s%s:"=%s%s"'%(prefix, category, + '.' if use_prefix else '', + name.replace(r'"', r'\"'))) return ans def find_item_node(self, key, txt, start_path): diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index da71ce0d4e..a84a9e0940 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -124,9 +124,16 @@ def _match(query, value, matchkind): for t in value: t = icu_lower(t) try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished - if ((matchkind == EQUALS_MATCH and query == t) or - (matchkind == REGEXP_MATCH and re.search(query, t, re.I)) or ### search unanchored - (matchkind == CONTAINS_MATCH and query in t)): + if (matchkind == EQUALS_MATCH): + if query[0] == '.': + if t.startswith(query[1:]): + ql = len(query) - 1 + print ql, t, query + return (len(t) == ql) or (t[ql:ql+1] == '.') + elif query == t: + return True + elif ((matchkind == REGEXP_MATCH and re.search(query, t, re.I)) or ### search unanchored + (matchkind == CONTAINS_MATCH and query in t)): return True except re.error: pass