From 40204e0d3a754feb5948ee2c710741fd0c4e4e80 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 7 Apr 2014 12:07:33 +0530 Subject: [PATCH] Add/remove for dictionaries from OpenOffice extensions works --- src/calibre/gui2/tweak_book/spell.py | 52 +++++++++++++++++++--------- src/calibre/spell/dictionary.py | 31 ++++++++++------- src/calibre/spell/import_from.py | 21 ++++++++--- 3 files changed, 71 insertions(+), 33 deletions(-) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 782c75ffaa..c992b81035 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -19,7 +19,8 @@ from calibre.gui2 import choose_files, error_dialog 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) + get_dictionary, DictionaryLocale, dprefs, remove_dictionary) +from calibre.spell.import_from import import_from_oxt from calibre.utils.localization import calibre_langcode_to_name from calibre.utils.icu import sort_key @@ -35,7 +36,7 @@ def country_map(): _country_map = cPickle.loads(P('localization/iso3166.pickle', data=True, allow_user_override=False)) return _country_map -class AddDictionary(QDialog): +class AddDictionary(QDialog): # {{{ def __init__(self, parent=None): QDialog.__init__(self, parent) @@ -79,7 +80,7 @@ class AddDictionary(QDialog): def choose_file(self): path = choose_files(self, 'choose-dict-for-import', _('Choose OXT Dictionary'), filters=[ - (_('Dictionaries', ['oxt']))], all_files=False, select_only_single_file=True) + (_('Dictionaries'), ['oxt'])], all_files=False, select_only_single_file=True) if path is not None: self.path.setText(path[0]) if not self.nickname: @@ -98,7 +99,17 @@ class AddDictionary(QDialog): if nick in {d.name for d in custom_dictionaries()}: return error_dialog(self, _('Nickname already used'), _( 'A dictionary with the nick name "%s" already exists.'), show=True) + oxt = unicode(self.path.text()) + try: + num = import_from_oxt(oxt, nick) + except: + return error_dialog(self, _('Failed to import dictionaries'), _( + 'Failed to import dictionaries from %s. Click "Show Details" for more information') % oxt, show=True) + if num == 0: + return error_dialog(self, _('No dictionaries'), _( + 'No dictionaries were found in %s') % oxt, show=True) QDialog.accept(self) +# }}} class ManageDictionaries(Dialog): @@ -126,7 +137,8 @@ class ManageDictionaries(Dialog): 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) + self.remove_dictionary_button = rd = QPushButton(_('&Remove this dictionary'), w) + rd.clicked.connect(self.remove_dictionary) dl.addWidget(b), dl.addWidget(rd) w.setLayout(dl) s.addWidget(la) @@ -149,8 +161,8 @@ class ManageDictionaries(Dialog): b.clicked.connect(self.add_dictionary) l.addWidget(self.bb, l.rowCount(), 0, 1, l.columnCount()) - def build_dictionaries(self): - all_dictionaries = builtin_dictionaries() | custom_dictionaries() + def build_dictionaries(self, reread=False): + all_dictionaries = builtin_dictionaries() | custom_dictionaries(reread=reread) languages = defaultdict(lambda : defaultdict(set)) for d in all_dictionaries: for locale in d.locales | {d.primary_locale}: @@ -159,6 +171,7 @@ class ManageDictionaries(Dialog): bf.setBold(True) itf = QFont(self.dictionaries.font()) itf.setItalic(True) + self.dictionaries.clear() for lc in sorted(languages, key=lambda x:sort_key(calibre_langcode_to_name(x))): i = QTreeWidgetItem(self.dictionaries, LANG) @@ -174,12 +187,12 @@ class ManageDictionaries(Dialog): 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: + pl = calibre_langcode_to_name(dictionary.primary_locale.langcode) + if dictionary.primary_locale.countrycode: + pl += '-' + dictionary.primary_locale.countrycode.upper() + k.setText(0, dictionary.name or (_('').format(pl))) + k.setData(0, Qt.UserRole, dictionary) + if pd == dictionary: k.setData(0, Qt.FontRole, itf) self.dictionaries.expandAll() @@ -187,8 +200,15 @@ class ManageDictionaries(Dialog): def add_dictionary(self): d = AddDictionary(self) if d.exec_() == d.Accepted: - path = unicode(d.path.text()) - print (path) + self.build_dictionaries(reread=True) + + def remove_dictionary(self): + item = self.dictionaries.currentItem() + if item is not None and item.type() == DICTIONARY: + dic = item.data(0, Qt.UserRole).toPyObject() + if not dic.builtin: + remove_dictionary(dic) + self.build_dictionaries(reread=True) def current_item_changed(self): item = self.dictionaries.currentItem() @@ -242,7 +262,7 @@ class ManageDictionaries(Dialog): '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) + self.remove_dictionary_button.setEnabled(not item.data(0, Qt.UserRole).toPyObject().builtin) def set_favorite(self): item = self.dictionaries.currentItem() @@ -255,7 +275,7 @@ class ManageDictionaries(Dialog): d = item.data(0, Qt.UserRole).toPyObject() locale = '%s-%s' % (lc, cc) pl = dprefs['preferred_dictionaries'] - pl[locale] = d.name + pl[locale] = d.id dprefs['preferred_dictionaries'] = pl if __name__ == '__main__': diff --git a/src/calibre/spell/dictionary.py b/src/calibre/spell/dictionary.py index 694b859b86..677f4cc186 100644 --- a/src/calibre/spell/dictionary.py +++ b/src/calibre/spell/dictionary.py @@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import cPickle, os, glob +import cPickle, os, glob, shutil from collections import namedtuple from operator import attrgetter @@ -15,8 +15,8 @@ from calibre.utils.config import JSONConfig from calibre.utils.localization import get_lang, canonicalize_lang DictionaryLocale = namedtuple('DictionaryLocale', 'langcode countrycode') -Dictionary = namedtuple('Dictionary', 'primary_locale locales dicpath affpath builtin name') -LoadedDictionary = namedtuple('Dictionary', 'primary_locale locales obj builtin name') +Dictionary = namedtuple('Dictionary', 'primary_locale locales dicpath affpath builtin name id') +LoadedDictionary = namedtuple('Dictionary', 'primary_locale locales obj builtin name id') hunspell = plugins['hunspell'][0] if hunspell is None: raise RuntimeError('Failed to load hunspell: %s' % plugins[1]) @@ -60,15 +60,13 @@ def builtin_dictionaries(): base = os.path.dirname(lc) dics.append(Dictionary( parse_lang_code(locale), frozenset(map(parse_lang_code, locales)), os.path.join(base, '%s.dic' % locale), - os.path.join(base, '%s.aff' % locale), True, None)) + os.path.join(base, '%s.aff' % locale), True, None, None)) _builtins = frozenset(dics) return _builtins def custom_dictionaries(reread=False): global _custom - if reread: - _custom = None - if _custom is None: + if _custom is None or reread: dics = [] for lc in glob.glob(os.path.join(config_dir, 'dictionaries', '*/locales')): locales = filter(None, open(lc, 'rb').read().decode('utf-8').splitlines()) @@ -76,7 +74,7 @@ def custom_dictionaries(reread=False): base = os.path.dirname(lc) dics.append(Dictionary( parse_lang_code(locale), frozenset(map(parse_lang_code, locales)), os.path.join(base, '%s.dic' % locale), - os.path.join(base, '%s.aff' % locale), False, name)) + os.path.join(base, '%s.aff' % locale), False, name, os.path.basename(base))) _custom = frozenset(dics) return _custom @@ -88,7 +86,14 @@ def best_locale_for_language(langcode): 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) + return {parse_lang_code(k):v for k, v in dprefs['preferred_dictionaries'].iteritems()}.get(locale, None) + +def remove_dictionary(dictionary): + if dictionary.builtin: + raise ValueError('Cannot remove builtin dictionaries') + base = os.path.dirname(dictionary.dicpath) + shutil.rmtree(base) + dprefs['preferred_dictionaries'] = {k:v for k, v in dprefs['preferred_dictionaries'].iteritems() if v != dictionary.id} def get_dictionary(locale, exact_match=False): preferred = preferred_dictionary(locale) @@ -97,11 +102,11 @@ def get_dictionary(locale, exact_match=False): for collection in (custom_dictionaries(), builtin_dictionaries()): for d in collection: if d.primary_locale == locale: - exact_matches[d.name] = d + exact_matches[d.id] = d for d in collection: for q in d.locales: - if q == locale and d.name not in exact_matches: - exact_matches[d.name] = d + if q == locale and d.id not in exact_matches: + exact_matches[d.id] = d # If the user has specified a preferred dictionary for this locale, use it, # otherwise, if a builtin dictionary exists, use that @@ -134,7 +139,7 @@ def get_dictionary(locale, exact_match=False): def load_dictionary(dictionary): with open(dictionary.dicpath, 'rb') as dic, open(dictionary.affpath, 'rb') as aff: obj = hunspell.Dictionary(dic.read(), aff.read()) - return LoadedDictionary(dictionary.primary_locale, dictionary.locales, obj, dictionary.builtin, dictionary.name) + return LoadedDictionary(dictionary.primary_locale, dictionary.locales, obj, dictionary.builtin, dictionary.name, dictionary.id) class Dictionaries(object): diff --git a/src/calibre/spell/import_from.py b/src/calibre/spell/import_from.py index 49c9dcc901..bda9155dd3 100644 --- a/src/calibre/spell/import_from.py +++ b/src/calibre/spell/import_from.py @@ -6,10 +6,11 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import sys, glob, os, shutil +import sys, glob, os, shutil, tempfile from lxml import etree +from calibre.constants import config_dir from calibre.utils.zipfile import ZipFile NS_MAP = { @@ -60,13 +61,25 @@ def import_from_libreoffice_source_tree(source_path): if want_locales: raise Exception('Failed to find dictionaries for some wanted locales: %s' % want_locales) -def import_from_oxt(source_path): +def import_from_oxt(source_path, name, dest_dir=None, prefix='dic-'): + dest_dir = dest_dir or os.path.join(config_dir, 'dictionaries') + num = 0 with ZipFile(source_path) as zf: root = etree.fromstring(zf.open('META-INF/manifest.xml').read()) xcu = XPath('//manifest:file-entry[@manifest:media-type="application/vnd.sun.star.configuration-data"]')(root)[0].get( '{%s}full-path' % NS_MAP['manifest']) - dictionaries = {(dic.lstrip('/'), aff.lstrip('/')):locales for (dic, aff), locales in parse_xcu(zf.open(xcu).read(), origin='').iteritems()} - return dictionaries + for (dic, aff), locales in parse_xcu(zf.open(xcu).read(), origin='').iteritems(): + dic, aff = dic.lstrip('/'), aff.lstrip('/') + d = tempfile.mkdtemp(prefix=prefix, dir=dest_dir) + metadata = [name] + locales + with open(os.path.join(d, 'locales'), 'wb') as f: + f.write(('\n'.join(metadata)).encode('utf-8')) + with open(os.path.join(d, '%s.dic' % locales[0]), 'wb') as f: + shutil.copyfileobj(zf.open(dic), f) + with open(os.path.join(d, '%s.aff' % locales[0]), 'wb') as f: + shutil.copyfileobj(zf.open(aff), f) + num += 1 + return num if __name__ == '__main__': import_from_libreoffice_source_tree(sys.argv[-1])