diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py new file mode 100644 index 0000000000..1062d51a83 --- /dev/null +++ b/src/calibre/gui2/tweak_book/spell.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2014, Kovid Goyal ' + +import cPickle +from collections import defaultdict + +from PyQt4.Qt import ( + QGridLayout, QApplication, QTreeWidget, QTreeWidgetItem, Qt, QFont, + QStackedLayout, QLabel, QVBoxLayout, QVariant, QWidget, QPushButton) + +from calibre.gui2.tweak_book.widgets import Dialog +from calibre.spell.dictionary import ( + builtin_dictionaries, custom_dictionaries, best_locale_for_language, + get_dictionary, DictionaryLocale, dprefs) +from calibre.utils.localization import calibre_langcode_to_name +from calibre.utils.icu import sort_key + +LANG = 0 +COUNTRY = 1 +DICTIONARY = 2 + +_country_map = None + +def country_map(): + global _country_map + if _country_map is None: + _country_map = cPickle.loads(P('localization/iso3166.pickle', data=True, allow_user_override=False)) + return _country_map + +class ManageDictionaries(Dialog): + + def __init__(self, parent=None): + Dialog.__init__(self, _('Manage dictionaries'), 'manage-dictionaries', parent=parent) + + def sizeHint(self): + ans = Dialog.sizeHint(self) + ans.setWidth(ans.width() + 250) + ans.setHeight(ans.height() + 200) + return ans + + def setup_ui(self): + self.l = l = QGridLayout(self) + self.setLayout(l) + self.stack = s = QStackedLayout() + self.helpl = la = QLabel('

') + la.setWordWrap(True) + self.pcb = pc = QPushButton(self) + pc.clicked.connect(self.set_preferred_country) + self.lw = w = QWidget(self) + self.ll = ll = QVBoxLayout(w) + ll.addWidget(pc) + self.dw = w = QWidget(self) + self.dl = dl = QVBoxLayout(w) + self.fb = b = QPushButton(self) + b.clicked.connect(self.set_favorite) + self.remove_dictionary = rd = QPushButton(_('&Remove this dictionary'), w) + dl.addWidget(b), dl.addWidget(rd) + w.setLayout(dl) + s.addWidget(la) + s.addWidget(self.lw) + s.addWidget(w) + + self.dictionaries = d = QTreeWidget(self) + self.build_dictionaries() + d.setCurrentIndex(d.model().index(0, 0)) + d.header().close() + d.currentItemChanged.connect(self.current_item_changed) + self.current_item_changed() + l.addWidget(d) + l.addLayout(s, 0, 1) + + self.bb.clear() + self.bb.addButton(self.bb.Close) + l.addWidget(self.bb, l.rowCount(), 0, 1, l.columnCount()) + + def build_dictionaries(self): + all_dictionaries = builtin_dictionaries() | custom_dictionaries() + languages = defaultdict(lambda : defaultdict(set)) + for d in all_dictionaries: + for locale in d.locales | {d.primary_locale}: + languages[locale.langcode][locale.countrycode].add(d) + bf = QFont(self.dictionaries.font()) + bf.setBold(True) + itf = QFont(self.dictionaries.font()) + itf.setItalic(True) + + for lc in sorted(languages, key=lambda x:sort_key(calibre_langcode_to_name(x))): + i = QTreeWidgetItem(self.dictionaries, LANG) + i.setText(0, calibre_langcode_to_name(lc)) + i.setData(0, Qt.UserRole, lc) + best_country = getattr(best_locale_for_language(lc), 'countrycode', None) + for countrycode in sorted(languages[lc], key=lambda x: country_map()['names'].get(x, x)): + j = QTreeWidgetItem(i, COUNTRY) + j.setText(0, country_map()['names'].get(countrycode, countrycode)) + j.setData(0, Qt.UserRole, countrycode) + if countrycode == best_country: + j.setData(0, Qt.FontRole, bf) + pd = get_dictionary(DictionaryLocale(lc, countrycode)) + for dictionary in sorted(languages[lc][countrycode], key=lambda d:d.name): + k = QTreeWidgetItem(j, DICTIONARY) + pl = calibre_langcode_to_name(d.primary_locale.langcode) + if d.primary_locale.countrycode: + pl += '-' + d.primary_locale.countrycode.upper() + k.setText(0, d.name or (_('').format(pl))) + k.setData(0, Qt.UserRole, d) + if pd == d: + k.setData(0, Qt.FontRole, itf) + + self.dictionaries.expandAll() + + def current_item_changed(self): + item = self.dictionaries.currentItem() + if item is not None: + self.stack.setCurrentIndex(item.type()) + if item.type() == LANG: + self.init_language(item) + elif item.type() == COUNTRY: + self.init_country(item) + elif item.type() == DICTIONARY: + self.init_dictionary(item) + + def init_language(self, item): + self.helpl.setText(_( + '''

