Add history and history based completion to search boxes

This commit is contained in:
Kovid Goyal 2009-08-18 21:04:32 -06:00
parent 470b6792a9
commit 4544754e1a
8 changed files with 509 additions and 476 deletions

View File

@ -76,6 +76,12 @@ def _config():
'only take place when the Enter or Return key is pressed.') 'only take place when the Enter or Return key is pressed.')
c.add_opt('save_to_disk_template_history', default=[], c.add_opt('save_to_disk_template_history', default=[],
help='Previously used Save to Disk templates') help='Previously used Save to Disk templates')
c.add_opt('main_search_history', default=[],
help='Search history for the main GUI')
c.add_opt('viewer_search_history', default=[],
help='Search history for the ebook viewer')
c.add_opt('lrf_viewer_search_history', default=[],
help='Search history for the LRF viewer')
return ConfigProxy(c) return ConfigProxy(c)
config = _config() config = _config()

View File

@ -8,10 +8,10 @@ from operator import attrgetter
from math import cos, sin, pi from math import cos, sin, pi
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \ from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \ QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QLineEdit, \ QPen, QStyle, QPainter, \
QPalette, QImage, QApplication, QMenu, \ QImage, QApplication, QMenu, \
QStyledItemDelegate, QCompleter QStyledItemDelegate, QCompleter
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \ from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, \
SIGNAL, QObject, QSize, QModelIndex, QDate SIGNAL, QObject, QSize, QModelIndex, QDate
from calibre import strftime from calibre import strftime
@ -1100,94 +1100,4 @@ class DeviceBooksModel(BooksModel):
self.editable = editable self.editable = editable
class SearchBox(QLineEdit):
INTERVAL = 1000 #: Time to wait before emitting search signal
def __init__(self, parent, help_text=_('Search (For Advanced Search click the button to the left)')):
QLineEdit.__init__(self, parent)
self.help_text = help_text
self.initial_state = True
self.as_you_type = True
self.default_palette = QApplication.palette(self)
self.gray = QPalette(self.default_palette)
self.gray.setBrush(QPalette.Text, QBrush(QColor('gray')))
self.prev_search = ''
self.timer = None
self.clear_to_help()
QObject.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot)
def normalize_state(self):
self.setText('')
self.setPalette(self.default_palette)
self.setStyleSheet('QLineEdit { background-color: white; }')
def clear_to_help(self):
self.setPalette(self.gray)
self.setText(self.help_text)
self.home(False)
self.initial_state = True
self.setStyleSheet('QLineEdit { background-color: white; }')
self.emit(SIGNAL('cleared()'))
def clear(self):
self.clear_to_help()
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), '', False)
def search_done(self, ok):
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
self.setStyleSheet('QLineEdit { background-color: %s; }' % col)
def keyPressEvent(self, event):
if self.initial_state:
self.normalize_state()
self.initial_state = False
if not self.as_you_type:
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
self.do_search()
QLineEdit.keyPressEvent(self, event)
def mouseReleaseEvent(self, event):
if self.initial_state:
self.normalize_state()
self.initial_state = False
QLineEdit.mouseReleaseEvent(self, event)
def text_edited_slot(self, text):
if self.as_you_type:
text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
self.prev_text = text
self.timer = self.startTimer(self.__class__.INTERVAL)
def timerEvent(self, event):
self.killTimer(event.timerId())
if event.timerId() == self.timer:
self.do_search()
def do_search(self):
text = qstring_to_unicode(self.text())
refinement = text.startswith(self.prev_search) and ':' not in text
self.prev_search = text
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement)
def search_from_tokens(self, tokens, all):
ans = u' '.join([u'%s:%s'%x for x in tokens])
if not all:
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()
self.setText(txt)
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), txt, False)
self.end(False)
self.initial_state = False
def search_as_you_type(self, enabled):
self.as_you_type = enabled

View File

