mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add history and history based completion to search boxes
This commit is contained in:
parent
470b6792a9
commit
4544754e1a
@ -76,6 +76,12 @@ def _config():
|
||||
'only take place when the Enter or Return key is pressed.')
|
||||
c.add_opt('save_to_disk_template_history', default=[],
|
||||
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)
|
||||
|
||||
config = _config()
|
||||
|
@ -8,10 +8,10 @@ from operator import attrgetter
|
||||
from math import cos, sin, pi
|
||||
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
|
||||
QItemDelegate, QPainterPath, QLinearGradient, QBrush, \
|
||||
QPen, QStyle, QPainter, QLineEdit, \
|
||||
QPalette, QImage, QApplication, QMenu, \
|
||||
QPen, QStyle, QPainter, \
|
||||
QImage, QApplication, QMenu, \
|
||||
QStyledItemDelegate, QCompleter
|
||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, \
|
||||
SIGNAL, QObject, QSize, QModelIndex, QDate
|
||||
|
||||
from calibre import strftime
|
||||
@ -1100,94 +1100,4 @@ class DeviceBooksModel(BooksModel):
|
||||
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
|
||||
|
||||
|
@ -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.main_window import MainWindow
|
||||
from calibre.gui2.lrf_renderer.document import Document
|
||||
from calibre.gui2.library import SearchBox
|
||||
from calibre.gui2.search_box import SearchBox2
|
||||
|
||||
class RenderWorker(QThread):
|
||||
|
||||
|
||||
def __init__(self, parent, lrf_stream, logger, opts):
|
||||
QThread.__init__(self, parent)
|
||||
self.stream, self.logger, self.opts = lrf_stream, logger, opts
|
||||
@ -26,7 +26,7 @@ class RenderWorker(QThread):
|
||||
self.lrf = None
|
||||
self.document = None
|
||||
self.exception = None
|
||||
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.lrf = LRFDocument(self.stream)
|
||||
@ -34,35 +34,35 @@ class RenderWorker(QThread):
|
||||
self.stream.close()
|
||||
self.stream = None
|
||||
if self.aborted:
|
||||
self.lrf = None
|
||||
self.lrf = None
|
||||
except Exception, err:
|
||||
self.lrf, self.stream = None, None
|
||||
self.exception = err
|
||||
self.formatted_traceback = traceback.format_exc()
|
||||
|
||||
|
||||
def abort(self):
|
||||
if self.lrf is not None:
|
||||
self.aborted = True
|
||||
self.lrf.keep_parsing = False
|
||||
|
||||
|
||||
class Config(QDialog, Ui_ViewerConfig):
|
||||
|
||||
|
||||
def __init__(self, parent, opts):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_ViewerConfig.__init__(self)
|
||||
self.setupUi(self)
|
||||
self.white_background.setChecked(opts.white_background)
|
||||
self.hyphenate.setChecked(opts.hyphenate)
|
||||
|
||||
|
||||
class Main(MainWindow, Ui_MainWindow):
|
||||
|
||||
|
||||
def __init__(self, logger, opts, parent=None):
|
||||
MainWindow.__init__(self, opts, parent)
|
||||
Ui_MainWindow.__init__(self)
|
||||
Ui_MainWindow.__init__(self)
|
||||
self.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.setWindowTitle(__appname__ + _(' - LRF Viewer'))
|
||||
|
||||
|
||||
self.logger = logger
|
||||
self.opts = opts
|
||||
self.document = None
|
||||
@ -73,18 +73,19 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.slider_action = self.slider = QSlider(Qt.Horizontal)
|
||||
self.tool_bar.addWidget(self.slider)
|
||||
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)
|
||||
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.search, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self.find)
|
||||
|
||||
|
||||
self.action_next_page.setShortcuts([QKeySequence.MoveToNextPage, QKeySequence(Qt.Key_Space)])
|
||||
self.action_previous_page.setShortcuts([QKeySequence.MoveToPreviousPage, QKeySequence(Qt.Key_Backspace)])
|
||||
self.action_next_match.setShortcuts(QKeySequence.FindNext)
|
||||
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_back, SIGNAL('triggered(bool)'), self.back)
|
||||
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.spin_box, 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.TextAntialiasing, True)
|
||||
self.graphics_view.setRenderHint(QPainter.SmoothPixmapTransform, True)
|
||||
|
||||
|
||||
self.closed = False
|
||||
|
||||
|
||||
|
||||
|
||||
def configure(self, triggered):
|
||||
opts = config['LRF_ebook_viewer_options']
|
||||
if not opts:
|
||||
@ -112,65 +113,64 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
opts.white_background = bool(d.white_background.isChecked())
|
||||
opts.hyphenate = bool(d.hyphenate.isChecked())
|
||||
config['LRF_ebook_viewer_options'] = opts
|
||||
|
||||
|
||||
def set_ebook(self, stream):
|
||||
self.progress_bar.setMinimum(0)
|
||||
self.progress_bar.setMaximum(0)
|
||||
self.progress_bar.setValue(0)
|
||||
|
||||
|
||||
if stream is not None:
|
||||
self.file_name = os.path.basename(stream.name) if hasattr(stream, 'name') else ''
|
||||
self.progress_label.setText('Parsing '+ self.file_name)
|
||||
self.renderer = RenderWorker(self, stream, self.logger, self.opts)
|
||||
QObject.connect(self.renderer, SIGNAL('finished()'), self.parsed, Qt.QueuedConnection)
|
||||
self.search.help_text = 'Search'
|
||||
self.search.clear_to_help()
|
||||
self.last_search = None
|
||||
else:
|
||||
self.stack.setCurrentIndex(0)
|
||||
self.renderer = None
|
||||
|
||||
|
||||
def open_ebook(self, triggered):
|
||||
files = choose_files(self, 'open ebook dialog', 'Choose ebook',
|
||||
[('Ebooks', ['lrf'])], all_files=False,
|
||||
files = choose_files(self, 'open ebook dialog', 'Choose ebook',
|
||||
[('Ebooks', ['lrf'])], all_files=False,
|
||||
select_only_single_file=True)
|
||||
if files:
|
||||
file = files[0]
|
||||
self.set_ebook(open(file, 'rb'))
|
||||
self.render()
|
||||
|
||||
|
||||
|
||||
|
||||
def page_changed(self, num):
|
||||
self.slider.setValue(num)
|
||||
self.spin_box.setValue(num)
|
||||
|
||||
|
||||
def render(self):
|
||||
if self.renderer is not None:
|
||||
self.stack.setCurrentIndex(1)
|
||||
self.renderer.start()
|
||||
|
||||
|
||||
def find(self, search, refinement):
|
||||
self.last_search = search
|
||||
try:
|
||||
self.document.search(search)
|
||||
except StopIteration:
|
||||
error_dialog(self, _('No matches found'), _('<b>No matches</b> for the search phrase <i>%s</i> were found.')%(search,)).exec_()
|
||||
|
||||
|
||||
def parsed(self):
|
||||
if not self.renderer.aborted and self.renderer.lrf is not None:
|
||||
width, height = self.renderer.lrf.device_info.width, \
|
||||
self.renderer.lrf.device_info.height
|
||||
hdelta = self.tool_bar.height()+3
|
||||
|
||||
|
||||
from PyQt4.QtGui import QScrollBar
|
||||
s = QScrollBar(self)
|
||||
scrollbar_adjust = min(s.width(), s.height())
|
||||
self.graphics_view.resize_for(width+scrollbar_adjust, height+scrollbar_adjust)
|
||||
|
||||
|
||||
desktop = QCoreApplication.instance().desktop()
|
||||
screen_height = desktop.availableGeometry(self).height() - 25
|
||||
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.document_title = self.renderer.lrf.metadata.title
|
||||
if self.opts.profile:
|
||||
@ -183,7 +183,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.document.render(self.renderer.lrf)
|
||||
print 'Layout time:', time.time()-start, 'seconds'
|
||||
self.renderer.lrf = None
|
||||
|
||||
|
||||
self.graphics_view.setScene(self.document)
|
||||
self.graphics_view.show()
|
||||
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>Failed to render document</p>'
|
||||
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.exec_()
|
||||
|
||||
|
||||
def chapter_rendered(self, num):
|
||||
if num > 0:
|
||||
self.progress_bar.setMinimum(0)
|
||||
@ -213,7 +213,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
else:
|
||||
self.progress_bar.setValue(self.progress_bar.value()+1)
|
||||
QCoreApplication.processEvents()
|
||||
|
||||
|
||||
def next(self, triggered):
|
||||
self.document.next()
|
||||
|
||||
@ -222,19 +222,19 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.document.next_match()
|
||||
except StopIteration:
|
||||
pass
|
||||
|
||||
|
||||
def previous(self, triggered):
|
||||
self.document.previous()
|
||||
|
||||
|
||||
def go_to_page(self, num):
|
||||
self.document.show_page(num)
|
||||
|
||||
|
||||
def forward(self, triggered):
|
||||
self.document.forward()
|
||||
|
||||
|
||||
def back(self, triggered):
|
||||
self.document.back()
|
||||
|
||||
|
||||
def wheelEvent(self, ev):
|
||||
if ev.delta() >= 0:
|
||||
self.document.previous()
|
||||
@ -263,7 +263,7 @@ def file_renderer(stream, opts, parent=None, logger=None):
|
||||
m = Main(logger, opts, parent=parent)
|
||||
m.set_ebook(stream)
|
||||
return m
|
||||
|
||||
|
||||
|
||||
def option_parser():
|
||||
from calibre.gui2.main_window import option_parser
|
||||
@ -295,7 +295,7 @@ def normalize_settings(parser, opts):
|
||||
continue
|
||||
setattr(saved_opts, opt.dest, getattr(opts, opt.dest))
|
||||
return saved_opts
|
||||
|
||||
|
||||
|
||||
def main(args=sys.argv, logger=None):
|
||||
parser = option_parser()
|
||||
@ -310,17 +310,17 @@ def main(args=sys.argv, logger=None):
|
||||
QCoreApplication.setOrganizationName(ORG_NAME)
|
||||
QCoreApplication.setApplicationName(APP_UID)
|
||||
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)
|
||||
sys.excepthook = main.unhandled_exception
|
||||
main.show()
|
||||
main.render()
|
||||
main.activateWindow()
|
||||
main.raise_()
|
||||
return app.exec_()
|
||||
return app.exec_()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -121,6 +121,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
Ui_MainWindow.__init__(self)
|
||||
self.setupUi(self)
|
||||
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.verbose = opts.verbose
|
||||
self.get_metadata = GetMetadata()
|
||||
@ -148,7 +151,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||
self.system_tray_icon.hide()
|
||||
else:
|
||||
self.system_tray_icon.show()
|
||||
self.search.search_as_you_type(config['search_as_you_type'])
|
||||
self.system_tray_menu = QMenu(self)
|
||||
self.restore_action = self.system_tray_menu.addAction(
|
||||
QIcon(':/images/page.svg'), _('&Restore'))
|
||||
|
@ -185,33 +185,18 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="SearchBox" name="search">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="SearchBox2" name="search">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>1</horstretch>
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
||||
<string><p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Search the list of books by title, author, publisher, tags and comments<br><br>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>
|
||||
<string><p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -673,11 +658,6 @@
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>SearchBox</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>library.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BooksView</class>
|
||||
<extends>QTableView</extends>
|
||||
@ -698,26 +678,14 @@
|
||||
<extends>QTreeView</extends>
|
||||
<header>calibre/gui2/tag_view.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>SearchBox2</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre.gui2.search_box</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="images.qrc"/>
|
||||
</resources>
|
||||
<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>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
148
src/calibre/gui2/search_box.py
Normal file
148
src/calibre/gui2/search_box.py
Normal 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
|
||||
|
@ -21,7 +21,7 @@ from calibre.ebooks.oeb.iterator import EbookIterator
|
||||
from calibre.ebooks import DRMError
|
||||
from calibre.constants import islinux
|
||||
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.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.insertSeparator(self.action_find_next)
|
||||
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.tool_bar2.insertWidget(self.action_find_next, self.search)
|
||||
self.view.set_manager(self)
|
||||
@ -408,10 +409,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
|
||||
def find(self, text, refinement, repeat=False):
|
||||
if not text:
|
||||
return
|
||||
return self.search.search_done(False)
|
||||
if self.view.search(text):
|
||||
self.scrolled(self.view.scroll_fraction)
|
||||
return
|
||||
return self.search.search_done(True)
|
||||
index = self.iterator.search(text, self.current_index)
|
||||
if index is None:
|
||||
if self.current_index > 0:
|
||||
@ -419,8 +420,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
if index is None:
|
||||
info_dialog(self, _('No matches found'),
|
||||
_('No matches found for: %s')%text).exec_()
|
||||
return
|
||||
return
|
||||
return self.search.search_done(True)
|
||||
return self.search.search_done(True)
|
||||
self.pending_search = text
|
||||
self.load_path(self.iterator.spine[index])
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user