From 470f62174888ea24a9f108c61c693acd79d10a46 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 24 Aug 2016 17:06:33 +0530 Subject: [PATCH] Add an option in Preferences->Searching to make searching case-sensitive --- src/calibre/db/search.py | 24 +++++++++++---------- src/calibre/gui2/preferences/search.py | 14 ++++++++++++- src/calibre/gui2/preferences/search.ui | 29 ++++++++++++++++---------- src/calibre/utils/config_base.py | 5 ++++- 4 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index 5a41d85255..7d33bb9f92 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -26,7 +26,7 @@ REGEXP_MATCH = 2 # Utils {{{ -def _matchkind(query): +def _matchkind(query, case_sensitive=False): matchkind = CONTAINS_MATCH if (len(query) > 1): if query.startswith('\\'): @@ -38,12 +38,12 @@ def _matchkind(query): matchkind = REGEXP_MATCH query = query[1:] - if matchkind != REGEXP_MATCH: + if not case_sensitive and matchkind != REGEXP_MATCH: # leave case in regexps because it can be significant e.g. \S \W \D query = icu_lower(query) return matchkind, query -def _match(query, value, matchkind, use_primary_find_in_search=True): +def _match(query, value, matchkind, use_primary_find_in_search=True, case_sensitive=False): if query.startswith('..'): query = query[1:] sq = query[1:] @@ -52,7 +52,8 @@ def _match(query, value, matchkind, use_primary_find_in_search=True): internal_match_ok = False for t in value: try: # ignore regexp exceptions, required because search-ahead tries before typing is finished - t = icu_lower(t) + if not case_sensitive: + t = icu_lower(t) if (matchkind == EQUALS_MATCH): if internal_match_ok: if query == t: @@ -69,10 +70,10 @@ def _match(query, value, matchkind, use_primary_find_in_search=True): elif query == t: return True elif matchkind == REGEXP_MATCH: - if re.search(query, t, re.I|re.UNICODE): + if re.search(query, t, re.UNICODE if case_sensitive else re.I|re.UNICODE): return True elif matchkind == CONTAINS_MATCH: - if use_primary_find_in_search: + if not case_sensitive and use_primary_find_in_search: if primary_contains(query, t): return True elif query in t: @@ -598,7 +599,8 @@ class Parser(SearchQueryParser): # {{{ return self.get_user_category_matches(location[1:], icu_lower(query), candidates) # Everything else (and 'all' matches) - matchkind, query = _matchkind(query) + case_sensitive = prefs['case_sensitive'] + matchkind, query = _matchkind(query, case_sensitive=case_sensitive) all_locs = set() text_fields = set() field_metadata = {} @@ -644,12 +646,12 @@ class Parser(SearchQueryParser): # {{{ rm = {v.lower():k for k,v in lm.iteritems()} q = rm.get(query, query) - if matchkind == CONTAINS_MATCH and q in {'true', 'false'}: + if matchkind == CONTAINS_MATCH and q.lower() in {'true', 'false'}: found = set() for val, book_ids in self.field_iter(location, current_candidates): if val and (not hasattr(val, 'strip') or val.strip()): found |= book_ids - matches |= (found if q == 'true' else (current_candidates-found)) + matches |= (found if q.lower() == 'true' else (current_candidates-found)) continue dt = field_metadata.get(location, {}).get('datatype', None) @@ -679,14 +681,14 @@ class Parser(SearchQueryParser): # {{{ if val is not None: if isinstance(val, basestring): val = (val,) - if _match(q, val, matchkind, use_primary_find_in_search=upf): + if _match(q, val, matchkind, use_primary_find_in_search=upf, case_sensitive=case_sensitive): matches |= book_ids if location == 'series_sort': book_lang_map = self.dbcache.fields['languages'].book_value_map for val, book_ids in self.dbcache.fields['series'].iter_searchable_values_for_sort(current_candidates, book_lang_map): if val is not None: - if _match(q, (val,), matchkind, use_primary_find_in_search=upf): + if _match(q, (val,), matchkind, use_primary_find_in_search=upf, case_sensitive=case_sensitive): matches |= book_ids return matches diff --git a/src/calibre/gui2/preferences/search.py b/src/calibre/gui2/preferences/search.py index 8e6da039f5..5e879a8081 100644 --- a/src/calibre/gui2/preferences/search.py +++ b/src/calibre/gui2/preferences/search.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' from PyQt5.Qt import QApplication from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \ - CommaSeparatedList + CommaSeparatedList, AbortCommit from calibre.gui2.preferences.search_ui import Ui_Form from calibre.gui2 import config, error_dialog, gprefs from calibre.utils.config import prefs @@ -29,6 +29,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('show_highlight_toggle_button', gprefs) r('limit_search_columns', prefs) r('use_primary_find_in_search', prefs) + r('case_sensitive', prefs) r('limit_search_columns_to', prefs, setting=CommaSeparatedList) fl = db.field_metadata.get_search_terms() self.opt_limit_search_columns_to.update_items_cache(fl) @@ -101,6 +102,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.muc_changed = False self.opt_grouped_search_make_user_categories.lineEdit().editingFinished.connect( self.muc_box_changed) + self.opt_case_sensitive.toggled.connect(self.case_sensitive_toggled) + self.case_sensitive_toggled() + + def case_sensitive_toggled(self): + if self.opt_case_sensitive.isChecked(): + self.opt_use_primary_find_in_search.setChecked(False) def set_similar_fields(self, initial=False): self.set_similar('similar_authors_search_key', initial=initial) @@ -211,6 +218,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.gst_value.blockSignals(False) def commit(self): + if self.opt_case_sensitive.isChecked() and self.opt_use_primary_find_in_search.isChecked(): + error_dialog(self, _('Incompatible options'), _( + 'The option to have un-accented characters match accented characters has no effect' + ' if you also turn on case-sensitive searching. So only turn on one of those options'), show=True) + raise AbortCommit() if self.gst_changed: self.db.new_api.set_pref('grouped_search_terms', self.gst) self.db.field_metadata.add_grouped_search_terms(self.gst) diff --git a/src/calibre/gui2/preferences/search.ui b/src/calibre/gui2/preferences/search.ui index abd563a78b..c00612777a 100644 --- a/src/calibre/gui2/preferences/search.ui +++ b/src/calibre/gui2/preferences/search.ui @@ -14,7 +14,7 @@ Form - + What to search by default @@ -63,7 +63,7 @@ - + What to search when searching similar books @@ -152,7 +152,7 @@ - + @@ -175,13 +175,6 @@ - - - - Unaccented characters match accented characters - - - @@ -189,7 +182,7 @@ - + Grouped Search Terms @@ -313,6 +306,20 @@ to be shown as user categories + + + + Case sensitive searching + + + + + + + Unaccented characters match accented characters + + + diff --git a/src/calibre/utils/config_base.py b/src/calibre/utils/config_base.py index 3de816229e..bfc8d7b92f 100644 --- a/src/calibre/utils/config_base.py +++ b/src/calibre/utils/config_base.py @@ -437,7 +437,10 @@ def create_global_prefs(conf_obj=None): u' English, searching for n will match %s and n, but if ' 'your language is Spanish it will only match n. Note that ' 'this is much slower than a simple search on very large ' - 'libraries.')%u'\xf1') + 'libraries. Note that this option will have no effect if you turn ' + 'on case-sensitive searching')%u'\xf1') + c.add_opt('case_sensitive', default=False, help=_( + 'Make searches case-sensitive')) c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.') return c