@ -15,10 +15,10 @@ from calibre.gui2.lrf_renderer.main_ui import Ui_MainWindow
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
from calibre.gui2.main_window import MainWindow from calibre.gui2.main_window import MainWindow
from calibre.gui2.lrf_renderer.document import Document from calibre.gui2.lrf_renderer.document import Document
from calibre.gui2.library import SearchBox from calibre.gui2.search_box import SearchBox2
class RenderWorker(QThread): class RenderWorker(QThread):
def __init__(self, parent, lrf_stream, logger, opts): def __init__(self, parent, lrf_stream, logger, opts):
QThread.__init__(self, parent) QThread.__init__(self, parent)
self.stream, self.logger, self.opts = lrf_stream, logger, opts self.stream, self.logger, self.opts = lrf_stream, logger, opts
@ -26,7 +26,7 @@ class RenderWorker(QThread):
self.lrf = None self.lrf = None
self.document = None self.document = None
self.exception = None self.exception = None
def run(self): def run(self):
try: try:
self.lrf = LRFDocument(self.stream) self.lrf = LRFDocument(self.stream)
@ -34,35 +34,35 @@ class RenderWorker(QThread):
self.stream.close() self.stream.close()
self.stream = None self.stream = None
if self.aborted: if self.aborted:
self.lrf = None self.lrf = None
except Exception, err: except Exception, err:
self.lrf, self.stream = None, None self.lrf, self.stream = None, None
self.exception = err self.exception = err
self.formatted_traceback = traceback.format_exc() self.formatted_traceback = traceback.format_exc()
def abort(self): def abort(self):
if self.lrf is not None: if self.lrf is not None:
self.aborted = True self.aborted = True
self.lrf.keep_parsing = False self.lrf.keep_parsing = False
class Config(QDialog, Ui_ViewerConfig): class Config(QDialog, Ui_ViewerConfig):
def __init__(self, parent, opts): def __init__(self, parent, opts):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
Ui_ViewerConfig.__init__(self) Ui_ViewerConfig.__init__(self)
self.setupUi(self) self.setupUi(self)
self.white_background.setChecked(opts.white_background) self.white_background.setChecked(opts.white_background)
self.hyphenate.setChecked(opts.hyphenate) self.hyphenate.setChecked(opts.hyphenate)
class Main(MainWindow, Ui_MainWindow): class Main(MainWindow, Ui_MainWindow):
def __init__(self, logger, opts, parent=None): def __init__(self, logger, opts, parent=None):
MainWindow.__init__(self, opts, parent) MainWindow.__init__(self, opts, parent)
Ui_MainWindow.__init__(self) Ui_MainWindow.__init__(self)
self.setupUi(self) self.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowTitle(__appname__ + _(' - LRF Viewer')) self.setWindowTitle(__appname__ + _(' - LRF Viewer'))
self.logger = logger self.logger = logger
self.opts = opts self.opts = opts
self.document = None self.document = None
@ -73,18 +73,19 @@ class Main(MainWindow, Ui_MainWindow):
self.slider_action = self.slider = QSlider(Qt.Horizontal) self.slider_action = self.slider = QSlider(Qt.Horizontal)
self.tool_bar.addWidget(self.slider) self.tool_bar.addWidget(self.slider)
self.tool_bar.addSeparator() self.tool_bar.addSeparator()
self.search = SearchBox(self) self.search = SearchBox2(self)
self.search.initialize('lrf_viewer_search_history')
self.search_action = self.tool_bar.addWidget(self.search) self.search_action = self.tool_bar.addWidget(self.search)
QObject.connect(self.document, SIGNAL('chapter_rendered(int)'), self.chapter_rendered) QObject.connect(self.document, SIGNAL('chapter_rendered(int)'), self.chapter_rendered)
QObject.connect(self.document, SIGNAL('page_changed(PyQt_PyObject)'), self.page_changed) QObject.connect(self.document, SIGNAL('page_changed(PyQt_PyObject)'), self.page_changed)
QObject.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self.find) QObject.connect(self.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self.find)
self.action_next_page.setShortcuts([QKeySequence.MoveToNextPage, QKeySequence(Qt.Key_Space)]) self.action_next_page.setShortcuts([QKeySequence.MoveToNextPage, QKeySequence(Qt.Key_Space)])
self.action_previous_page.setShortcuts([QKeySequence.MoveToPreviousPage, QKeySequence(Qt.Key_Backspace)]) self.action_previous_page.setShortcuts([QKeySequence.MoveToPreviousPage, QKeySequence(Qt.Key_Backspace)])
self.action_next_match.setShortcuts(QKeySequence.FindNext) self.action_next_match.setShortcuts(QKeySequence.FindNext)
self.addAction(self.action_next_match) self.addAction(self.action_next_match)
QObject.connect(self.action_next_page, SIGNAL('triggered(bool)'), self.next) QObject.connect(self.action_next_page, SIGNAL('triggered(bool)'), self.next)
QObject.connect(self.action_previous_page, SIGNAL('triggered(bool)'), self.previous) QObject.connect(self.action_previous_page, SIGNAL('triggered(bool)'), self.previous)
QObject.connect(self.action_back, SIGNAL('triggered(bool)'), self.back) QObject.connect(self.action_back, SIGNAL('triggered(bool)'), self.back)
QObject.connect(self.action_forward, SIGNAL('triggered(bool)'), self.forward) QObject.connect(self.action_forward, SIGNAL('triggered(bool)'), self.forward)
@ -93,15 +94,15 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.action_configure, SIGNAL('triggered(bool)'), self.configure) QObject.connect(self.action_configure, SIGNAL('triggered(bool)'), self.configure)
QObject.connect(self.spin_box, SIGNAL('valueChanged(int)'), self.go_to_page) QObject.connect(self.spin_box, SIGNAL('valueChanged(int)'), self.go_to_page)
QObject.connect(self.slider, SIGNAL('valueChanged(int)'), self.go_to_page) QObject.connect(self.slider, SIGNAL('valueChanged(int)'), self.go_to_page)
self.graphics_view.setRenderHint(QPainter.Antialiasing, True) self.graphics_view.setRenderHint(QPainter.Antialiasing, True)
self.graphics_view.setRenderHint(QPainter.TextAntialiasing, True) self.graphics_view.setRenderHint(QPainter.TextAntialiasing, True)
self.graphics_view.setRenderHint(QPainter.SmoothPixmapTransform, True) self.graphics_view.setRenderHint(QPainter.SmoothPixmapTransform, True)
self.closed = False self.closed = False
def configure(self, triggered): def configure(self, triggered):
opts = config['LRF_ebook_viewer_options'] opts = config['LRF_ebook_viewer_options']
if not opts: if not opts:
@ -112,65 +113,64 @@ class Main(MainWindow, Ui_MainWindow):
opts.white_background = bool(d.white_background.isChecked()) opts.white_background = bool(d.white_background.isChecked())
opts.hyphenate = bool(d.hyphenate.isChecked()) opts.hyphenate = bool(d.hyphenate.isChecked())
config['LRF_ebook_viewer_options'] = opts config['LRF_ebook_viewer_options'] = opts
def set_ebook(self, stream): def set_ebook(self, stream):
self.progress_bar.setMinimum(0) self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(0) self.progress_bar.setMaximum(0)
self.progress_bar.setValue(0) self.progress_bar.setValue(0)
if stream is not None: if stream is not None:
self.file_name = os.path.basename(stream.name) if hasattr(stream, 'name') else '' self.file_name = os.path.basename(stream.name) if hasattr(stream, 'name') else ''
self.progress_label.setText('Parsing '+ self.file_name) self.progress_label.setText('Parsing '+ self.file_name)
self.renderer = RenderWorker(self, stream, self.logger, self.opts) self.renderer = RenderWorker(self, stream, self.logger, self.opts)
QObject.connect(self.renderer, SIGNAL('finished()'), self.parsed, Qt.QueuedConnection) QObject.connect(self.renderer, SIGNAL('finished()'), self.parsed, Qt.QueuedConnection)
self.search.help_text = 'Search'
self.search.clear_to_help() self.search.clear_to_help()
self.last_search = None self.last_search = None
else: else:
self.stack.setCurrentIndex(0) self.stack.setCurrentIndex(0)
self.renderer = None self.renderer = None
def open_ebook(self, triggered): def open_ebook(self, triggered):
files = choose_files(self, 'open ebook dialog', 'Choose ebook', files = choose_files(self, 'open ebook dialog', 'Choose ebook',
[('Ebooks', ['lrf'])], all_files=False, [('Ebooks', ['lrf'])], all_files=False,
select_only_single_file=True) select_only_single_file=True)
if files: if files:
file = files[0] file = files[0]
self.set_ebook(open(file, 'rb')) self.set_ebook(open(file, 'rb'))
self.render() self.render()
def page_changed(self, num): def page_changed(self, num):
self.slider.setValue(num) self.slider.setValue(num)
self.spin_box.setValue(num) self.spin_box.setValue(num)
def render(self): def render(self):
if self.renderer is not None: if self.renderer is not None:
self.stack.setCurrentIndex(1) self.stack.setCurrentIndex(1)
self.renderer.start() self.renderer.start()
def find(self, search, refinement): def find(self, search, refinement):
self.last_search = search self.last_search = search
try: try:
self.document.search(search) self.document.search(search)
except StopIteration: except StopIteration:
error_dialog(self, _('No matches found'), _('<b>No matches</b> for the search phrase <i>%s</i> were found.')%(search,)).exec_() error_dialog(self, _('No matches found'), _('<b>No matches</b> for the search phrase <i>%s</i> were found.')%(search,)).exec_()
def parsed(self): def parsed(self):
if not self.renderer.aborted and self.renderer.lrf is not None: if not self.renderer.aborted and self.renderer.lrf is not None:
width, height = self.renderer.lrf.device_info.width, \ width, height = self.renderer.lrf.device_info.width, \
self.renderer.lrf.device_info.height self.renderer.lrf.device_info.height
hdelta = self.tool_bar.height()+3 hdelta = self.tool_bar.height()+3
from PyQt4.QtGui import QScrollBar from PyQt4.QtGui import QScrollBar
s = QScrollBar(self) s = QScrollBar(self)
scrollbar_adjust = min(s.width(), s.height()) scrollbar_adjust = min(s.width(), s.height())
self.graphics_view.resize_for(width+scrollbar_adjust, height+scrollbar_adjust) self.graphics_view.resize_for(width+scrollbar_adjust, height+scrollbar_adjust)
desktop = QCoreApplication.instance().desktop() desktop = QCoreApplication.instance().desktop()
screen_height = desktop.availableGeometry(self).height() - 25 screen_height = desktop.availableGeometry(self).height() - 25
height = min(screen_height, height+hdelta+scrollbar_adjust) height = min(screen_height, height+hdelta+scrollbar_adjust)
self.resize(width+scrollbar_adjust, height) self.resize(width+scrollbar_adjust, height)
self.setWindowTitle(self.renderer.lrf.metadata.title + ' - ' + __appname__) self.setWindowTitle(self.renderer.lrf.metadata.title + ' - ' + __appname__)
self.document_title = self.renderer.lrf.metadata.title self.document_title = self.renderer.lrf.metadata.title
if self.opts.profile: if self.opts.profile:
@ -183,7 +183,7 @@ class Main(MainWindow, Ui_MainWindow):
self.document.render(self.renderer.lrf) self.document.render(self.renderer.lrf)
print 'Layout time:', time.time()-start, 'seconds' print 'Layout time:', time.time()-start, 'seconds'
self.renderer.lrf = None self.renderer.lrf = None
self.graphics_view.setScene(self.document) self.graphics_view.setScene(self.document)
self.graphics_view.show() self.graphics_view.show()
self.spin_box.setRange(1, self.document.num_of_pages) self.spin_box.setRange(1, self.document.num_of_pages)
@ -200,10 +200,10 @@ class Main(MainWindow, Ui_MainWindow):
msg = u'<p><b>%s</b>: '%(exception.__class__.__name__,) + unicode(str(exception), 'utf8', 'replace') + u'</p>' msg = u'<p><b>%s</b>: '%(exception.__class__.__name__,) + unicode(str(exception), 'utf8', 'replace') + u'</p>'
msg += u'<p>Failed to render document</p>' msg += u'<p>Failed to render document</p>'
msg += u'<p>Detailed <b>traceback</b>:<pre>' msg += u'<p>Detailed <b>traceback</b>:<pre>'
msg += self.renderer.formatted_traceback + '</pre>' msg += self.renderer.formatted_traceback + '</pre>'
d = ConversionErrorDialog(self, 'Error while rendering file', msg) d = ConversionErrorDialog(self, 'Error while rendering file', msg)
d.exec_() d.exec_()
def chapter_rendered(self, num): def chapter_rendered(self, num):
if num > 0: if num > 0:
self.progress_bar.setMinimum(0) self.progress_bar.setMinimum(0)
@ -213,7 +213,7 @@ class Main(MainWindow, Ui_MainWindow):
else: else:
self.progress_bar.setValue(self.progress_bar.value()+1) self.progress_bar.setValue(self.progress_bar.value()+1)
QCoreApplication.processEvents() QCoreApplication.processEvents()
def next(self, triggered): def next(self, triggered):
self.document.next() self.document.next()
@ -222,19 +222,19 @@ class Main(MainWindow, Ui_MainWindow):
self.document.next_match() self.document.next_match()
except StopIteration: except StopIteration:
pass pass
def previous(self, triggered): def previous(self, triggered):
self.document.previous() self.document.previous()
def go_to_page(self, num): def go_to_page(self, num):
self.document.show_page(num) self.document.show_page(num)
def forward(self, triggered): def forward(self, triggered):
self.document.forward() self.document.forward()
def back(self, triggered): def back(self, triggered):
self.document.back() self.document.back()
def wheelEvent(self, ev): def wheelEvent(self, ev):
if ev.delta() >= 0: if ev.delta() >= 0:
self.document.previous() self.document.previous()
@ -263,7 +263,7 @@ def file_renderer(stream, opts, parent=None, logger=None):
m = Main(logger, opts, parent=parent) m = Main(logger, opts, parent=parent)
m.set_ebook(stream) m.set_ebook(stream)
return m return m
def option_parser(): def option_parser():
from calibre.gui2.main_window import option_parser from calibre.gui2.main_window import option_parser
@ -295,7 +295,7 @@ def normalize_settings(parser, opts):
continue continue
setattr(saved_opts, opt.dest, getattr(opts, opt.dest)) setattr(saved_opts, opt.dest, getattr(opts, opt.dest))
return saved_opts return saved_opts
def main(args=sys.argv, logger=None): def main(args=sys.argv, logger=None):
parser = option_parser() parser = option_parser()
@ -310,17 +310,17 @@ def main(args=sys.argv, logger=None):
QCoreApplication.setOrganizationName(ORG_NAME) QCoreApplication.setOrganizationName(ORG_NAME)
QCoreApplication.setApplicationName(APP_UID) QCoreApplication.setApplicationName(APP_UID)
opts = normalize_settings(parser, opts) opts = normalize_settings(parser, opts)
stream = open(args[1], 'rb') if len(args) > 1 else None stream = open(args[1], 'rb') if len(args) > 1 else None
main = file_renderer(stream, opts, logger=logger) main = file_renderer(stream, opts, logger=logger)
sys.excepthook = main.unhandled_exception sys.excepthook = main.unhandled_exception
main.show() main.show()
main.render() main.render()
main.activateWindow() main.activateWindow()
main.raise_() main.raise_()
return app.exec_() return app.exec_()
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) sys.exit(main())

