Implement adding and removing of words from user dictionaries

This commit is contained in:
Kovid Goyal 2014-04-15 09:46:36 +05:30
parent 50f4dd503f
commit 03832a6070
3 changed files with 156 additions and 20 deletions

View File

@ -74,6 +74,7 @@ TOP = object()
dictionaries = Dictionaries()
def set_book_locale(lang):
dictionaries.initialize()
try:
dictionaries.default_locale = parse_lang_code(lang)
if dictionaries.default_locale.langcode == 'und':

View File

@ -14,7 +14,8 @@ from PyQt4.Qt import (
QGridLayout, QApplication, QTreeWidget, QTreeWidgetItem, Qt, QFont, QSize,
QStackedLayout, QLabel, QVBoxLayout, QVariant, QWidget, QPushButton, QIcon,
QDialogButtonBox, QLineEdit, QDialog, QToolButton, QFormLayout, QHBoxLayout,
pyqtSignal, QAbstractTableModel, QModelIndex, QTimer, QTableView, QCheckBox)
pyqtSignal, QAbstractTableModel, QModelIndex, QTimer, QTableView, QCheckBox,
QComboBox)
from calibre.constants import __appname__
from calibre.gui2 import choose_files, error_dialog
@ -416,6 +417,20 @@ class WordsModel(QAbstractTableModel):
self.spell_map[w] = dictionaries.recognized(*w)
self.update_word(w)
def add_word(self, row, udname):
w = self.word_for_row(row)
if w is not None:
if dictionaries.add_to_user_dictionary(udname, *w):
self.spell_map[w] = dictionaries.recognized(*w)
self.update_word(w)
def remove_word(self, row):
w = self.word_for_row(row)
if w is not None:
if dictionaries.remove_from_user_dictionaries(*w):
self.spell_map[w] = dictionaries.recognized(*w)
self.update_word(w)
def update_word(self, w):
should_be_filtered = not self.filter_item(w)
row = self.row_for_word(w)
@ -518,7 +533,20 @@ class SpellCheck(Dialog):
l = QVBoxLayout()
l.addStrut(250)
h.addLayout(l)
l.addWidget(b)
l.addWidget(b), l.addSpacing(20)
self.add_button = b = QPushButton(_('&Add word to dictionary:'))
b.add_text, b.remove_text = unicode(b.text()), _('&Remove word from user dictionaries')
b.clicked.connect(self.add_remove)
self.user_dictionaries = d = QComboBox(self)
self.user_dictionaries_missing_label = la = QLabel(_(
'You have no active user dictionaries. You must'
' choose at least one active user dictionary via'
' Preferences->Editor->Manage spelling dictionaries'))
la.setWordWrap(True)
self.initialize_user_dictionaries()
d.setMinimumContentsLength(25)
l.addWidget(b), l.addWidget(d), l.addWidget(la)
l.addStretch(1)
hh.setSectionHidden(3, m.show_only_misspelt)
self.show_only_misspelled = om = QCheckBox(_('Show only misspelled words'))
@ -528,8 +556,23 @@ class SpellCheck(Dialog):
self.summary = s = QLabel('')
self.main.l.addLayout(h), h.addWidget(s), h.addWidget(om), h.addStretch(10)
def initialize_user_dictionaries(self):
ct = unicode(self.user_dictionaries.currentText())
self.user_dictionaries.clear()
self.user_dictionaries.addItems([d.name for d in dictionaries.active_user_dictionaries])
if ct:
idx = self.user_dictionaries.findText(ct)
if idx > -1:
self.user_dictionaries.setCurrentIndex(idx)
self.user_dictionaries.setVisible(self.user_dictionaries.count() > 0)
self.user_dictionaries_missing_label.setVisible(not self.user_dictionaries.isVisible())
def current_word_changed(self, *args):
ignored = recognized = False
try:
b = self.ignore_button
except AttributeError:
return
ignored = recognized = in_user_dictionary = False
current = self.words_view.currentIndex()
current_word = ''
if current.isValid():
@ -539,20 +582,31 @@ class SpellCheck(Dialog):
ignored = dictionaries.is_word_ignored(*w)
recognized = self.words_model.spell_map[w]
current_word = w[0]
if recognized:
in_user_dictionary = dictionaries.word_in_user_dictionary(*w)
try:
b = self.ignore_button
except AttributeError:
return
prefix = b.unign_text if ignored else b.ign_text
b.setText(prefix + ' ' + current_word)
b.setEnabled(current.isValid() and (ignored or not recognized))
if not self.user_dictionaries_missing_label.isVisible():
b = self.add_button
b.setText(b.remove_text if in_user_dictionary else b.add_text)
self.user_dictionaries.setVisible(not in_user_dictionary)
def toggle_ignore(self):
current = self.words_view.currentIndex()
if current.isValid():
self.words_model.toggle_ignored(current.row())
def add_remove(self):
current = self.words_view.currentIndex()
if current.isValid():
if self.user_dictionaries.isVisible(): # add
udname = unicode(self.user_dictionaries.currentText())
self.words_model.add_word(current.row(), udname)
else:
self.words_model.remove_word(current.row())
def update_show_only_misspelt(self):
m = self.words_model
m.show_only_misspelt = self.show_only_misspelled.isChecked()
@ -630,6 +684,7 @@ class SpellCheck(Dialog):
col, Qt.DescendingOrder if reverse else Qt.AscendingOrder)
self.highlight_row(0)
self.update_summary()
self.initialize_user_dictionaries()
def update_summary(self):
self.summary.setText(_('Misspelled words: {0} Total words: {1}').format(*self.words_model.counts))

