Work on word lookup

This commit is contained in:
Kovid Goyal 2019-08-20 13:53:03 +05:30
parent 5503ad9d12
commit 3772cfbfba
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 157 additions and 10 deletions

View File

@ -0,0 +1,113 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
import os
from PyQt5.Qt import (
QApplication, QComboBox, QHBoxLayout, QLabel, Qt, QTimer, QUrl, QVBoxLayout,
QWidget
)
from PyQt5.QtWebEngineWidgets import (
QWebEnginePage, QWebEngineProfile, QWebEngineView
)
from calibre import random_user_agent
from calibre.constants import cache_dir
from calibre.gui2.viewer.web_view import vprefs
from calibre.gui2.webengine import secure_webengine
vprefs.defaults['lookup_locations'] = [
{
'name': 'Google dictionary',
'url': 'https://google.com/search?q=define:{word}',
'langs': [],
},
{
'name': 'Google search',
'url': 'https://google.com/search?q={word}',
'langs': [],
},
{
'name': 'Wordnik',
'url': 'https://www.wordnik.com/words/{word}',
'langs': ['eng'],
},
]
vprefs.defaults['lookup_location'] = 'Google dictionary'
def create_profile():
ans = getattr(create_profile, 'ans', None)
if ans is None:
ans = QWebEngineProfile('viewer-lookup', QApplication.instance())
ans.setHttpUserAgent(random_user_agent(allow_ie=False))
ans.setCachePath(os.path.join(cache_dir(), 'ev2vl'))
s = ans.settings()
s.setDefaultTextEncoding('utf-8')
create_profile.ans = ans
return ans
class Lookup(QWidget):
def __init__(self, parent):
QWidget.__init__(self, parent)
self.is_visible = False
self.selected_text = ''
self.current_query = ''
self.current_source = ''
self.l = l = QVBoxLayout(self)
self.h = h = QHBoxLayout()
l.addLayout(h)
self.debounce_timer = t = QTimer(self)
t.setInterval(150), t.timeout.connect(self.update_query)
self.source_box = sb = QComboBox(self)
self.label = la = QLabel(_('Lookup &in:'))
h.addWidget(la), h.addWidget(sb), la.setBuddy(sb)
self.view = QWebEngineView(self)
self._page = QWebEnginePage(create_profile(), self.view)
secure_webengine(self._page, for_viewer=True)
self.view.setPage(self._page)
l.addWidget(self.view)
self.populate_sources()
self.source_box.currentIndexChanged.connect(self.update_query)
def populate_sources(self):
sb = self.source_box
sb.clear()
for item in vprefs['lookup_locations']:
sb.addItem(item['name'], item)
idx = sb.findText(item['name'], Qt.MatchExactly)
if idx > -1:
self.setCurrentIndex(idx)
def visibility_changed(self, is_visible):
self.is_visible = is_visible
self.update_query()
@property
def url_template(self):
idx = self.source_box.currentIndex()
if idx > -1:
return self.source_box.itemData(idx)['url']
def update_query(self):
self.debounce_timer.stop()
query = self.selected_text or self.current_query
if self.current_query == query and self.current_source == self.url_template:
return
if not self.is_visible:
return
self.current_source = self.url_template
url = self.current_source.format(word=query)
self.view.load(QUrl(url))
self.current_query = query
def selected_text_changed(self, text):
self.selected_text = text or ''
self.debounce_timer.start()

View File