You can change the dictionaries used for any specified language.

+

A language can have many country specific variants. Each of these variants + can have one or more dictionaries assigned to it. The default variant for each language + is shown in bold to the left.

+

You can change the default country variant as well as changing the dictionaries used for + every variant.

+

When a book specifies its language as a plain language, without any country variant, + the default variant you choose here will be used.

+ ''')) + + def init_country(self, item): + pc = self.pcb + font = item.data(0, Qt.FontRole).toPyObject() + preferred = bool(font and font.bold()) + pc.setText((_( + 'This is already the preferred variant for the {1} language') if preferred else _( + 'Use this as the preferred variant for the {1} language')).format( + unicode(item.text(0)), unicode(item.parent().text(0)))) + pc.setEnabled(not preferred) + + def set_preferred_country(self): + item = self.dictionaries.currentItem() + bf = QFont(self.dictionaries.font()) + bf.setBold(True) + for x in (item.parent().child(i) for i in xrange(item.parent().childCount())): + x.setData(0, Qt.FontRole, bf if x is item else QVariant()) + lc = unicode(item.parent().data(0, Qt.UserRole).toPyObject()) + pl = dprefs['preferred_locales'] + pl[lc] = '%s-%s' % (lc, unicode(item.data(0, Qt.UserRole).toPyObject())) + dprefs['preferred_locales'] = pl + + def init_dictionary(self, item): + saf = self.fb + font = item.data(0, Qt.FontRole).toPyObject() + preferred = bool(font and font.italic()) + saf.setText((_( + 'This is already the preferred dictionary') if preferred else + _('Use this as the preferred dictionary'))) + saf.setEnabled(not preferred) + self.remove_dictionary.setEnabled(not item.data(0, Qt.UserRole).toPyObject().builtin) + + def set_favorite(self): + item = self.dictionaries.currentItem() + bf = QFont(self.dictionaries.font()) + bf.setItalic(True) + for x in (item.parent().child(i) for i in xrange(item.parent().childCount())): + x.setData(0, Qt.FontRole, bf if x is item else QVariant()) + cc = unicode(item.parent().data(0, Qt.UserRole).toPyObject()) + lc = unicode(item.parent().parent().data(0, Qt.UserRole).toPyObject()) + d = item.data(0, Qt.UserRole).toPyObject() + locale = '%s-%s' % (lc, cc) + pl = dprefs['preferred_dictionaries'] + pl[locale] = d.name + dprefs['preferred_dictionaries'] = pl + +if __name__ == '__main__': + app = QApplication([]) # noqa + d = ManageDictionaries() + d.exec_() diff --git a/src/calibre/spell/dictionary.py b/src/calibre/spell/dictionary.py index a3f0d62daa..694b859b86 100644 --- a/src/calibre/spell/dictionary.py +++ b/src/calibre/spell/dictionary.py @@ -82,8 +82,16 @@ def custom_dictionaries(reread=False): default_preferred_locales = {'eng':'en-US', 'deu':'de-DE', 'spa':'es-ES', 'fra':'fr-FR'} +def best_locale_for_language(langcode): + best_locale = dprefs['preferred_locales'].get(langcode, default_preferred_locales.get(langcode, None)) + if best_locale is not None: + return parse_lang_code(best_locale) + +def preferred_dictionary(locale): + return {parse_lang_code(k):v for k, v in dprefs['preferred_dictionaries']}.get(locale, None) + def get_dictionary(locale, exact_match=False): - preferred = {parse_lang_code(k):v for k, v in dprefs['preferred_dictionaries']}.get(locale, None) + preferred = preferred_dictionary(locale) # First find all dictionaries that match locale exactly exact_matches = {} for collection in (custom_dictionaries(), builtin_dictionaries()): @@ -110,9 +118,9 @@ def get_dictionary(locale, exact_match=False): # No dictionary matched the locale exactly, we will now fallback to # matching only on language. First see if a dictionary matching the # preferred locale for the language exists. - best_locale = dprefs['preferred_locales'].get(locale.langcode, default_preferred_locales.get(locale.langcode, None)) + best_locale = best_locale_for_language(locale.langcode) if best_locale is not None: - ans = get_dictionary(parse_lang_code(best_locale), exact_match=True) + ans = get_dictionary(best_locale, exact_match=True) if ans is not None: return ans