diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index 4d66978ba2..403bb7f287 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -1,33 +1,42 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' import re -from PyQt4.QtGui import QWidget, QDialog, QVBoxLayout -from PyQt4.QtCore import SIGNAL +from PyQt4.QtGui import QDialog from calibre.gui2.dialogs.search_ui import Ui_Dialog -from calibre.gui2.dialogs.search_item_ui import Ui_Form from calibre.gui2 import qstring_to_unicode -class SearchItem(Ui_Form, QWidget): - - FIELDS = { - _('Title') : 'title:', - _('Author') : 'author:', - _('Publisher') : 'publisher:', - _('Tag') : 'tag:', - _('Series') : 'series:', - _('Format') : 'format:', - _('Comments') : 'comments:', - _('Any') :'' - } - - def __init__(self, parent): - QWidget.__init__(self, parent) - self.setupUi(self) - for field in self.FIELDS.keys(): - self.field.addItem(field) - +class SearchDialog(QDialog, Ui_Dialog): + + def __init__(self, *args): + QDialog.__init__(self, *args) + self.setupUi(self) + + def tokens(self, raw): + phrases = re.findall(r'\s+".*?"\s+', raw) + for f in phrases: + raw = raw.replace(f, ' ') + return [t.strip() for t in phrases + raw.split()] + + def search_string(self): + all, any, phrase, none = map(lambda x: unicode(x.text()), (self.all, self.any, self.phrase, self.none)) + all, any, none = map(self.tokens, (all, any, none)) + phrase = phrase.strip() + all = ' and '.join(all) + any = ' or '.join(any) + none = ' and not '.join(none) + ans = '' + if phrase: + ans += '"%s"'%phrase + if all: + ans += (' and ' if ans else '') + all + if none: + ans += (' and not ' if ans else '') + none + if any: + ans += (' or ' if ans else '') + any + return ans + def token(self): txt = qstring_to_unicode(self.text.text()).strip() if txt: @@ -37,44 +46,4 @@ class SearchItem(Ui_Form, QWidget): if re.search(r'\s', tok): tok = '"%s"'%tok return tok - - - -class SearchDialog(Ui_Dialog, QDialog): - def __init__(self, parent): - QDialog.__init__(self, parent) - self.setupUi(self) - - self.tokens = [] - self.token_layout = QVBoxLayout() - self.search_items_frame.setLayout(self.token_layout) - self.add_token() - self.add_token() - - self.connect(self.fewer_button, SIGNAL('clicked()'), self.remove_token) - self.connect(self.more_button, SIGNAL('clicked()'), self.add_token) - - def remove_token(self): - if self.tokens: - tok = self.tokens[-1] - self.tokens = self.tokens[:-1] - self.token_layout.removeWidget(tok) - tok.setVisible(False) - - def add_token(self): - tok = SearchItem(self) - self.token_layout.addWidget(tok) - self.tokens.append(tok) - - def search_string(self): - ans = [] - for tok in self.tokens: - token = tok.token() - if token: - ans.append(token) - ans = ' '.join(ans) - if self.match_any.isChecked(): - ans = '['+ans+']' - return ans - \ No newline at end of file diff --git a/src/calibre/gui2/dialogs/search.ui b/src/calibre/gui2/dialogs/search.ui index 38fb8bd67d..f1ba9e60ae 100644 --- a/src/calibre/gui2/dialogs/search.ui +++ b/src/calibre/gui2/dialogs/search.ui @@ -5,107 +5,113 @@ 0 0 - 578 - 474 + 667 + 391 Advanced Search - :/images/search.svg + + :/images/search.svg:/images/search.svg - - - - - - - - - Match a&ll of the following criteria - - - true - - - - - - - Match a&ny of the following criteria - - - - - - - - - Search criteria - - + + + + + Find entries that have... + + + + - - - QFrame::StyledPanel + + + &All these words: - - QFrame::Raised + + all - - - - - More - - - :/images/plus.svg - - - - - - - Fewer - - - :/images/list_remove.svg - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok - - - - + + + + + + + This exact &phrase: + + + all + + + + + + + + + + + + + + &One or more of these words: + + + all + + + + + + + + + + + + + + + But dont show entries that have... + + + + + + + + Any of these &unwanted words: + + + all + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index a973bb85fa..eb0b7298b8 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -886,6 +886,9 @@ class SearchBox(QLineEdit): ans = '[' + ans + ']' self.set_search_string(ans) + def search_from_tags(self, tags, all): + joiner = ' and ' if all else ' or ' + self.set_search_string(joiner.join(tags)) def set_search_string(self, txt): self.normalize_state() diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 14bfd938be..7ffc1e1148 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -253,7 +253,7 @@ class Main(MainWindow, Ui_MainWindow): self.popularity.setVisible(False) self.tags_view.set_database(db, self.match_all, self.popularity) self.connect(self.tags_view, SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'), - self.search.search_from_tokens) + self.search.search_from_tags) self.connect(self.status_bar.tag_view_button, SIGNAL('toggled(bool)'), self.toggle_tags_view) self.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self.tags_view.model().reinit) diff --git a/src/calibre/gui2/tags.py b/src/calibre/gui2/tags.py index d7d6dc62d9..0a08411056 100644 --- a/src/calibre/gui2/tags.py +++ b/src/calibre/gui2/tags.py @@ -96,9 +96,8 @@ class TagsModel(QAbstractItemModel): for key in self.row_map.values(): for tag in self._data[key]: if tag.state > 0: - if tag.state == 2: - tag = '!'+tag - ans.append((key, tag)) + prefix = ' not ' if tag.state == 2 else '' + ans.append('%s%s:"%s"'%(prefix, key, tag)) return ans def index(self, row, col, parent=QModelIndex()): diff --git a/src/calibre/library/server.py b/src/calibre/library/server.py index f4a793bdea..6840b6aad4 100644 --- a/src/calibre/library/server.py +++ b/src/calibre/library/server.py @@ -50,7 +50,7 @@ class LibraryServer(object): LIBRARY = MarkupTemplate(textwrap.dedent('''\ - ${Markup(book)} diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index f53584fb1b..f852c30cec 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -188,7 +188,15 @@ You can search all the metadata by entering search terms in the search bar. Sear Asimov Foundation format:lrf -This will match all books in your library that have ``Asimov`` and ``Foundation`` in their metadata and are available in the LRF format. You can build advanced search queries easily using the :guilabel:`Advanced Search Dialog`, accessed by clicking the button |sbi|. +This will match all books in your library that have ``Asimov`` and ``Foundation`` in their metadata and +are available in the LRF format. Some more examples:: + + author:Asimov and not series:Foundation + title:"The Ring" or "This book is about a ring" + format:epub publisher:feedbooks.com + +You can build advanced search queries easily using the :guilabel:`Advanced Search Dialog`, accessed by +clicking the button |sbi|. .. |sbi| image:: images/search_button.png :align: middle @@ -197,7 +205,6 @@ This will match all books in your library that have ``Asimov`` and ``Foundation` :guilabel:`Advanced Search Dialog` - You can search on individual fields as shown. The "Negate" checkbox implies that only results that do not match the search expression will be returned. You can require all the search criteria to match or any of them. .. _configuration: diff --git a/src/calibre/manual/images/search.png b/src/calibre/manual/images/search.png index 8c8c3ffda1..dbabca55ee 100644 Binary files a/src/calibre/manual/images/search.png and b/src/calibre/manual/images/search.png differ