mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
Work on word lookup
This commit is contained in:
parent
5503ad9d12
commit
3772cfbfba
113
src/calibre/gui2/viewer/lookup.py
Normal file
113
src/calibre/gui2/viewer/lookup.py
Normal 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()
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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():
|
||||
|
Loading…
x
Reference in New Issue
Block a user