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:
Kovid Goyal 2014-10-01 15:32:16 +05:30
parent 01847b8983
commit 3bdb87f90b
3 changed files with 195 additions and 27 deletions

View File

@ -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

View File

@ -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>&amp;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>&amp;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>&amp;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>&amp;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>

View File

@ -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