@ -27,6 +27,7 @@ from calibre.gui2.viewer.annotations import (
)
from calibre.gui2.viewer.bookmarks import BookmarkManager
from calibre.gui2.viewer.convert_book import prepare_book, update_book
from calibre.gui2.viewer.lookup import Lookup
from calibre.gui2.viewer.toc import TOC, TOCSearch, TOCView
from calibre.gui2.viewer.web_view import (
WebView, get_session_pref, set_book_path, vprefs
@ -79,6 +80,11 @@ 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.lookup_dock = create_dock(_('Lookup'), 'lookup-dock', Qt.RightDockWidgetArea)
self.lookup_widget = w = Lookup(self)
self.lookup_dock.visibilityChanged.connect(self.lookup_widget.visibility_changed)
self.lookup_dock.setWidget(w)
self.bookmarks_dock = create_dock(_('Bookmarks'), 'bookmarks-dock', Qt.RightDockWidgetArea)
self.bookmarks_widget = w = BookmarkManager(self)
connect_lambda(
@ -97,9 +103,11 @@ class EbookViewer(MainWindow):
self.web_view.toggle_toc.connect(self.toggle_toc)
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)
self.web_view.update_current_toc_nodes.connect(self.toc.update_current_toc_nodes)
self.web_view.toggle_full_screen.connect(self.toggle_full_screen)
self.web_view.ask_for_open.connect(self.ask_for_open, type=Qt.QueuedConnection)
self.web_view.selection_changed.connect(self.lookup_widget.selected_text_changed, type=Qt.QueuedConnection)
self.setCentralWidget(self.web_view)
self.restore_state()
if continue_reading:
@ -146,17 +154,15 @@ class EbookViewer(MainWindow):
# }}}
# ToC/Bookmarks {{{
def toggle_toc(self):
if self.toc_dock.isVisible():
self.toc_dock.setVisible(False)
else:
self.toc_dock.setVisible(True)
self.toc_dock.setVisible(not self.toc_dock.isVisible())
def toggle_bookmarks(self):
if self.bookmarks_dock.isVisible():
self.bookmarks_dock.setVisible(False)
else:
self.bookmarks_dock.setVisible(True)
self.bookmarks_dock.setVisible(not self.bookmarks_dock.isVisible())
def toggle_lookup(self):
self.lookup_dock.setVisible(not self.lookup_dock.isVisible())
def toc_clicked(self, index):
item = self.toc_model.itemFromIndex(index)

View File

@ -188,10 +188,12 @@ class ViewerBridge(Bridge):
toggle_toc = from_js()
toggle_bookmarks = from_js()
toggle_inspector = from_js()
toggle_lookup = from_js()
update_current_toc_nodes = from_js(object, object)
toggle_full_screen = from_js()
report_cfi = from_js(object, object)
ask_for_open = from_js(object)
selection_changed = from_js(object)
create_view = to_js()
show_preparing_message = to_js()
@ -307,9 +309,11 @@ class WebView(RestartingWebEngineView):
toggle_toc = pyqtSignal()
toggle_bookmarks = pyqtSignal()
toggle_inspector = pyqtSignal()
toggle_lookup = pyqtSignal()
update_current_toc_nodes = pyqtSignal(object, object)
toggle_full_screen = pyqtSignal()
ask_for_open = pyqtSignal(object)
selection_changed = pyqtSignal(object)
def __init__(self, parent=None):
self._host_widget = None
@ -328,9 +332,11 @@ class WebView(RestartingWebEngineView):
self.bridge.toggle_toc.connect(self.toggle_toc)
self.bridge.toggle_bookmarks.connect(self.toggle_bookmarks)
self.bridge.toggle_inspector.connect(self.toggle_inspector)
self.bridge.toggle_lookup.connect(self.toggle_lookup)
self.bridge.update_current_toc_nodes.connect(self.update_current_toc_nodes)
self.bridge.toggle_full_screen.connect(self.toggle_full_screen)
self.bridge.ask_for_open.connect(self.ask_for_open)
self.bridge.selection_changed.connect(self.selection_changed)
self.bridge.report_cfi.connect(self.call_callback)
self.pending_bridge_ready_actions = {}
self.setPage(self._page)

View File

@ -100,6 +100,7 @@ class IframeBoss:
window.addEventListener('wheel', self.onwheel, {'passive': False})
window.addEventListener('keydown', self.onkeydown, {'passive': False})
document.documentElement.addEventListener('contextmenu', self.oncontextmenu, {'passive': False})
document.addEventListener('selectionchange', self.onselectionchange)
self.color_scheme = data.color_scheme
create_touch_handlers()
@ -313,6 +314,10 @@ class IframeBoss:
return
self.onresize_stage2()
def onselectionchange(self):
if self.content_ready:
self.send_message('selectionchange', text=document.getSelection().toString())
def onresize_stage2(self):
if scroll_viewport.width() is self.last_window_width and scroll_viewport.height() is self.last_window_height:
# Safari at least, generates lots of spurious resize events

View File

@ -284,6 +284,10 @@ class MainOverlay: # {{{
actions_div.appendChild(E.ul(*full_screen_actions))
if runtime.is_standalone_viewer:
actions_div.appendChild(E.ul(
ac(_('Lookup/search word'), _('Lookup or search for the currently selected word'),
def(): self.overlay.hide(), ui_operations.toggle_lookup();, 'library')
))
actions_div.appendChild(E.ul(
ac(_('Inspector'), _('Show the content inspector'),
def(): self.overlay.hide(), ui_operations.toggle_inspector();, 'bug')
@ -293,8 +297,8 @@ class MainOverlay: # {{{
onclick=def (evt):evt.stopPropagation();,
set_css(E.div( # top row
E.div(self.overlay.view.book.metadata.title, style='max-width: 90%; text-overflow: ellipsis; font-weight: bold'),
E.div(self.date_formatter.format(Date()), id=self.timer_id, style='max-width: 9%; text-overflow: ellipsis'),
E.div(self.overlay.view.book.metadata.title, style='max-width: 90%; text-overflow: ellipsis; font-weight: bold; white-space: nowrap'),
E.div(self.date_formatter.format(Date()), id=self.timer_id, style='max-width: 9%; text-overflow: ellipsis; white-space: nowrap'),
), display='flex', justify_content='space-between', align_items='baseline', font_size='smaller', padding='0.5ex 1rem', border_bottom='solid 1px currentColor'
),
actions_div,

View File

@ -149,6 +149,7 @@ class View:
'show_footnote': self.on_show_footnote,
'print': self.on_print,
'human_scroll': self.on_human_scroll,
'selectionchange': self.on_selection_change,
}
entry_point = None if runtime.is_standalone_viewer else 'read_book.iframe'
self.iframe_wrapper = IframeWrapper(handlers, document.getElementById(iframe_id), entry_point, _('Bootstrapping book reader...'), runtime.FAKE_PROTOCOL, runtime.FAKE_HOST)
@ -164,6 +165,9 @@ class View:
return self.iframe_wrapper.iframe
def on_lookup_word(self, data):
if runtime.is_standalone_viewer:
ui_operations.selection_changed(data.word)
return
self.overlay.show_word_actions(data.word)
def left_margin_clicked(self, event):
@ -211,6 +215,11 @@ class View:
amt_scrolled = data.scrolled_by_frac * length
self.timers.on_human_scroll(amt_scrolled)
def on_selection_change(self, data):
self.currently_showing.selected_text = data.text
if ui_operations.selection_changed:
ui_operations.selection_changed(data.text)
def find(self, text, backwards):
self.iframe_wrapper.send_message('find', text=text, backwards=backwards, searched_in_spine=False)

View File

@ -261,6 +261,10 @@ if window is window.top:
to_python.toggle_bookmarks()
ui_operations.toggle_inspector = def():
to_python.toggle_inspector()
ui_operations.toggle_lookup = def():
to_python.toggle_lookup()
ui_operations.selection_changed = def(selected_text):
to_python.selection_changed(selected_text)
ui_operations.update_current_toc_nodes = def(current_node_id, top_level_node_id):
to_python.update_current_toc_nodes(current_node_id, top_level_node_id)
ui_operations.toggle_full_screen = def():