View File

@ -121,6 +121,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
Ui_MainWindow.__init__(self) Ui_MainWindow.__init__(self)
self.setupUi(self) self.setupUi(self)
self.setWindowTitle(__appname__) self.setWindowTitle(__appname__)
self.search.initialize('main_search_history', colorize=True,
help_text=_('Search (For Advanced Search click the button to the left)'))
self.connect(self.clear_button, SIGNAL('clicked()'), self.search.clear)
self.progress_indicator = ProgressIndicator(self) self.progress_indicator = ProgressIndicator(self)
self.verbose = opts.verbose self.verbose = opts.verbose
self.get_metadata = GetMetadata() self.get_metadata = GetMetadata()
@ -148,7 +151,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.system_tray_icon.hide() self.system_tray_icon.hide()
else: else:
self.system_tray_icon.show() self.system_tray_icon.show()
self.search.search_as_you_type(config['search_as_you_type'])
self.system_tray_menu = QMenu(self) self.system_tray_menu = QMenu(self)
self.restore_action = self.system_tray_menu.addAction( self.restore_action = self.system_tray_menu.addAction(
QIcon(':/images/page.svg'), _('&Restore')) QIcon(':/images/page.svg'), _('&Restore'))

View File

@ -185,33 +185,18 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="SearchBox" name="search"> <widget class="SearchBox2" name="search">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Search the list of books by title or author&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string> <string>&lt;p&gt;Search the list of books by title, author, publisher, tags, comments, etc.&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string>
</property> </property>
<property name="whatsThis"> <property name="whatsThis">
<string>Search the list of books by title, author, publisher, tags and comments&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string> <string>&lt;p&gt;Search the list of books by title, author, publisher, tags, comments, etc.&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
<property name="frame">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -673,11 +658,6 @@
</action> </action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget>
<class>SearchBox</class>
<extends>QLineEdit</extends>
<header>library.h</header>
</customwidget>
<customwidget> <customwidget>
<class>BooksView</class> <class>BooksView</class>
<extends>QTableView</extends> <extends>QTableView</extends>
@ -698,26 +678,14 @@
<extends>QTreeView</extends> <extends>QTreeView</extends>
<header>calibre/gui2/tag_view.h</header> <header>calibre/gui2/tag_view.h</header>
</customwidget> </customwidget>
<customwidget>
<class>SearchBox2</class>
<extends>QComboBox</extends>
<header>calibre.gui2.search_box</header>
</customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="images.qrc"/> <include location="images.qrc"/>
</resources> </resources>
<connections> <connections/>
<connection>
<sender>clear_button</sender>
<signal>clicked()</signal>
<receiver>search</receiver>
<slot>clear()</slot>
<hints>
<hint type="sourcelabel">
<x>787</x>
<y>215</y>
</hint>
<hint type="destinationlabel">
<x>755</x>
<y>213</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

