diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 42b4ca9f60..526249d6ed 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -140,8 +140,8 @@ class SearchBox2(QComboBox): # {{{ icon = QIcon(I(icon)) return self.lineEdit().addAction(icon, position) - def initialize(self, opt_name, colorize=False, help_text=_('Search')): - self.as_you_type = config['search_as_you_type'] + def initialize(self, opt_name, colorize=False, help_text=_('Search'), as_you_type=None): + self.as_you_type = config['search_as_you_type'] if as_you_type is None else as_you_type self.opt_name = opt_name items = [] for item in config[opt_name]: diff --git a/src/calibre/gui2/viewer/search.py b/src/calibre/gui2/viewer/search.py new file mode 100644 index 0000000000..986d022f99 --- /dev/null +++ b/src/calibre/gui2/viewer/search.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python2 +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2020, Kovid Goyal + +from __future__ import absolute_import, division, print_function, unicode_literals + +import textwrap +from collections import namedtuple + +from PyQt5.Qt import ( + QCheckBox, QComboBox, QHBoxLayout, QIcon, Qt, QToolButton, QVBoxLayout, QWidget, + pyqtSignal +) + +from calibre.gui2.viewer.web_view import vprefs +from calibre.gui2.widgets2 import HistoryComboBox + +Search = namedtuple('Search', 'text mode case_sensitive backwards') + + +class SearchInput(QWidget): + + do_search = pyqtSignal(object) + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.l = l = QVBoxLayout(self) + l.setContentsMargins(0, 0, 0, 0) + h = QHBoxLayout() + h.setContentsMargins(0, 0, 0, 0) + l.addLayout(h) + + self.search_box = sb = HistoryComboBox(self) + sb.lineEdit().setPlaceholderText(_('Search')) + sb.lineEdit().setClearButtonEnabled(True) + sb.lineEdit().returnPressed.connect(self.find_next) + sb.initialize('viewer-search-box-history') + h.addWidget(sb) + + self.next_button = nb = QToolButton(self) + h.addWidget(nb) + nb.setFocusPolicy(Qt.NoFocus) + nb.setIcon(QIcon(I('arrow-down.png'))) + nb.clicked.connect(self.find_next) + nb.setToolTip(_('Find next match')) + + self.prev_button = nb = QToolButton(self) + h.addWidget(nb) + nb.setFocusPolicy(Qt.NoFocus) + nb.setIcon(QIcon(I('arrow-up.png'))) + nb.clicked.connect(self.find_previous) + nb.setToolTip(_('Find previous match')) + + h = QHBoxLayout() + h.setContentsMargins(0, 0, 0, 0) + l.addLayout(h) + self.query_type = qt = QComboBox(self) + qt.setFocusPolicy(Qt.NoFocus) + qt.addItem(_('Normal'), 'normal') + qt.addItem(_('Regex'), 'regex') + qt.setToolTip(textwrap.fill(_('Choose the type of search: Normal will search' + ' for the entered text, Regex will interpret the text as a' + ' regular expression'))) + qt.setCurrentIndex(qt.findData(vprefs.get('viewer-search-mode', 'normal') or 'normal')) + h.addWidget(qt) + + self.case_sensitive = cs = QCheckBox(_('Case sensitive'), self) + cs.setFocusPolicy(Qt.NoFocus) + cs.setChecked(bool(vprefs.get('viewer-search-case-sensitive', False))) + h.addWidget(cs) + + def search_query(self, backwards=False): + text = self.search_box.currentText().strip() + if text: + return Search(text, self.query_type.currentData() or 'normal', self.case_sensitive.isChecked(), backwards) + + def emit_search(self, backwards=False): + vprefs['viewer-search-case-sensitive'] = self.case_sensitive.isChecked() + vprefs['viewer-search-mode'] = self.query_type.currentData() + sq = self.search_query(backwards) + if sq is not None: + self.do_search.emit(sq) + + def find_next(self): + self.emit_search() + + def find_previous(self): + self.emit_search(backwards=True) + + def focus_input(self): + self.search_box.setFocus(Qt.OtherFocusReason) + le = self.search_box.lineEdit() + le.end(False) + le.selectAll() + + +class SearchPanel(QWidget): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.l = l = QVBoxLayout(self) + self.search_input = si = SearchInput(self) + l.addWidget(si) + l.addStretch(10) + + def focus_input(self): + self.search_input.focus_input() diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index a3ba6eaf5a..5299ba718a 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -33,6 +33,7 @@ from calibre.gui2.viewer.convert_book import ( ) from calibre.gui2.viewer.lookup import Lookup from calibre.gui2.viewer.overlay import LoadingOverlay +from calibre.gui2.viewer.search import SearchPanel from calibre.gui2.viewer.toc import TOC, TOCSearch, TOCView from calibre.gui2.viewer.toolbars import ActionsToolBar from calibre.gui2.viewer.web_view import ( @@ -68,6 +69,7 @@ def dock_defs(): d(_('Table of Contents'), 'toc', Qt.LeftDockWidgetArea), d(_('Lookup'), 'lookup', Qt.RightDockWidgetArea), d(_('Bookmarks'), 'bookmarks', Qt.RightDockWidgetArea) + d(_('Search'), 'search', Qt.LeftDockWidgetArea) d(_('Inspector'), 'inspector', Qt.RightDockWidgetArea, Qt.AllDockWidgetAreas) return ans @@ -134,6 +136,9 @@ class EbookViewer(MainWindow): w.l.addWidget(self.toc), w.l.addWidget(self.toc_search), w.l.setContentsMargins(0, 0, 0, 0) self.toc_dock.setWidget(w) + self.search_widget = w = SearchPanel(self) + self.search_dock.setWidget(w) + self.lookup_widget = w = Lookup(self) self.lookup_dock.visibilityChanged.connect(self.lookup_widget.visibility_changed) self.lookup_dock.setWidget(w) @@ -151,6 +156,7 @@ class EbookViewer(MainWindow): self.web_view.cfi_changed.connect(self.cfi_changed) self.web_view.reload_book.connect(self.reload_book) self.web_view.toggle_toc.connect(self.toggle_toc) + self.web_view.show_search.connect(self.show_search) self.web_view.toggle_bookmarks.connect(self.toggle_bookmarks) self.web_view.toggle_inspector.connect(self.toggle_inspector) self.web_view.toggle_lookup.connect(self.toggle_lookup) @@ -237,6 +243,10 @@ class EbookViewer(MainWindow): def toggle_toc(self): self.toc_dock.setVisible(not self.toc_dock.isVisible()) + def show_search(self): + self.search_dock.setVisible(True) + self.search_widget.focus_input() + def toggle_bookmarks(self): is_visible = self.bookmarks_dock.isVisible() self.bookmarks_dock.setVisible(not is_visible) diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index fc377d7340..65694b6f35 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -244,6 +244,7 @@ class ViewerBridge(Bridge): toggle_bookmarks = from_js() toggle_inspector = from_js() toggle_lookup = from_js() + show_search = from_js() quit = from_js() update_current_toc_nodes = from_js(object, object) toggle_full_screen = from_js() @@ -412,6 +413,7 @@ class WebView(RestartingWebEngineView): cfi_changed = pyqtSignal(object) reload_book = pyqtSignal() toggle_toc = pyqtSignal() + show_search = pyqtSignal() toggle_bookmarks = pyqtSignal() toggle_inspector = pyqtSignal() toggle_lookup = pyqtSignal() @@ -455,6 +457,7 @@ class WebView(RestartingWebEngineView): self.bridge.set_local_storage.connect(self.set_local_storage) self.bridge.reload_book.connect(self.reload_book) self.bridge.toggle_toc.connect(self.toggle_toc) + self.bridge.show_search.connect(self.show_search) self.bridge.toggle_bookmarks.connect(self.toggle_bookmarks) self.bridge.toggle_inspector.connect(self.toggle_inspector) self.bridge.toggle_lookup.connect(self.toggle_lookup) diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index c38cbc1fe1..9d31dd7659 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -529,7 +529,10 @@ class View: def show_search(self): self.hide_overlays() - self.search_overlay.show() + if runtime.is_standalone_viewer: + ui_operations.show_search() + else: + self.search_overlay.show() def show_content_popup(self): self.hide_overlays() diff --git a/src/pyj/viewer-main.pyj b/src/pyj/viewer-main.pyj index f3c4bef05a..90552dbba6 100644 --- a/src/pyj/viewer-main.pyj +++ b/src/pyj/viewer-main.pyj @@ -339,6 +339,8 @@ if window is window.top: to_python.toggle_inspector() ui_operations.content_file_changed = def(name): to_python.content_file_changed(name) + ui_operations.show_search = def(): + to_python.show_search() ui_operations.reset_interface = def(): sd = get_session_data() defaults = session_defaults()