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,32 +1,41 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
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') :''
}
class SearchDialog(QDialog, Ui_Dialog):
def __init__(self, parent):
QWidget.__init__(self, parent)
def __init__(self, *args):
QDialog.__init__(self, *args)
self.setupUi(self)
for field in self.FIELDS.keys():
self.field.addItem(field)
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()
@ -38,43 +47,3 @@ class SearchItem(Ui_Form, QWidget):
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

View File

@ -5,107 +5,113 @@
<rect>
<x>0</x>
<y>0</y>
<width>578</width>
<height>474</height>
<width>667</width>
<height>391</height>
</rect>
</property>
<property name="windowTitle" >
<string>Advanced Search</string>
</property>
<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>
<layout class="QGridLayout" >
<item row="0" column="0" >
<layout class="QVBoxLayout" >
<item>
<layout class="QVBoxLayout" >
<item>
<widget class="QRadioButton" name="match_all" >
<property name="text" >
<string>Match a&amp;ll of the following criteria</string>
</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" >
<layout class="QVBoxLayout" name="verticalLayout_3" >
<item>
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<string>Find entries that have...</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" >
<item>
<layout class="QHBoxLayout" name="horizontalLayout" >
<item>
<widget class="QFrame" name="search_items_frame" >
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
<widget class="QLabel" name="label" >
<property name="text" >
<string>&amp;All these words:</string>
</property>
<property name="frameShadow" >
<enum>QFrame::Raised</enum>
<property name="buddy" >
<cstring>all</cstring>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<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>
<widget class="QLineEdit" name="all" />
</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::NoButton|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>This exact &amp;phrase:</string>
</property>
<property name="buddy" >
<cstring>all</cstring>
</property>
</widget>
</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>
</layout>
</widget>

View File

@ -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()

View File

@ -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)

View File

@ -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()):

View File

@ -50,7 +50,7 @@ class LibraryServer(object):
LIBRARY = MarkupTemplate(textwrap.dedent('''\
<?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">
${Markup(book)}
</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
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:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 30 KiB