mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
E-book viewer: Allow customizing the website that is used as a dictionary for looking up words in the viewer. Click the Preferences button in the viewer and choose the dictionaries tab to customize the website.
This commit is contained in:
parent
01847b8983
commit
3bdb87f90b
@ -10,15 +10,18 @@ __docformat__ = 'restructuredtext en'
|
|||||||
import zipfile
|
import zipfile
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt5.Qt import (QFont, QDialog, Qt, QColor, QColorDialog,
|
from PyQt5.Qt import (
|
||||||
QMenu, QInputDialog)
|
QFont, QDialog, Qt, QColor, QColorDialog, QMenu, QInputDialog,
|
||||||
|
QListWidgetItem, QFormLayout, QLabel, QLineEdit, QDialogButtonBox)
|
||||||
|
|
||||||
from calibre.constants import iswindows, isxp
|
from calibre.constants import iswindows, isxp
|
||||||
from calibre.utils.config import Config, StringConfig, JSONConfig
|
from calibre.utils.config import Config, StringConfig, JSONConfig
|
||||||
from calibre.gui2 import min_available_height
|
from calibre.utils.icu import sort_key
|
||||||
|
from calibre.utils.localization import get_language, calibre_langcode_to_name
|
||||||
|
from calibre.gui2 import min_available_height, error_dialog
|
||||||
|
from calibre.gui2.languages import LanguagesEdit
|
||||||
from calibre.gui2.shortcuts import ShortcutConfig
|
from calibre.gui2.shortcuts import ShortcutConfig
|
||||||
from calibre.gui2.viewer.config_ui import Ui_Dialog
|
from calibre.gui2.viewer.config_ui import Ui_Dialog
|
||||||
from calibre.utils.localization import get_language
|
|
||||||
|
|
||||||
def config(defaults=None):
|
def config(defaults=None):
|
||||||
desc = _('Options to customize the ebook viewer')
|
desc = _('Options to customize the ebook viewer')
|
||||||
@ -143,10 +146,14 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
opts = config().parse()
|
opts = config().parse()
|
||||||
self.load_options(opts)
|
self.load_options(opts)
|
||||||
self.init_load_themes()
|
self.init_load_themes()
|
||||||
|
self.init_dictionaries()
|
||||||
|
|
||||||
self.clear_search_history_button.clicked.connect(self.clear_search_history)
|
self.clear_search_history_button.clicked.connect(self.clear_search_history)
|
||||||
self.resize(self.width(), min(self.height(), max(575, min_available_height()-25)))
|
self.resize(self.width(), min(self.height(), max(575, min_available_height()-25)))
|
||||||
|
|
||||||
|
for x in 'add remove change'.split():
|
||||||
|
getattr(self, x + '_dictionary_website_button').clicked.connect(getattr(self, x + '_dictionary_website'))
|
||||||
|
|
||||||
def clear_search_history(self):
|
def clear_search_history(self):
|
||||||
from calibre.gui2 import config
|
from calibre.gui2 import config
|
||||||
config['viewer_search_history'] = []
|
config['viewer_search_history'] = []
|
||||||
@ -190,9 +197,83 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.theming_message.setText(_('Deleted the theme named: %s')%
|
self.theming_message.setText(_('Deleted the theme named: %s')%
|
||||||
theme[len('theme_'):])
|
theme[len('theme_'):])
|
||||||
|
|
||||||
|
def init_dictionaries(self):
|
||||||
|
from calibre.gui2.viewer.main import dprefs
|
||||||
|
self.word_lookups = dprefs['word_lookups']
|
||||||
|
|
||||||
|
@dynamic_property
|
||||||
|
def word_lookups(self):
|
||||||
|
def fget(self):
|
||||||
|
return dict(self.dictionary_list.item(i).data(Qt.UserRole) for i in range(self.dictionary_list.count()))
|
||||||
|
def fset(self, wl):
|
||||||
|
self.dictionary_list.clear()
|
||||||
|
for langcode, url in sorted(wl.iteritems(), key=lambda (lc, url):sort_key(calibre_langcode_to_name(lc))):
|
||||||
|
i = QListWidgetItem('%s: %s' % (calibre_langcode_to_name(langcode), url), self.dictionary_list)
|
||||||
|
i.setData(Qt.UserRole, (langcode, url))
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
|
||||||
|
def add_dictionary_website(self):
|
||||||
|
class AD(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setWindowTitle(_('Add a dictionary website'))
|
||||||
|
self.l = l = QFormLayout(self)
|
||||||
|
self.la = la = QLabel(
|
||||||
|
_('Choose a language and enter the website address (URL) for it below.'
|
||||||
|
' The URL must have %s in it, which will be replaced by the actual word being'
|
||||||
|
' looked up') % '{word}')
|
||||||
|
la.setWordWrap(True)
|
||||||
|
l.addRow(la)
|
||||||
|
self.le = LanguagesEdit(self)
|
||||||
|
l.addRow(_('&Language:'), self.le)
|
||||||
|
self.url = u = QLineEdit(self)
|
||||||
|
u.setMinimumWidth(350)
|
||||||
|
u.setPlaceholderText(_('For example: %s') % 'http://dictionary.com/{word}')
|
||||||
|
l.addRow(_('&URL:'), u)
|
||||||
|
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||||
|
l.addRow(bb)
|
||||||
|
bb.accepted.connect(self.accept), bb.rejected.connect(self.reject)
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
if '{word}' not in self.url.text():
|
||||||
|
return error_dialog(self, _('Invalid URL'), _(
|
||||||
|
'The URL {0} does not have {1} in it.').format(self.url.text(), '{word}'), show=True)
|
||||||
|
QDialog.accept(self)
|
||||||
|
|
||||||
|
d = AD(self)
|
||||||
|
if d.exec_() == d.Accepted:
|
||||||
|
url = d.url.text()
|
||||||
|
if url:
|
||||||
|
wl = self.word_lookups
|
||||||
|
for lc in d.le.lang_codes:
|
||||||
|
wl[lc] = url
|
||||||
|
self.word_lookups = wl
|
||||||
|
|
||||||
|
def remove_dictionary_website(self):
|
||||||
|
idx = self.dictionary_list.currentIndex()
|
||||||
|
if idx.isValid():
|
||||||
|
lc, url = idx.data(Qt.UserRole)
|
||||||
|
wl = self.word_lookups
|
||||||
|
wl.pop(lc, None)
|
||||||
|
self.word_lookups = wl
|
||||||
|
|
||||||
|
def change_dictionary_website(self):
|
||||||
|
idx = self.dictionary_list.currentIndex()
|
||||||
|
if idx.isValid():
|
||||||
|
lc, url = idx.data(Qt.UserRole)
|
||||||
|
url, ok = QInputDialog.getText(self, _('Enter new website'), 'URL:', text=url)
|
||||||
|
if ok:
|
||||||
|
wl = self.word_lookups
|
||||||
|
wl[lc] = url
|
||||||
|
self.word_lookups = wl
|
||||||
|
|
||||||
def restore_defaults(self):
|
def restore_defaults(self):
|
||||||
opts = config('').parse()
|
opts = config('').parse()
|
||||||
self.load_options(opts)
|
self.load_options(opts)
|
||||||
|
from calibre.gui2.viewer.main import dprefs
|
||||||
|
self.word_lookups = dprefs.defaults['word_lookups']
|
||||||
|
|
||||||
def load_options(self, opts):
|
def load_options(self, opts):
|
||||||
self.opt_remember_window_size.setChecked(opts.remember_window_size)
|
self.opt_remember_window_size.setChecked(opts.remember_window_size)
|
||||||
@ -320,5 +401,5 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
c.set('show_controls', self.opt_show_controls.isChecked())
|
c.set('show_controls', self.opt_show_controls.isChecked())
|
||||||
for x in ('top', 'bottom', 'side'):
|
for x in ('top', 'bottom', 'side'):
|
||||||
c.set(x+'_margin', int(getattr(self, 'opt_%s_margin'%x).value()))
|
c.set(x+'_margin', int(getattr(self, 'opt_%s_margin'%x).value()))
|
||||||
|
from calibre.gui2.viewer.main import dprefs
|
||||||
|
dprefs['word_lookups'] = self.word_lookups
|
||||||
|
@ -67,8 +67,8 @@ QToolBox::tab:hover {
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>811</width>
|
<width>371</width>
|
||||||
<height>380</height>
|
<height>236</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="label">
|
<attribute name="label">
|
||||||
@ -240,8 +240,8 @@ QToolBox::tab:hover {
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>811</width>
|
<width>374</width>
|
||||||
<height>380</height>
|
<height>211</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="label">
|
<attribute name="label">
|
||||||
@ -370,8 +370,8 @@ QToolBox::tab:hover {
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>811</width>
|
<width>799</width>
|
||||||
<height>380</height>
|
<height>378</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="label">
|
<attribute name="label">
|
||||||
@ -472,8 +472,8 @@ QToolBox::tab:hover {
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>811</width>
|
<width>340</width>
|
||||||
<height>380</height>
|
<height>70</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="label">
|
<attribute name="label">
|
||||||
@ -551,8 +551,8 @@ QToolBox::tab:hover {
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>811</width>
|
<width>384</width>
|
||||||
<height>380</height>
|
<height>115</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="label">
|
<attribute name="label">
|
||||||
@ -622,8 +622,8 @@ QToolBox::tab:hover {
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>811</width>
|
<width>368</width>
|
||||||
<height>380</height>
|
<height>167</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<attribute name="label">
|
<attribute name="label">
|
||||||
@ -853,6 +853,81 @@ QToolBox::tab:hover {
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QWidget" name="tab_5">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>&Dictionaries</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_25">
|
||||||
|
<property name="text">
|
||||||
|
<string>When you lookup a word, the viewer opens the word's definition in a dictionary website. The dictionary website is chosen based on the language of the book. You can customize the website used for a particular language here.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="dictionary_list"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="add_dictionary_website_button">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Add website</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/plus.png</normaloff>:/images/plus.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="remove_dictionary_website_button">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Remove website</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/minus.png</normaloff>:/images/minus.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="change_dictionary_website_button">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>&Change website</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="../../../../resources/images.qrc">
|
||||||
|
<normaloff>:/images/edit_input.png</normaloff>:/images/edit_input.png</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -24,8 +24,11 @@ from calibre.customize.ui import available_input_formats
|
|||||||
from calibre import as_unicode, force_unicode, isbytestring
|
from calibre import as_unicode, force_unicode, isbytestring
|
||||||
from calibre.ptempfile import reset_base_dir
|
from calibre.ptempfile import reset_base_dir
|
||||||
from calibre.utils.zipfile import BadZipfile
|
from calibre.utils.zipfile import BadZipfile
|
||||||
|
from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1
|
||||||
|
|
||||||
vprefs = JSONConfig('viewer')
|
vprefs = JSONConfig('viewer')
|
||||||
|
dprefs = JSONConfig('viewer_dictionaries')
|
||||||
|
dprefs.defaults['word_lookups'] = {}
|
||||||
|
|
||||||
class Worker(Thread):
|
class Worker(Thread):
|
||||||
|
|
||||||
@ -48,6 +51,18 @@ class RecentAction(QAction):
|
|||||||
self.path = path
|
self.path = path
|
||||||
QAction.__init__(self, os.path.basename(path), parent)
|
QAction.__init__(self, os.path.basename(path), parent)
|
||||||
|
|
||||||
|
def default_lookup_website(lang):
|
||||||
|
lang = lang_as_iso639_1(lang) or lang
|
||||||
|
if lang == 'en':
|
||||||
|
prefix = 'https://www.wordnik.com/words/'
|
||||||
|
else:
|
||||||
|
prefix = 'http://%s.wiktionary.org/wiki/' % lang
|
||||||
|
return prefix + '{word}'
|
||||||
|
|
||||||
|
def lookup_website(lang):
|
||||||
|
wm = dprefs['word_lookups']
|
||||||
|
return wm.get(lang, default_lookup_website(lang))
|
||||||
|
|
||||||
class EbookViewer(MainWindow):
|
class EbookViewer(MainWindow):
|
||||||
|
|
||||||
STATE_VERSION = 2
|
STATE_VERSION = 2
|
||||||
@ -272,17 +287,14 @@ class EbookViewer(MainWindow):
|
|||||||
at_start=True)
|
at_start=True)
|
||||||
|
|
||||||
def lookup(self, word):
|
def lookup(self, word):
|
||||||
from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1
|
|
||||||
from urllib import quote
|
from urllib import quote
|
||||||
lang = lang_as_iso639_1(self.view.current_language)
|
|
||||||
if not lang:
|
|
||||||
lang = canonicalize_lang(lang) or 'en'
|
|
||||||
word = quote(word.encode('utf-8'))
|
word = quote(word.encode('utf-8'))
|
||||||
if lang == 'en':
|
try:
|
||||||
prefix = 'https://www.wordnik.com/words/'
|
url = lookup_website(canonicalize_lang(self.view.current_language) or 'en').format(word=word)
|
||||||
else:
|
except Exception:
|
||||||
prefix = 'http://%s.wiktionary.org/wiki/' % lang
|
traceback.print_exc()
|
||||||
open_url(prefix + word)
|
url = default_lookup_website(canonicalize_lang(self.view.current_language) or 'en').format(word=word)
|
||||||
|
open_url(url)
|
||||||
|
|
||||||
def get_remember_current_page_opt(self):
|
def get_remember_current_page_opt(self):
|
||||||
from calibre.gui2.viewer.documentview import config
|
from calibre.gui2.viewer.documentview import config
|
||||||
|
Loading…
x
Reference in New Issue
Block a user