From 7c22f4ffa145e8bb3cd927f475725a676eb3e74c Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 26 Feb 2011 07:16:43 +0000 Subject: [PATCH 1/2] Fix #9169: not possible to hide user category with hierarchy Fix #9166: hierarchy indicator for no children Fix removes all hidden tag browser categories. They must be re-hidden. Fix adds clicking on category nodes to search, plus four-state searching. --- resources/images/minusminus.png | Bin 0 -> 1883 bytes resources/images/plusplus.png | Bin 0 -> 4249 bytes src/calibre/gui2/tag_view.py | 167 ++++++++++++++++++++------------ src/calibre/library/caches.py | 21 ++-- 4 files changed, 114 insertions(+), 74 deletions(-) create mode 100644 resources/images/minusminus.png create mode 100644 resources/images/plusplus.png diff --git a/resources/images/minusminus.png b/resources/images/minusminus.png new file mode 100644 index 0000000000000000000000000000000000000000..71225be8d7f6e5691e4efa5135c500c140141437 GIT binary patch literal 1883 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 F@*kvZ)O-K{ literal 0 HcmV?d00001 diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 2693ba8ed6..034d88e02d 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -21,6 +21,7 @@ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QFont, QSize, \ from calibre.ebooks.metadata import title_sort from calibre.gui2 import config, NONE, gprefs from calibre.library.field_metadata import TagsIcons, category_icon_map +from calibre.library.database2 import Tag from calibre.utils.config import tweaks from calibre.utils.icu import sort_key, lower, strcmp from calibre.utils.search_query_parser import saved_searches @@ -69,7 +70,8 @@ class TagDelegate(QItemDelegate): # {{{ # }}} -TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_minus': 2} +TAG_SEARCH_STATES = {'clear': 0, 'mark_plus': 1, 'mark_plusplus': 2, + 'mark_minus': 3, 'mark_minusminus': 4} class TagsView(QTreeView): # {{{ @@ -127,13 +129,17 @@ class TagsView(QTreeView): # {{{ self.set_new_model(self._model.get_filter_categories_by()) def set_database(self, db, tag_match, sort_by): - self.hidden_categories = db.prefs.get('tag_browser_hidden_categories', None) + hidden_cats = db.prefs.get('tag_browser_hidden_categories', None) + self.hidden_categories = [] # migrate from config to db prefs - if self.hidden_categories is None: - self.hidden_categories = config['tag_browser_hidden_categories'] - db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories)) - else: - self.hidden_categories = set(self.hidden_categories) + if hidden_cats is None: + hidden_cats = config['tag_browser_hidden_categories'] + # strip out any non-existence field keys + for cat in hidden_cats: + if cat in db.field_metadata: + self.hidden_categories.append(cat) + db.prefs.set('tag_browser_hidden_categories', list(self.hidden_categories)) + self.hidden_categories = set(self.hidden_categories) old = getattr(self, '_model', None) if old is not None: @@ -370,14 +376,15 @@ class TagsView(QTreeView): # {{{ action='delete_user_category', key=key)) self.context_menu.addSeparator() # Hide/Show/Restore categories - if not key.startswith('@') or key.find('.') < 0: - self.context_menu.addAction(_('Hide category %s') % category, - partial(self.context_menu_handler, action='hide', - category=category)) +# if not key.startswith('@') or key.find('.') < 0: + self.context_menu.addAction(_('Hide category %s') % category, + partial(self.context_menu_handler, action='hide', + category=key)) if self.hidden_categories: m = self.context_menu.addMenu(_('Show category')) - for col in sorted(self.hidden_categories, key=sort_key): - m.addAction(col, + for col in sorted(self.hidden_categories, + key=lambda x: sort_key(self.db.field_metadata[x]['name'])): + m.addAction(self.db.field_metadata[col]['name'], partial(self.context_menu_handler, action='show', category=col)) # search by category @@ -540,6 +547,7 @@ class TagTreeItem(object): # {{{ self.id_set = set() self.is_gst = False self.boxed = False + self.icon_state_map = list(map(QVariant, icon_map)) if self.parent is not None: self.parent.append(self) if data is None: @@ -554,9 +562,11 @@ class TagTreeItem(object): # {{{ self.bold_font = QVariant(self.bold_font) self.category_key = category_key self.temporary = temporary + self.tag = Tag(data) + self.tag.is_hierarchical = category_key.startswith('@') elif self.type == self.TAG: icon_map[0] = data.icon - self.tag, self.icon_state_map = data, list(map(QVariant, icon_map)) + self.tag = data if tooltip: self.tooltip = tooltip + ' ' else: @@ -593,6 +603,8 @@ class TagTreeItem(object): # {{{ if role == Qt.EditRole: return QVariant(self.py_name) if role == Qt.DecorationRole: + if self.tag.state: + return self.icon_state_map[self.tag.state] return self.icon if role == Qt.FontRole: return self.bold_font @@ -642,11 +654,22 @@ class TagTreeItem(object): # {{{ ''' set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES ''' - if self.type == self.TAG: - if set_to is None: - self.tag.state = (self.tag.state + 1)%3 - else: - self.tag.state = set_to +# if self.type == self.TAG: + if set_to is None: + while True: + self.tag.state = (self.tag.state + 1)%5 + if self.tag.state == TAG_SEARCH_STATES['mark_plus'] or \ + self.tag.state == TAG_SEARCH_STATES['mark_minus']: + if self.tag.is_editable: + break + elif self.tag.state == TAG_SEARCH_STATES['mark_plusplus'] or\ + self.tag.state == TAG_SEARCH_STATES['mark_minusminus']: + if self.tag.is_hierarchical and len(self.children): + break + else: + break + else: + self.tag.state = set_to def child_tags(self): res = [] @@ -677,7 +700,8 @@ class TagsModel(QAbstractItemModel): # {{{ self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags'] self.drag_drop_finished = drag_drop_finished - self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('minus.png'))] + self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('plusplus.png')), + QIcon(I('minus.png')), QIcon(I('minusminus.png'))] self.db = db self.tags_view = parent self.hidden_categories = hidden_categories @@ -691,26 +715,33 @@ class TagsModel(QAbstractItemModel): # {{{ data = self.get_node_tree(config['sort_tags_by']) gst = db.prefs.get('grouped_search_terms', {}) - self.root_item = TagTreeItem() + self.root_item = TagTreeItem(icon_map=self.icon_state_map) self.category_nodes = [] 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 + for i, key in enumerate(self.row_map): + if self.hidden_categories: + if key in self.hidden_categories: + continue + found = False + for cat in self.hidden_categories: + if cat.startswith('@') and key.startswith(cat + '.'): + found = True + if found: + continue is_gst = False - if r.startswith('@') and r[1:] in gst: - tt = _(u'The grouped search term name is "{0}"').format(r[1:]) + if key.startswith('@') and key[1:] in gst: + tt = _(u'The grouped search term name is "{0}"').format(key[1:]) is_gst = True - elif r == 'news': + elif key == 'news': tt = '' else: - tt = _(u'The lookup/search name is "{0}"').format(r) + tt = _(u'The lookup/search name is "{0}"').format(key) - if r.startswith('@'): - path_parts = [p for p in r.split('.')] + if key.startswith('@'): + path_parts = [p for p in key.split('.')] path = '' last_category_node = self.root_item tree_root = self.category_node_tree @@ -719,9 +750,10 @@ class TagsModel(QAbstractItemModel): # {{{ if path not in category_node_map: node = TagTreeItem(parent=last_category_node, data=p[1:] if i == 0 else p, - category_icon=self.category_icon_map[r], - tooltip=tt if path == r else path, - category_key=path) + category_icon=self.category_icon_map[key], + tooltip=tt if path == key else path, + category_key=path, + icon_map=self.icon_state_map) last_category_node = node category_node_map[path] = node self.category_nodes.append(node) @@ -736,11 +768,12 @@ class TagsModel(QAbstractItemModel): # {{{ path += '.' else: node = TagTreeItem(parent=self.root_item, - data=self.categories[i], - category_icon=self.category_icon_map[r], - tooltip=tt, category_key=r) + data=self.categories[key], + category_icon=self.category_icon_map[key], + tooltip=tt, category_key=key, + icon_map=self.icon_state_map) node.is_gst = False - category_node_map[r] = node + category_node_map[key] = node last_category_node = node self.category_nodes.append(node) self.refresh(data=data) @@ -1015,7 +1048,7 @@ class TagsModel(QAbstractItemModel): # {{{ def get_node_tree(self, sort): old_row_map = self.row_map[:] self.row_map = [] - self.categories = [] + self.categories = {} # Get the categories if self.search_restriction: @@ -1062,7 +1095,7 @@ class TagsModel(QAbstractItemModel): # {{{ for category in tb_categories: if category in data: # The search category can come and go self.row_map.append(category) - self.categories.append(tb_categories[category]['name']) + self.categories[category] = tb_categories[category]['name'] if len(old_row_map) != 0 and len(old_row_map) != len(self.row_map): # A category has been added or removed. We must force a rebuild of @@ -1163,7 +1196,8 @@ class TagsModel(QAbstractItemModel): # {{{ sub_cat = TagTreeItem(parent=category, data = name, tooltip = None, temporary=True, category_icon = category_node.icon, - category_key=category_node.category_key) + category_key=category_node.category_key, + icon_map=self.icon_state_map) self.endInsertRows() else: # by 'first letter' cl = cl_list[idx] @@ -1173,7 +1207,8 @@ class TagsModel(QAbstractItemModel): # {{{ data = collapse_letter, category_icon = category_node.icon, tooltip = None, temporary=True, - category_key=category_node.category_key) + category_key=category_node.category_key, + icon_map=self.icon_state_map) node_parent = sub_cat else: node_parent = category @@ -1477,16 +1512,16 @@ class TagsModel(QAbstractItemModel): # {{{ def reset_all_states(self, except_=None): update_list = [] 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) +# 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) @@ -1503,13 +1538,11 @@ class TagsModel(QAbstractItemModel): # {{{ ''' if not index.isValid(): return False item = index.internalPointer() - if item.type == TagTreeItem.TAG: - item.toggle(set_to=set_to) - if exclusive: - self.reset_all_states(except_=item.tag) - self.dataChanged.emit(index, index) - return True - return False + item.toggle(set_to=set_to) + if exclusive: + self.reset_all_states(except_=item.tag) + self.dataChanged.emit(index, index) + return True def tokens(self): ans = [] @@ -1523,19 +1556,31 @@ class TagsModel(QAbstractItemModel): # {{{ # into the search string only once. The nodes_seen set helps us do that nodes_seen = set() + node_searches = {TAG_SEARCH_STATES['mark_plus'] : 'true', + TAG_SEARCH_STATES['mark_plusplus'] : '.true', + TAG_SEARCH_STATES['mark_minus'] : 'false', + TAG_SEARCH_STATES['mark_minusminus'] : '.false'} + for node in self.category_nodes: + if node.tag.state: + ans.append('%s:%s'%(node.category_key, node_searches[node.tag.state])) + key = node.category_key for tag_item in node.child_tags(): tag = tag_item.tag if tag.state != TAG_SEARCH_STATES['clear']: - prefix = ' not ' if tag.state == TAG_SEARCH_STATES['mark_minus'] \ - else '' + if tag.state == TAG_SEARCH_STATES['mark_minus'] or \ + tag.state == TAG_SEARCH_STATES['mark_minusminus']: + prefix = ' not ' + else: + prefix = '' category = tag.category if key != 'news' else 'tag' 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 = original_name(tag) - use_prefix = tag.is_hierarchical + use_prefix = tag.state in [TAG_SEARCH_STATES['mark_plusplus'], + TAG_SEARCH_STATES['mark_minusminus']] if category == 'tags': if name in tags_seen: continue diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index e626d446d2..0335c1d280 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -419,28 +419,23 @@ class ResultCache(SearchQueryParser): # {{{ def get_user_category_matches(self, location, query, candidates): res = set([]) - if self.db_prefs is None: + if self.db_prefs is None or len(query) < 2: return res user_cats = self.db_prefs.get('user_categories', []) c = set(candidates) - l = location.rfind('.') - if l > 0: - alt_loc = location[0:l] - alt_item = location[l+1:] + + if query.startswith('.'): + check_subcats = True + query = query[1:] else: - alt_loc = None + check_subcats = False + for key in user_cats: - if key == location or key.startswith(location + '.'): + if key == location or (check_subcats and key.startswith(location + '.')): for (item, category, ign) in user_cats[key]: s = self.get_matches(category, '=' + item, candidates=c) c -= s res |= s - elif key == alt_loc: - for (item, category, ign) in user_cats[key]: - if item == alt_item: - s = self.get_matches(category, '=' + item, candidates=c) - c -= s - res |= s if query == 'false': return candidates - res return res From 2aa275dad54555f8752069fd41cc4208e6975c87 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sun, 27 Feb 2011 11:43:10 +0000 Subject: [PATCH 2/2] 1) Fix problem with case sensitive matching when creating user categories 2) Fix problem in search where setting focus to the search box then removing it caused the item in history to replace the item in the search box case-insensitively. This broke tag state matching in the tag browser. --- src/calibre/gui2/dialogs/tag_categories.py | 6 ++++-- src/calibre/gui2/search_box.py | 4 ++++ src/calibre/gui2/tag_view.py | 13 +++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 9bddb817cf..899d3d1920 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -178,8 +178,10 @@ class TagCategories(QDialog, Ui_TagCategories): 'multiple periods in a row or spaces before ' 'or after periods.')).exec_() return False - for c in self.categories: - if strcmp(c, cat_name) == 0: + for c in sorted(self.categories.keys(), key=sort_key): + if strcmp(c, cat_name) == 0 or \ + (icu_lower(cat_name).startswith(icu_lower(c) + '.') and\ + not cat_name.startswith(c + '.')): error_dialog(self, _('Name already used'), _('That name is already used, perhaps with different case.')).exec_() return False diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 34be6cd276..5a4c34a5cd 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -217,11 +217,15 @@ class SearchBox2(QComboBox): # {{{ self.clear() else: self.normalize_state() + self.lineEdit().setCompleter(None) self.setEditText(txt) self.line_edit.end(False) if emit_changed: self.changed.emit() self._do_search(store_in_history=store_in_history) + c = QCompleter() + self.lineEdit().setCompleter(c) + c.setCompletionMode(c.PopupCompletion) self.focus_to_library.emit() finally: if not store_in_history: diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 034d88e02d..5986717753 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -654,7 +654,6 @@ class TagTreeItem(object): # {{{ ''' set_to: None => advance the state, otherwise a value from TAG_SEARCH_STATES ''' -# if self.type == self.TAG: if set_to is None: while True: self.tag.state = (self.tag.state + 1)%5 @@ -1319,16 +1318,19 @@ class TagsModel(QAbstractItemModel): # {{{ return False user_cats = self.db.prefs.get('user_categories', {}) + user_cat_keys_lower = [icu_lower(k) for k in user_cats] ckey = item.category_key[1:] + ckey_lower = icu_lower(ckey) dotpos = ckey.rfind('.') if dotpos < 0: nkey = val else: nkey = ckey[:dotpos+1] + val - for c in user_cats: - if c.startswith(ckey): + nkey_lower = icu_lower(nkey) + for c in sorted(user_cats.keys(), key=sort_key): + if icu_lower(c).startswith(ckey_lower): if len(c) == len(ckey): - if nkey in user_cats: + if nkey_lower in user_cat_keys_lower: error_dialog(self.tags_view, _('Rename user category'), _('The name %s is already used')%nkey, show=True) return False @@ -1336,7 +1338,7 @@ class TagsModel(QAbstractItemModel): # {{{ del user_cats[ckey] elif c[len(ckey)] == '.': rest = c[len(ckey):] - if (nkey + rest) in user_cats: + if icu_lower(nkey + rest) in user_cat_keys_lower: error_dialog(self.tags_view, _('Rename user category'), _('The name %s is already used')%(nkey+rest), show=True) return False @@ -1512,7 +1514,6 @@ class TagsModel(QAbstractItemModel): # {{{ def reset_all_states(self, except_=None): update_list = [] 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)