diff --git a/src/calibre/gui2/store/search/models.py b/src/calibre/gui2/store/search/models.py
index 975601c603..3d24499e94 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 = re.sub(r'(^|\s)(and|not|or|a|the|is|of|,)(\s|$)', ' ', query).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 adde77cce1..9f401d436c 100644
--- a/src/calibre/gui2/store/search/search.py
+++ b/src/calibre/gui2/store/search/search.py
@@ -13,7 +13,7 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, QLabel,
QVBoxLayout, QIcon, QWidget, QTabWidget, QGridLayout,
QComboBox)
-from calibre.gui2 import JSONConfig, info_dialog
+from calibre.gui2 import JSONConfig, info_dialog, error_dialog
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
@@ -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,24 @@ 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.button_layout.takeAt(0)
+ self.button_layout.setAlignment(Qt.AlignCenter)
+ self.button_layout.insertWidget(0, self.pi, 0, Qt.AlignCenter)
self.adv_search_button.setIcon(QIcon(I('search.png')))
self.configure.setIcon(QIcon(I('config.png')))
@@ -152,8 +165,19 @@ 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'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)
if not query.strip():
+ error_dialog(self, _('No query'),
+ _('You must enter a title, author or keyword to'
+ ' search for.'), show=True)
return
# Give the query to the results model so it can do
# futher filtering.
@@ -188,7 +212,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.
diff --git a/src/calibre/gui2/store/search/search.ui b/src/calibre/gui2/store/search/search.ui
index 83f42cd0d1..715f6788e7 100644
--- a/src/calibre/gui2/store/search/search.ui
+++ b/src/calibre/gui2/store/search/search.ui
@@ -14,49 +14,74 @@
Get Books
-
+
:/images/store.png:/images/store.png
true
-
- -
-
-
-
-
-
- Query:
-
-
-
- -
-
-
- ...
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
- Search
-
-
-
-
+
+ -
+
+
+ Search by title
+
+
- -
+
-
+
+
+ &Author:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ search_author
+
+
+
+ -
+
+
+ Search by author
+
+
+
+ -
+
+
+ ...
+
+
+
+ -
+
+
+ &Keyword:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ search_edit
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Search by any keyword
+
+
+
+ -
Qt::Horizontal
@@ -82,8 +107,8 @@
0
0
- 193
- 127
+ 204
+ 141
@@ -202,7 +227,7 @@
- -
+
-
-
@@ -234,7 +259,47 @@
-
- Close
+ &Close
+
+
+
+
+
+ -
+
+
+ &Title:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ search_title
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 24
+ 24
+
+
+
+
+ -
+
+
+ &Search
@@ -255,17 +320,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
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py
index df2c5ffd1d..997502a9d7 100644
--- a/src/calibre/gui2/widgets.py
+++ b/src/calibre/gui2/widgets.py
@@ -594,6 +594,9 @@ class HistoryLineEdit(QComboBox): # {{{
self.setInsertPolicy(self.NoInsert)
self.setMaxCount(10)
+ def setPlaceholderText(self, txt):
+ return self.lineEdit().setPlaceholderText(txt)
+
@property
def store_name(self):
return 'lineedit_history_'+self._name