diff --git a/src/calibre/gui2/viewer/config.py b/src/calibre/gui2/viewer/config.py
index 69165b3ee5..7a071ef08f 100644
--- a/src/calibre/gui2/viewer/config.py
+++ b/src/calibre/gui2/viewer/config.py
@@ -10,15 +10,18 @@ __docformat__ = 'restructuredtext en'
import zipfile
from functools import partial
-from PyQt5.Qt import (QFont, QDialog, Qt, QColor, QColorDialog,
- QMenu, QInputDialog)
+from PyQt5.Qt import (
+ QFont, QDialog, Qt, QColor, QColorDialog, QMenu, QInputDialog,
+ QListWidgetItem, QFormLayout, QLabel, QLineEdit, QDialogButtonBox)
from calibre.constants import iswindows, isxp
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.viewer.config_ui import Ui_Dialog
-from calibre.utils.localization import get_language
def config(defaults=None):
desc = _('Options to customize the ebook viewer')
@@ -143,10 +146,14 @@ class ConfigDialog(QDialog, Ui_Dialog):
opts = config().parse()
self.load_options(opts)
self.init_load_themes()
+ self.init_dictionaries()
self.clear_search_history_button.clicked.connect(self.clear_search_history)
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):
from calibre.gui2 import config
config['viewer_search_history'] = []
@@ -190,9 +197,83 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.theming_message.setText(_('Deleted the theme named: %s')%
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):
opts = config('').parse()
self.load_options(opts)
+ from calibre.gui2.viewer.main import dprefs
+ self.word_lookups = dprefs.defaults['word_lookups']
def load_options(self, opts):
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())
for x in ('top', 'bottom', 'side'):
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
diff --git a/src/calibre/gui2/viewer/config.ui b/src/calibre/gui2/viewer/config.ui
index 9900ba45d3..9b325f6398 100644
--- a/src/calibre/gui2/viewer/config.ui
+++ b/src/calibre/gui2/viewer/config.ui
@@ -67,8 +67,8 @@ QToolBox::tab:hover {
0
0
- 811
- 380
+ 371
+ 236
@@ -240,8 +240,8 @@ QToolBox::tab:hover {
0
0
- 811
- 380
+ 374
+ 211
@@ -370,8 +370,8 @@ QToolBox::tab:hover {
0
0
- 811
- 380
+ 799
+ 378
@@ -472,8 +472,8 @@ QToolBox::tab:hover {
0
0
- 811
- 380
+ 340
+ 70
@@ -551,8 +551,8 @@ QToolBox::tab:hover {
0
0
- 811
- 380
+ 384
+ 115
@@ -622,8 +622,8 @@ QToolBox::tab:hover {
0
0
- 811
- 380
+ 368
+ 167
@@ -853,6 +853,81 @@ QToolBox::tab:hover {
+
+
+ &Dictionaries
+
+
+ -
+
+
+ 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.
+
+
+ true
+
+
+
+ -
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ &Add website
+
+
+
+ :/images/plus.png:/images/plus.png
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ &Remove website
+
+
+
+ :/images/minus.png:/images/minus.png
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ &Change website
+
+
+
+ :/images/edit_input.png:/images/edit_input.png
+
+
+
+
+
+
+
diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py
index 017395cff9..0124b7dfdc 100644
--- a/src/calibre/gui2/viewer/main.py
+++ b/src/calibre/gui2/viewer/main.py
@@ -24,8 +24,11 @@ from calibre.customize.ui import available_input_formats
from calibre import as_unicode, force_unicode, isbytestring
from calibre.ptempfile import reset_base_dir
from calibre.utils.zipfile import BadZipfile
+from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1
vprefs = JSONConfig('viewer')
+dprefs = JSONConfig('viewer_dictionaries')
+dprefs.defaults['word_lookups'] = {}
class Worker(Thread):
@@ -48,6 +51,18 @@ class RecentAction(QAction):
self.path = path
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):
STATE_VERSION = 2
@@ -272,17 +287,14 @@ class EbookViewer(MainWindow):
at_start=True)
def lookup(self, word):
- from calibre.utils.localization import canonicalize_lang, lang_as_iso639_1
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'))
- if lang == 'en':
- prefix = 'https://www.wordnik.com/words/'
- else:
- prefix = 'http://%s.wiktionary.org/wiki/' % lang
- open_url(prefix + word)
+ try:
+ url = lookup_website(canonicalize_lang(self.view.current_language) or 'en').format(word=word)
+ except Exception:
+ traceback.print_exc()
+ 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):
from calibre.gui2.viewer.documentview import config