View File

@ -0,0 +1,148 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import QComboBox, SIGNAL, Qt, QLineEdit, QStringList
from calibre.gui2 import config
class SearchLineEdit(QLineEdit):
def keyPressEvent(self, event):
self.emit(SIGNAL('key_pressed(PyQt_PyObject)'), event)
QLineEdit.keyPressEvent(self, event)
def mouseReleaseEvent(self, event):
self.emit(SIGNAL('mouse_released(PyQt_PyObject)'), event)
QLineEdit.mouseReleaseEvent(self, event)
class SearchBox2(QComboBox):
INTERVAL = 1500 #: Time to wait before emitting search signal
MAX_COUNT = 25
def __init__(self, parent=None):
QComboBox.__init__(self, parent)
self.line_edit = SearchLineEdit(self)
self.setLineEdit(self.line_edit)
self.connect(self.line_edit, SIGNAL('key_pressed(PyQt_PyObject)'),
self.key_pressed, Qt.DirectConnection)
self.connect(self.line_edit, SIGNAL('mouse_released(PyQt_PyObject)'),
self.key_pressed, Qt.DirectConnection)
self.setEditable(True)
self.help_state = True
self.as_you_type = True
self.prev_search = ''
self.timer = None
self.setInsertPolicy(self.NoInsert)
self.setMaxCount(self.MAX_COUNT)
def initialize(self, opt_name, colorize=False,
help_text=_('Search')):
self.as_you_type = config['search_as_you_type']
self.opt_name = opt_name
self.addItems(QStringList(list(set(config[opt_name]))))
self.help_text = help_text
self.colorize = colorize
self.clear_to_help()
self.connect(self, SIGNAL('editTextChanged(QString)'), self.text_edited_slot)
def normalize_state(self):
self.setEditText('')
self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: white; }')
self.help_state = False
def clear_to_help(self):
self.setEditText(self.help_text)
self.line_edit.home(False)
self.help_state = True
self.line_edit.setStyleSheet('QLineEdit { color: gray; background-color: white; }')
self.emit(SIGNAL('cleared()'))
def text(self):
return self.currentText()
def clear(self):
self.clear_to_help()
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), '', False)
def search_done(self, ok):
if not unicode(self.currentText()).strip():
return self.clear_to_help()
col = 'rgba(0,255,0,20%)' if ok else 'rgb(255,0,0,20%)'
if not self.colorize:
col = 'white'
self.line_edit.setStyleSheet('QLineEdit { color: black; background-color: %s; }' % col)
def key_pressed(self, event):
if self.help_state:
self.normalize_state()
if not self.as_you_type:
if event.key() in (Qt.Key_Return, Qt.Key_Enter):
self.do_search()
def mouse_released(self, event):
if self.help_state:
self.normalize_state()
def text_edited_slot(self, text):
if self.as_you_type:
text = unicode(text)
self.prev_text = text
self.timer = self.startTimer(self.__class__.INTERVAL)
def timerEvent(self, event):
self.killTimer(event.timerId())
if event.timerId() == self.timer:
self.do_search()
def do_search(self):
text = unicode(self.currentText()).strip()
if not text or text == self.help_text:
return self.clear()
self.help_state = False
refinement = text.startswith(self.prev_search) and ':' not in text
self.prev_search = text
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), text, refinement)
idx = self.findText(text, Qt.MatchFixedString)
self.block_signals(True)
if idx < 0:
self.insertItem(0, text)
else:
t = self.itemText(idx)
self.removeItem(idx)
self.insertItem(0, t)
self.setCurrentIndex(0)
self.block_signals(False)
config[self.opt_name] = [unicode(self.itemText(i)) for i in
range(self.count())]
def block_signals(self, yes):
self.blockSignals(yes)
self.line_edit.blockSignals(yes)
def search_from_tokens(self, tokens, all):
ans = u' '.join([u'%s:%s'%x for x in tokens])
if not all:
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()
self.setEditText(txt)
self.emit(SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), txt, False)
self.line_edit.end(False)
self.initial_state = False
def search_as_you_type(self, enabled):
self.as_you_type = enabled

