mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Change search syntax to be more google like
This commit is contained in:
parent
b6e55e908d
commit
828a141b29
@ -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
|
||||
|
@ -5,90 +5,98 @@
|
||||
<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" >
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3" >
|
||||
<item>
|
||||
<layout class="QVBoxLayout" >
|
||||
<widget class="QGroupBox" name="groupBox" >
|
||||
<property name="title" >
|
||||
<string>Find entries that have...</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" >
|
||||
<item>
|
||||
<widget class="QRadioButton" name="match_all" >
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Match a&ll of the following criteria</string>
|
||||
<string>&All these words:</string>
|
||||
</property>
|
||||
<property name="checked" >
|
||||
<bool>true</bool>
|
||||
<property name="buddy" >
|
||||
<cstring>all</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="match_any" >
|
||||
<property name="text" >
|
||||
<string>Match a&ny of the following criteria</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="all" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox" >
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>This exact &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>&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>Search criteria</string>
|
||||
<string>But dont show entries that have...</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" >
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" >
|
||||
<item>
|
||||
<widget class="QFrame" name="search_items_frame" >
|
||||
<property name="frameShape" >
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow" >
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4" >
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<item>
|
||||
<widget class="QPushButton" name="more_button" >
|
||||
<widget class="QLabel" name="label_4" >
|
||||
<property name="text" >
|
||||
<string>More</string>
|
||||
<string>Any of these &unwanted words:</string>
|
||||
</property>
|
||||
<property name="icon" >
|
||||
<iconset resource="../images.qrc" >:/images/plus.svg</iconset>
|
||||
<property name="buddy" >
|
||||
<cstring>all</cstring>
|
||||
</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>
|
||||
<widget class="QLineEdit" name="none" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
@ -101,13 +109,11 @@
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../images.qrc" />
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()):
|
||||
|
@ -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>
|
||||
|
@ -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 |
Loading…
x
Reference in New Issue
Block a user