View File

@ -9,6 +9,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import cPickle, os, glob, shutil
from collections import namedtuple
from operator import attrgetter
from itertools import chain
from calibre.constants import plugins, config_dir
from calibre.utils.config import JSONConfig
@ -23,8 +24,22 @@ if hunspell is None:
dprefs = JSONConfig('dictionaries/prefs.json')
dprefs.defaults['preferred_dictionaries'] = {}
dprefs.defaults['preferred_locales'] = {}
dprefs.defaults['user_dictionaries'] = [{'name':_('Default'), 'is_active':True, 'words':[]}]
not_present = object()
class UserDictionary(object):
__slots__ = ('name', 'is_active', 'words')
def __init__(self, **kwargs):
self.name = kwargs['name']
self.is_active = kwargs['is_active']
self.words = {(w, langcode) for w, langcode in kwargs['words']}
def serialize(self):
return {'name':self.name, 'is_active': self.is_active, 'words':[
(w, l) for w, l in self.words]}
ccodes, ccodemap, country_names = None, None, None
def get_codes():
global ccodes, ccodemap, country_names
@ -169,6 +184,10 @@ class Dictionaries(object):
self.default_locale = parse_lang_code('en-US')
self.ui_locale = self.default_locale
def initialize(self):
if not hasattr(self, 'active_user_dictionaries'):
self.read_user_dictionaries()
def clear_caches(self):
self.dictionaries.clear(), self.word_cache.clear()
@ -185,26 +204,86 @@ class Dictionaries(object):
return ans
def ignore_word(self, word, locale):
self.ignored_words.add((word, locale))
self.ignored_words.add((word, locale.langcode))
self.word_cache[(word, locale)] = True
def unignore_word(self, word, locale):
self.ignored_words.discard((word, locale))
self.ignored_words.discard((word, locale.langcode))
self.word_cache.pop((word, locale), None)
def is_word_ignored(self, word, locale):
return (word, locale) in self.ignored_words
return (word, locale.langcode) in self.ignored_words
@property
def all_user_dictionaries(self):
return chain(self.active_user_dictionaries, self.inactive_user_dictionaries)
def user_dictionary(self, name):
for ud in self.all_user_dictionaries:
if ud.name == name:
return ud
def read_user_dictionaries(self):
self.active_user_dictionaries = []
self.inactive_user_dictionaries = []
for d in dprefs['user_dictionaries']:
d = UserDictionary(**d)
(self.active_user_dictionaries if d.is_active else self.inactive_user_dictionaries).append(d)
def save_user_dictionaries(self):
dprefs['user_dictionaries'] = [d.serialize() for d in self.all_user_dictionaries]
def add_to_user_dictionary(self, name, word, locale):
ud = self.user_dictionary(name)
if ud is None:
raise ValueError('Cannot add to the dictionary named: %s as no such dictionary exists' % name)
wl = len(ud.words)
ud.words.add((word, locale.langcode))
if len(ud.words) > wl:
self.save_user_dictionaries()
self.word_cache.pop((word, locale), None)
return True
return False
def remove_from_user_dictionaries(self, word, locale):
key = (word, locale.langcode)
changed = False
for ud in self.active_user_dictionaries:
if key in ud.words:
changed = True
ud.words.discard(key)
if changed:
self.word_cache.pop((word, locale), None)
self.save_user_dictionaries()
return changed
def word_in_user_dictionary(self, word, locale):
key = (word, locale.langcode)
for ud in self.active_user_dictionaries:
if key in ud.words:
return ud.name
def create_user_dictionary(self, name):
if name in {d.name for d in self.all_user_dictionaries}:
raise ValueError('A dictionary named %s already exists' % name)
d = UserDictionary(name=name, is_active=True, words=())
self.active_user_dictionaries.append(d)
self.save_user_dictionaries()
def recognized(self, word, locale=None):
locale = locale or self.default_locale
if not isinstance(locale, DictionaryLocale):
locale = parse_lang_code(locale)
key = (word, locale)
ans = self.word_cache.get(key, None)
if ans is None:
lkey = (word, locale.langcode)
ans = False
if key in self.ignored_words:
if lkey in self.ignored_words:
ans = True
else:
for ud in self.active_user_dictionaries:
if lkey in ud.words:
ans = True
break
else:
d = self.dictionary_for_locale(locale)
if d is not None:
@ -218,4 +297,5 @@ class Dictionaries(object):
if __name__ == '__main__':
dictionaries = Dictionaries()
print (dictionaries.recognized('recognized', 'en'))
dictionaries.initialize()
print (dictionaries.recognized('recognized', parse_lang_code('en')))