From ff12f12ec939da44d9d4685a4ec9d33d90f2517a Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 18 Nov 2012 13:47:39 -0500 Subject: [PATCH 1/3] Store: Search, add author and title searches to search dialog because users can't be bothered to use the advanced search editor. --- src/calibre/gui2/store/search/search.py | 22 +++++++- src/calibre/gui2/store/search/search.ui | 69 ++++++++++++++++++------- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index adde77cce1..6f3b54c01c 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -31,6 +31,8 @@ class SearchDialog(QDialog, Ui_Dialog): self.setupUi(self) self.config = JSONConfig('store/search') + self.search_title.initialize('store_search_search_title') + self.search_author.initialize('store_search_search_author') self.search_edit.initialize('store_search_search') # Loads variables that store various settings. @@ -60,13 +62,22 @@ class SearchDialog(QDialog, Ui_Dialog): self.setup_store_checks() # Set the search query + # Title + self.search_title.setText(query) + self.search_title.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) + self.search_title.setMinimumContentsLength(25) + # Author + self.search_author.setText(query) + self.search_author.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) + self.search_author.setMinimumContentsLength(25) + # Keyword self.search_edit.setText(query) self.search_edit.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) self.search_edit.setMinimumContentsLength(25) # Create and add the progress indicator self.pi = ProgressIndicator(self, 24) - self.top_layout.addWidget(self.pi) + self.top_layout.addWidget(self.pi, 1, 3, Qt.AlignHCenter) self.adv_search_button.setIcon(QIcon(I('search.png'))) self.configure.setIcon(QIcon(I('config.png'))) @@ -152,7 +163,14 @@ class SearchDialog(QDialog, Ui_Dialog): self.results_view.model().clear_results() # Don't start a search if there is nothing to search for. - query = unicode(self.search_edit.text()) + query = [] + if self.search_title.text(): + query.append(u'title:"~%s"' % unicode(self.search_title.text()).replace(" ", ".*")) + if self.search_author.text(): + query.append(u'author:"~%s"' % unicode(self.search_author.text()).replace(" ", ".*")) + if self.search_edit.text(): + query.append(unicode(self.search_edit.text())) + query = " ".join(query) if not query.strip(): return # Give the query to the results model so it can do diff --git a/src/calibre/gui2/store/search/search.ui b/src/calibre/gui2/store/search/search.ui index 83f42cd0d1..5667162de4 100644 --- a/src/calibre/gui2/store/search/search.ui +++ b/src/calibre/gui2/store/search/search.ui @@ -22,22 +22,8 @@ - - - - - Query: - - - - - - - ... - - - - + + @@ -47,13 +33,56 @@ - + + + + Search + + + + + + + Author: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Title: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Keyword: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ... + + + @@ -255,17 +284,19 @@ + search_title + search_author + adv_search_button search_edit search - results_view store_list select_all_stores select_invert_stores select_none_stores + results_view configure open_external close - adv_search_button From d129c1d81fceb6d99c13faf5dc6ed8f18693d338 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 18 Nov 2012 16:41:22 -0500 Subject: [PATCH 2/3] Store: Search, tokenize author and search for matches within. --- src/calibre/gui2/store/search/models.py | 47 +++++++++++++++++++------ src/calibre/gui2/store/search/search.py | 5 +-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/calibre/gui2/store/search/models.py b/src/calibre/gui2/store/search/models.py index 975601c603..9c56c2cab0 100644 --- a/src/calibre/gui2/store/search/models.py +++ b/src/calibre/gui2/store/search/models.py @@ -16,8 +16,6 @@ from calibre.gui2 import NONE, FunctionDispatcher from calibre.gui2.store.search_result import SearchResult from calibre.gui2.store.search.download_thread import DetailsThreadPool, \ CoverThreadPool -from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \ - REGEXP_MATCH from calibre.utils.icu import sort_key from calibre.utils.search_query_parser import SearchQueryParser @@ -153,7 +151,7 @@ class Matches(QAbstractItemModel): mod_query = query # Remove filter identifiers # Remove the prefix. - for loc in ('all', 'author', 'authors', 'title'): + for loc in ('all', 'author', 'author2', 'authors', 'title'): query = re.sub(r'%s:"(?P[^\s"]+)"' % loc, '\g', query) query = query.replace('%s:' % loc, '') # Remove the prefix and search text. @@ -301,11 +299,16 @@ class Matches(QAbstractItemModel): class SearchFilter(SearchQueryParser): + CONTAINS_MATCH = 0 + EQUALS_MATCH = 1 + REGEXP_MATCH = 2 + IN_MATCH = 3 USABLE_LOCATIONS = [ 'all', 'affiliate', 'author', + 'author2', 'authors', 'cover', 'download', @@ -331,6 +334,26 @@ class SearchFilter(SearchQueryParser): def universal_set(self): return self.srs + def _match(self, query, value, matchkind): + for t in value: + try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished + t = icu_lower(t) + if matchkind == self.EQUALS_MATCH: + if query == t: + return True + elif matchkind == self.REGEXP_MATCH: + if re.search(query, t, re.I|re.UNICODE): + return True + elif matchkind == self.CONTAINS_MATCH: + if query in t: + return True + elif matchkind == self.IN_MATCH: + if t in query: + return True + except re.error: + pass + return False + def get_matches(self, location, query): query = query.strip() location = location.lower().strip() @@ -341,17 +364,17 @@ class SearchFilter(SearchQueryParser): elif location == 'formats': location = 'format' - matchkind = CONTAINS_MATCH + matchkind = self.CONTAINS_MATCH if len(query) > 1: if query.startswith('\\'): query = query[1:] elif query.startswith('='): - matchkind = EQUALS_MATCH + matchkind = self.EQUALS_MATCH query = query[1:] elif query.startswith('~'): - matchkind = REGEXP_MATCH + matchkind = self.REGEXP_MATCH query = query[1:] - if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D + if matchkind != self.REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D query = query.lower() if location not in self.USABLE_LOCATIONS: @@ -372,6 +395,7 @@ class SearchFilter(SearchQueryParser): } for x in ('author', 'download', 'format'): q[x+'s'] = q[x] + q['author2'] = q['author'] # make the price in query the same format as result if location == 'price': @@ -415,16 +439,19 @@ class SearchFilter(SearchQueryParser): ### Can't separate authors because comma is used for name sep and author sep ### Exact match might not get what you want. For that reason, turn author ### exactmatch searches into contains searches. - if locvalue == 'author' and matchkind == EQUALS_MATCH: - m = CONTAINS_MATCH + if locvalue == 'author' and matchkind == self.EQUALS_MATCH: + m = self.CONTAINS_MATCH else: m = matchkind if locvalue == 'format': vals = accessor(sr).split(',') + elif locvalue == 'author2': + m = self.IN_MATCH + vals = accessor(sr).replace(',', ' ').split(' ') else: vals = [accessor(sr)] - if _match(query, vals, m): + if self._match(query, vals, m): matches.add(sr) break except ValueError: # Unicode errors diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index 6f3b54c01c..a1d1218b02 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -167,7 +167,8 @@ class SearchDialog(QDialog, Ui_Dialog): if self.search_title.text(): query.append(u'title:"~%s"' % unicode(self.search_title.text()).replace(" ", ".*")) if self.search_author.text(): - query.append(u'author:"~%s"' % unicode(self.search_author.text()).replace(" ", ".*")) + query.append(u'author2:"%s"' % unicode(self.search_author.text())) + #query.append(u'author:"~%s"' % unicode(self.search_author.text()).replace(" ", ".*")) if self.search_edit.text(): query.append(unicode(self.search_edit.text())) query = " ".join(query) @@ -206,7 +207,7 @@ class SearchDialog(QDialog, Ui_Dialog): query = query.replace('>', '') query = query.replace('<', '') # Remove the prefix. - for loc in ('all', 'author', 'authors', 'title'): + for loc in ('all', 'author', 'author2', 'authors', 'title'): query = re.sub(r'%s:"(?P[^\s"]+)"' % loc, '\g', query) query = query.replace('%s:' % loc, '') # Remove the prefix and search text. From cdcf2978296158779229b2d02aaaf2660edc49a3 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 18 Nov 2012 16:44:47 -0500 Subject: [PATCH 3/3] Store: Search, Remove common words from author matching. --- src/calibre/gui2/store/search/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/search/models.py b/src/calibre/gui2/store/search/models.py index 9c56c2cab0..3d24499e94 100644 --- a/src/calibre/gui2/store/search/models.py +++ b/src/calibre/gui2/store/search/models.py @@ -448,7 +448,7 @@ class SearchFilter(SearchQueryParser): vals = accessor(sr).split(',') elif locvalue == 'author2': m = self.IN_MATCH - vals = accessor(sr).replace(',', ' ').split(' ') + vals = re.sub(r'(^|\s)(and|not|or|a|the|is|of|,)(\s|$)', ' ', query).split(' ') else: vals = [accessor(sr)] if self._match(query, vals, m):