Change search syntax to be more google like

This commit is contained in:
Kovid Goyal 2008-11-01 21:37:45 -07:00
parent b6e55e908d
commit 828a141b29
8 changed files with 139 additions and 155 deletions

View File

@ -1,33 +1,42 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import re import re
from PyQt4.QtGui import QWidget, QDialog, QVBoxLayout from PyQt4.QtGui import QDialog
from PyQt4.QtCore import SIGNAL
from calibre.gui2.dialogs.search_ui import Ui_Dialog 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 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(): class SearchDialog(QDialog, Ui_Dialog):
self.field.addItem(field)
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): def token(self):
txt = qstring_to_unicode(self.text.text()).strip() txt = qstring_to_unicode(self.text.text()).strip()
if txt: if txt:
@ -37,44 +46,4 @@ class SearchItem(Ui_Form, QWidget):
if re.search(r'\s', tok): if re.search(r'\s', tok):
tok = '"%s"'%tok tok = '"%s"'%tok
return 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

View File

@ -5,107 +5,113 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>578</width> <width>667</width>
<height>474</height> <height>391</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
<string>Advanced Search</string> <string>Advanced Search</string>
</property> </property>
<property name="windowIcon" > <property name="windowIcon" >
<iconset resource="../images.qrc" >:/images/search.svg</iconset> <iconset resource="../images.qrc" >
<normaloff>:/images/search.svg</normaloff>:/images/search.svg</iconset>
</property> </property>
<layout class="QGridLayout" > <layout class="QVBoxLayout" name="verticalLayout_3" >
<item row="0" column="0" > <item>
<layout class="QVBoxLayout" > <widget class="QGroupBox" name="groupBox" >
<item> <property name="title" >
<layout class="QVBoxLayout" > <string>Find entries that have...</string>
<item> </property>
<widget class="QRadioButton" name="match_all" > <layout class="QVBoxLayout" name="verticalLayout" >
<property name="text" > <item>
<string>Match a&amp;ll of the following criteria</string> <layout class="QHBoxLayout" name="horizontalLayout" >
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="match_any" >
<property name="text" >
<string>Match a&amp;ny of the following criteria</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<string>Search criteria</string>
</property>
<layout class="QVBoxLayout" >
<item> <item>
<widget class="QFrame" name="search_items_frame" > <widget class="QLabel" name="label" >
<property name="frameShape" > <property name="text" >
<enum>QFrame::StyledPanel</enum> <string>&amp;All these words:</string>
</property> </property>
<property name="frameShadow" > <property name="buddy" >
<enum>QFrame::Raised</enum> <cstring>all</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" > <widget class="QLineEdit" name="all" />
<item>
<widget class="QPushButton" name="more_button" >
<property name="text" >
<string>More</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >:/images/plus.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fewer_button" >
<property name="text" >
<string>Fewer</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >:/images/list_remove.svg</iconset>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </item>
</item> <item>
<item> <layout class="QHBoxLayout" name="horizontalLayout_2" >
<widget class="QDialogButtonBox" name="buttonBox" > <item>
<property name="orientation" > <widget class="QLabel" name="label_2" >
<enum>Qt::Horizontal</enum> <property name="text" >
</property> <string>This exact &amp;phrase:</string>
<property name="standardButtons" > </property>
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> <property name="buddy" >
</property> <cstring>all</cstring>
</widget> </property>
</item> </widget>
</layout> </item>
<item>
<widget class="QLineEdit" name="phrase" />
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" >
<item>
<widget class="QLabel" name="label_3" >
<property name="text" >
<string>&amp;One or more of these words:</string>
</property>
<property name="buddy" >
<cstring>all</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="any" />
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2" >
<property name="title" >
<string>But dont show entries that have...</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" >
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4" >
<item>
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>Any of these &amp;unwanted words:</string>
</property>
<property name="buddy" >
<cstring>all</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="none" />
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>

View File

@ -886,6 +886,9 @@ class SearchBox(QLineEdit):
ans = '[' + ans + ']' ans = '[' + ans + ']'
self.set_search_string(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): def set_search_string(self, txt):
self.normalize_state() self.normalize_state()

View File

@ -253,7 +253,7 @@ class Main(MainWindow, Ui_MainWindow):
self.popularity.setVisible(False) self.popularity.setVisible(False)
self.tags_view.set_database(db, self.match_all, self.popularity) self.tags_view.set_database(db, self.match_all, self.popularity)
self.connect(self.tags_view, SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'), 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.status_bar.tag_view_button, SIGNAL('toggled(bool)'), self.toggle_tags_view)
self.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'),
self.tags_view.model().reinit) self.tags_view.model().reinit)

View File

@ -96,9 +96,8 @@ class TagsModel(QAbstractItemModel):
for key in self.row_map.values(): for key in self.row_map.values():
for tag in self._data[key]: for tag in self._data[key]:
if tag.state > 0: if tag.state > 0:
if tag.state == 2: prefix = ' not ' if tag.state == 2 else ''
tag = '!'+tag ans.append('%s%s:"%s"'%(prefix, key, tag))
ans.append((key, tag))
return ans return ans
def index(self, row, col, parent=QModelIndex()): def index(self, row, col, parent=QModelIndex()):

View File

@ -50,7 +50,7 @@ class LibraryServer(object):
LIBRARY = MarkupTemplate(textwrap.dedent('''\ LIBRARY = MarkupTemplate(textwrap.dedent('''\
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<library xmlns:py="http://genshi.edgewall.org/" start="$start" num="${len(books)}" total="$total" updated="${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}> <library xmlns:py="http://genshi.edgewall.org/" start="$start" num="${len(books)}" total="$total" updated="${updated.strftime('%Y-%m-%dT%H:%M:%S+00:00')}">
<py:for each="book in books"> <py:for each="book in books">
${Markup(book)} ${Markup(book)}
</py:for> </py:for>

View File

@ -188,7 +188,15 @@ You can search all the metadata by entering search terms in the search bar. Sear
Asimov Foundation format:lrf 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 .. |sbi| image:: images/search_button.png
:align: middle :align: middle
@ -197,7 +205,6 @@ This will match all books in your library that have ``Asimov`` and ``Foundation`
:guilabel:`Advanced Search Dialog` :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: .. _configuration:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 30 KiB