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