From aa665e91f05613d8b05585ebc988435eea25de7d Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 Jan 2011 09:07:22 +0000 Subject: [PATCH 1/2] Commit before merge --- src/calibre/gui2/dialogs/tag_categories.py | 11 ++++++----- src/calibre/gui2/tag_view.py | 12 +++++++++++- src/calibre/library/caches.py | 11 +++-------- src/calibre/library/field_metadata.py | 15 +++++++++------ src/calibre/utils/search_query_parser.py | 3 +++ 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 7573f04012..90afe6046a 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -9,7 +9,7 @@ from PyQt4.QtGui import QDialog, QIcon, QListWidgetItem from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories from calibre.gui2.dialogs.confirm_delete import confirm from calibre.constants import islinux -from calibre.utils.icu import sort_key +from calibre.utils.icu import sort_key, strcmp class Item: def __init__(self, name, label, index, icon, exists): @@ -160,15 +160,17 @@ class TagCategories(QDialog, Ui_TagCategories): cat_name = unicode(self.input_box.text()).strip() if cat_name == '': return False + for c in self.categories: + if strcmp(c, cat_name) == 0: + cat_name = c if cat_name not in self.categories: self.category_box.clear() self.current_cat_name = cat_name self.categories[cat_name] = [] self.applied_items = [] self.populate_category_list() - self.category_box.setCurrentIndex(self.category_box.findText(cat_name)) - else: - self.select_category(self.category_box.findText(cat_name)) + self.input_box.clear() + self.category_box.setCurrentIndex(self.category_box.findText(cat_name)) return True def del_category(self): @@ -196,7 +198,6 @@ class TagCategories(QDialog, Ui_TagCategories): def accept(self): self.save_category() - self.db.prefs['user_categories'] = self.categories QDialog.accept(self) def save_category(self): diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 2160e13b65..80499c9f16 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -1187,9 +1187,19 @@ class TagBrowserMixin(object): # {{{ self.do_user_categories_edit()) def do_user_categories_edit(self, on_category=None): - d = TagCategories(self, self.library_view.model().db, on_category) + db = self.library_view.model().db + d = TagCategories(self, db, on_category) d.exec_() if d.result() == d.Accepted: + db.prefs.set('user_categories', d.categories) + st = db.field_metadata.get_search_terms() + for k in d.categories: + key = '@' + k + if key in st: + continue + db.field_metadata.add_user_category(key, k) + db.data.sqp_initialize(db.field_metadata.get_search_terms(), + optimize=True) self.tags_view.set_new_model() self.tags_view.recount() diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 7c935a4320..55045e7f98 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -197,15 +197,15 @@ class ResultCache(SearchQueryParser): # {{{ self.first_sort = True self.search_restriction = '' self.field_metadata = field_metadata - self.all_search_locations = field_metadata.get_search_terms() - SearchQueryParser.__init__(self, self.all_search_locations, optimize=True) + all_search_locations = field_metadata.get_search_terms() + SearchQueryParser.__init__(self, all_search_locations, optimize=True) self.build_date_relop_dict() self.build_numeric_relop_dict() def break_cycles(self): self._data = self.field_metadata = self.FIELD_MAP = \ self.numeric_search_relops = self.date_search_relops = \ - self.all_search_locations = self.db_prefs = None + self.db_prefs = None def __getitem__(self, row): @@ -424,11 +424,6 @@ class ResultCache(SearchQueryParser): # {{{ if self.db_prefs is None: return res user_cats = self.db_prefs.get('user_categories', []) - # translate the case of the location - for loc in user_cats: - if location == icu_lower(loc): - location = loc - break if location not in user_cats: return res c = set(candidates) diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index 224b6aa79f..b1b7b7754b 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -474,11 +474,10 @@ class FieldMetadata(dict): for key in list(self._tb_cats.keys()): val = self._tb_cats[key] if val['is_category'] and val['kind'] in ('user', 'search'): + for k in self._tb_cats[key]['search_terms']: + if k in self._search_term_map: + del self._search_term_map[k] del self._tb_cats[key] - if key in self._search_term_map: - del self._search_term_map[key] - if key in self._search_term_map: - del self._search_term_map[key] def cc_series_index_column_for(self, key): return self._tb_cats[key]['rec_index'] + 1 @@ -486,12 +485,15 @@ class FieldMetadata(dict): def add_user_category(self, label, name): if label in self._tb_cats: raise ValueError('Duplicate user field [%s]'%(label)) + st = [label] + if icu_lower(label) != label: + st.append(icu_lower(label)) self._tb_cats[label] = {'table':None, 'column':None, 'datatype':None, 'is_multiple':None, 'kind':'user', 'name':name, - 'search_terms':[label],'is_custom':False, + 'search_terms':st, 'is_custom':False, 'is_category':True} - self._add_search_terms_to_map(label, [label]) + self._add_search_terms_to_map(label, st) def add_search_category(self, label, name): if label in self._tb_cats: @@ -524,6 +526,7 @@ class FieldMetadata(dict): if terms is not None: for t in terms: if t in self._search_term_map: + print self._search_term_map raise ValueError('Attempt to add duplicate search term "%s"'%t) self._search_term_map[t] = key diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py index 447ff8cd14..6333cfde06 100644 --- a/src/calibre/utils/search_query_parser.py +++ b/src/calibre/utils/search_query_parser.py @@ -119,6 +119,9 @@ class SearchQueryParser(object): return failed def __init__(self, locations, test=False, optimize=False): + self.sqp_initialize(locations, test=test, optimize=optimize) + + def sqp_initialize(self, locations, test=False, optimize=False): self._tests_failed = False self.optimize = optimize # Define a token From 6e1e2fbd752cdd06a9e07bf5e0c91748a17fd313 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 26 Jan 2011 11:24:38 +0000 Subject: [PATCH 2/2] 1) add possibility to rename a user category in manage categories 2) reinitialize the search_query_parser when the user categories change 3) add code to db2 to rename user categories that differ in case only 4) fix up manage categories somewhat --- src/calibre/gui2/dialogs/tag_categories.py | 49 +++- src/calibre/gui2/dialogs/tag_categories.ui | 275 ++++++++++----------- src/calibre/gui2/tag_view.py | 12 +- src/calibre/library/database2.py | 23 ++ src/calibre/library/field_metadata.py | 10 +- 5 files changed, 211 insertions(+), 158 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 90afe6046a..65272e0037 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -2,12 +2,14 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' +from functools import partial from PyQt4.QtCore import SIGNAL, Qt from PyQt4.QtGui import QDialog, QIcon, QListWidgetItem from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories from calibre.gui2.dialogs.confirm_delete import confirm +from calibre.gui2 import error_dialog from calibre.constants import islinux from calibre.utils.icu import sort_key, strcmp @@ -102,12 +104,13 @@ class TagCategories(QDialog, Ui_TagCategories): self.category_filter_box.addItem(v) self.current_cat_name = None - self.connect(self.apply_button, SIGNAL('clicked()'), self.apply_tags) - self.connect(self.unapply_button, SIGNAL('clicked()'), self.unapply_tags) - self.connect(self.add_category_button, SIGNAL('clicked()'), self.add_category) - self.connect(self.category_box, SIGNAL('currentIndexChanged(int)'), self.select_category) - self.connect(self.category_filter_box, SIGNAL('currentIndexChanged(int)'), self.display_filtered_categories) - self.connect(self.delete_category_button, SIGNAL('clicked()'), self.del_category) + self.apply_button.clicked.connect(partial(self.apply_tags, node=None)) + self.unapply_button.clicked.connect(partial(self.unapply_tags, node=None)) + self.add_category_button.clicked.connect(self.add_category) + self.rename_category_button.clicked.connect(self.rename_category) + self.category_box.currentIndexChanged[int].connect(self.select_category) + self.category_filter_box.currentIndexChanged[int].connect(self.display_filtered_categories) + self.delete_category_button.clicked.connect(self.del_category) if islinux: self.available_items_box.itemDoubleClicked.connect(self.apply_tags) else: @@ -119,6 +122,9 @@ class TagCategories(QDialog, Ui_TagCategories): l = self.category_box.findText(on_category) if l >= 0: self.category_box.setCurrentIndex(l) + if self.current_cat_name is None: + self.category_box.setCurrentIndex(0) + self.select_category(0) def make_list_widget(self, item): n = item.name if item.exists else item.name + _(' (not on any book)') @@ -162,7 +168,9 @@ class TagCategories(QDialog, Ui_TagCategories): return False for c in self.categories: if strcmp(c, cat_name) == 0: - cat_name = c + error_dialog(self, _('Name already used'), + _('That name is already used, perhaps with different case.')).exec_() + return False if cat_name not in self.categories: self.category_box.clear() self.current_cat_name = cat_name @@ -173,6 +181,27 @@ class TagCategories(QDialog, Ui_TagCategories): self.category_box.setCurrentIndex(self.category_box.findText(cat_name)) return True + def rename_category(self): + self.save_category() + cat_name = unicode(self.input_box.text()).strip() + if cat_name == '': + return False + if not self.current_cat_name: + return False + for c in self.categories: + if strcmp(c, cat_name) == 0: + error_dialog(self, _('Name already used'), + _('That name is already used, perhaps with different case.')).exec_() + return False + # The order below is important because of signals + self.categories[cat_name] = self.categories[self.current_cat_name] + del self.categories[self.current_cat_name] + self.current_cat_name = None + self.populate_category_list() + self.input_box.clear() + self.category_box.setCurrentIndex(self.category_box.findText(cat_name)) + return True + def del_category(self): if self.current_cat_name is not None: if not confirm('