View File

@ -21,7 +21,7 @@ from calibre.ebooks.oeb.iterator import EbookIterator
from calibre.ebooks import DRMError from calibre.ebooks import DRMError
from calibre.constants import islinux from calibre.constants import islinux
from calibre.utils.config import Config, StringConfig, dynamic from calibre.utils.config import Config, StringConfig, dynamic
from calibre.gui2.library import SearchBox from calibre.gui2.search_box import SearchBox2
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.customize.ui import available_input_formats from calibre.customize.ui import available_input_formats
@ -218,7 +218,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.tool_bar2.insertWidget(self.action_find_next, self.reference) self.tool_bar2.insertWidget(self.action_find_next, self.reference)
self.tool_bar2.insertSeparator(self.action_find_next) self.tool_bar2.insertSeparator(self.action_find_next)
self.setFocusPolicy(Qt.StrongFocus) self.setFocusPolicy(Qt.StrongFocus)
self.search = SearchBox(self, _('Search')) self.search = SearchBox2(self)
self.search.initialize('viewer_search_history')
self.search.setToolTip(_('Search for text in book')) self.search.setToolTip(_('Search for text in book'))
self.tool_bar2.insertWidget(self.action_find_next, self.search) self.tool_bar2.insertWidget(self.action_find_next, self.search)
self.view.set_manager(self) self.view.set_manager(self)
@ -408,10 +409,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def find(self, text, refinement, repeat=False): def find(self, text, refinement, repeat=False):
if not text: if not text:
return return self.search.search_done(False)
if self.view.search(text): if self.view.search(text):
self.scrolled(self.view.scroll_fraction) self.scrolled(self.view.scroll_fraction)
return return self.search.search_done(True)
index = self.iterator.search(text, self.current_index) index = self.iterator.search(text, self.current_index)
if index is None: if index is None:
if self.current_index > 0: if self.current_index > 0:
@ -419,8 +420,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
if index is None: if index is None:
info_dialog(self, _('No matches found'), info_dialog(self, _('No matches found'),
_('No matches found for: %s')%text).exec_() _('No matches found for: %s')%text).exec_()
return return self.search.search_done(True)
return return self.search.search_done(True)
self.pending_search = text self.pending_search = text
self.load_path(self.iterator.spine[index]) self.load_path(self.iterator.spine[index])

File diff suppressed because it is too large Load Diff