'+_('The current tag category will be ' @@ -209,5 +238,7 @@ class TagCategories(QDialog, Ui_TagCategories): self.categories[self.current_cat_name] = l def populate_category_list(self): - for n in sorted(self.categories.keys(), key=sort_key): - self.category_box.addItem(n) + self.category_box.blockSignals(True) + self.category_box.clear() + self.category_box.addItems(sorted(self.categories.keys(), key=sort_key)) + self.category_box.blockSignals(False) \ No newline at end of file diff --git a/src/calibre/gui2/dialogs/tag_categories.ui b/src/calibre/gui2/dialogs/tag_categories.ui index a9319cc5f5..0b17ccac05 100644 --- a/src/calibre/gui2/dialogs/tag_categories.ui +++ b/src/calibre/gui2/dialogs/tag_categories.ui @@ -18,7 +18,139 @@ :/images/chapters.png:/images/chapters.png + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + Category name: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + category_box + + + + + + + + 160 + 0 + + + + + 145 + 0 + + + + Select a category to edit + + + false + + + + + + + + + Delete this selected tag category + + + ... + + + + :/images/minus.png:/images/minus.png + + + + + + + + + + 60 + 0 + + + + Enter a category name, then use the add button or the rename button + + + + + + + Add a new category + + + ... + + + + :/images/plus.png:/images/plus.png + + + + + + + + + + Rename the current category to the what is in the box + + + ... + + + + :/images/edit-undo.png:/images/edit-undo.png + + + + + + + + Category filter: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Select the content kind of the new category + + + + + + @@ -66,7 +198,7 @@ - + @@ -110,7 +242,7 @@ - + @@ -151,7 +283,7 @@ - + @@ -195,7 +327,7 @@ - + Qt::Horizontal @@ -208,141 +340,6 @@ - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - Category name: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - category_box - - - - - - - - 160 - 0 - - - - - 145 - 0 - - - - Select a category to edit - - - false - - - - - - - Delete this selected tag category - - - ... - - - - :/images/minus.png:/images/minus.png - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 60 - 0 - - - - Enter a new category name. Select the kind before adding it. - - - - - - - Add the new category - - - ... - - - - :/images/plus.png:/images/plus.png - - - - - - - Qt::Horizontal - - - - 20 - 20 - - - - - - - - Category filter: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - Select the content kind of the new category - - - - - diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 80499c9f16..3fe6e3cf93 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -576,10 +576,7 @@ class TagsModel(QAbstractItemModel): # {{{ for i, r in enumerate(self.row_map): if self.hidden_categories and self.categories[i] in self.hidden_categories: continue - if self.db.field_metadata[r]['kind'] != 'user': - tt = _('The lookup/search name is "{0}"').format(r) - else: - tt = '' + tt = _(u'The lookup/search name is "{0}"').format(r) TagTreeItem(parent=self.root_item, data=self.categories[i], category_icon=self.category_icon_map[r], @@ -1192,12 +1189,9 @@ class TagBrowserMixin(object): # {{{ d.exec_() if d.result() == d.Accepted: db.prefs.set('user_categories', d.categories) - st = db.field_metadata.get_search_terms() + db.field_metadata.remove_user_categories() for k in d.categories: - key = '@' + k - if key in st: - continue - db.field_metadata.add_user_category(key, k) + db.field_metadata.add_user_category('@' + k, k) db.data.sqp_initialize(db.field_metadata.get_search_terms(), optimize=True) self.tags_view.set_new_model() diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index ed47abbdb3..dbdeabf3d4 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -186,6 +186,29 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): migrate_preference('saved_searches', {}) set_saved_searches(self, 'saved_searches') + # Rename any user categories with names that differ only in case + user_cats = self.prefs.get('user_categories', []) + catmap = {} + for uc in user_cats: + ucl = icu_lower(uc) + if ucl not in catmap: + catmap[ucl] = [] + catmap[ucl].append(uc) + cats_changed = False + for uc in catmap: + if len(catmap[uc]) > 1: + prints('found user category case overlap', catmap[uc]) + cat = catmap[uc][0] + suffix = 1 + while icu_lower((cat + unicode(suffix))) in catmap: + suffix += 1 + prints('Renaming user category %s to %s'%(cat, cat+unicode(suffix))) + user_cats[cat + unicode(suffix)] = user_cats[cat] + del user_cats[cat] + cats_changed = True + if cats_changed: + self.prefs.set('user_categories', user_cats) + load_user_template_functions(self.prefs.get('user_template_functions', [])) self.conn.executescript(''' diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index b1b7b7754b..d64ea54424 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -479,6 +479,15 @@ class FieldMetadata(dict): del self._search_term_map[k] del self._tb_cats[key] + def remove_user_categories(self): + for key in list(self._tb_cats.keys()): + val = self._tb_cats[key] + if val['is_category'] and val['kind'] == 'user': + for k in self._tb_cats[key]['search_terms']: + if k in self._search_term_map: + del self._search_term_map[k] + del self._tb_cats[key] + def cc_series_index_column_for(self, key): return self._tb_cats[key]['rec_index'] + 1 @@ -526,7 +535,6 @@ class FieldMetadata(dict): if terms is not None: for t in terms: if t in self._search_term_map: - print self._search_term_map raise ValueError('Attempt to add duplicate search term "%s"'%t) self._search_term_map[t] = key