mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement adding and removing of words from user dictionaries
This commit is contained in:
parent
50f4dd503f
commit
03832a6070
@ -74,6 +74,7 @@ TOP = object()
|
|||||||
dictionaries = Dictionaries()
|
dictionaries = Dictionaries()
|
||||||
|
|
||||||
def set_book_locale(lang):
|
def set_book_locale(lang):
|
||||||
|
dictionaries.initialize()
|
||||||
try:
|
try:
|
||||||
dictionaries.default_locale = parse_lang_code(lang)
|
dictionaries.default_locale = parse_lang_code(lang)
|
||||||
if dictionaries.default_locale.langcode == 'und':
|
if dictionaries.default_locale.langcode == 'und':
|
||||||
|
@ -14,7 +14,8 @@ from PyQt4.Qt import (
|
|||||||
QGridLayout, QApplication, QTreeWidget, QTreeWidgetItem, Qt, QFont, QSize,
|
QGridLayout, QApplication, QTreeWidget, QTreeWidgetItem, Qt, QFont, QSize,
|
||||||
QStackedLayout, QLabel, QVBoxLayout, QVariant, QWidget, QPushButton, QIcon,
|
QStackedLayout, QLabel, QVBoxLayout, QVariant, QWidget, QPushButton, QIcon,
|
||||||
QDialogButtonBox, QLineEdit, QDialog, QToolButton, QFormLayout, QHBoxLayout,
|
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.constants import __appname__
|
||||||
from calibre.gui2 import choose_files, error_dialog
|
from calibre.gui2 import choose_files, error_dialog
|
||||||
@ -416,6 +417,20 @@ class WordsModel(QAbstractTableModel):
|
|||||||
self.spell_map[w] = dictionaries.recognized(*w)
|
self.spell_map[w] = dictionaries.recognized(*w)
|
||||||
self.update_word(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):
|
def update_word(self, w):
|
||||||
should_be_filtered = not self.filter_item(w)
|
should_be_filtered = not self.filter_item(w)
|
||||||
row = self.row_for_word(w)
|
row = self.row_for_word(w)
|
||||||
@ -518,7 +533,20 @@ class SpellCheck(Dialog):
|
|||||||
l = QVBoxLayout()
|
l = QVBoxLayout()
|
||||||
l.addStrut(250)
|
l.addStrut(250)
|
||||||
h.addLayout(l)
|
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)
|
hh.setSectionHidden(3, m.show_only_misspelt)
|
||||||
self.show_only_misspelled = om = QCheckBox(_('Show only misspelled words'))
|
self.show_only_misspelled = om = QCheckBox(_('Show only misspelled words'))
|
||||||
@ -528,8 +556,23 @@ class SpellCheck(Dialog):
|
|||||||
self.summary = s = QLabel('')
|
self.summary = s = QLabel('')
|
||||||
self.main.l.addLayout(h), h.addWidget(s), h.addWidget(om), h.addStretch(10)
|
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):
|
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 = self.words_view.currentIndex()
|
||||||
current_word = ''
|
current_word = ''
|
||||||
if current.isValid():
|
if current.isValid():
|
||||||
@ -539,20 +582,31 @@ class SpellCheck(Dialog):
|
|||||||
ignored = dictionaries.is_word_ignored(*w)
|
ignored = dictionaries.is_word_ignored(*w)
|
||||||
recognized = self.words_model.spell_map[w]
|
recognized = self.words_model.spell_map[w]
|
||||||
current_word = w[0]
|
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
|
prefix = b.unign_text if ignored else b.ign_text
|
||||||
b.setText(prefix + ' ' + current_word)
|
b.setText(prefix + ' ' + current_word)
|
||||||
b.setEnabled(current.isValid() and (ignored or not recognized))
|
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):
|
def toggle_ignore(self):
|
||||||
current = self.words_view.currentIndex()
|
current = self.words_view.currentIndex()
|
||||||
if current.isValid():
|
if current.isValid():
|
||||||
self.words_model.toggle_ignored(current.row())
|
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):
|
def update_show_only_misspelt(self):
|
||||||
m = self.words_model
|
m = self.words_model
|
||||||
m.show_only_misspelt = self.show_only_misspelled.isChecked()
|
m.show_only_misspelt = self.show_only_misspelled.isChecked()
|
||||||
@ -630,6 +684,7 @@ class SpellCheck(Dialog):
|
|||||||
col, Qt.DescendingOrder if reverse else Qt.AscendingOrder)
|
col, Qt.DescendingOrder if reverse else Qt.AscendingOrder)
|
||||||
self.highlight_row(0)
|
self.highlight_row(0)
|
||||||
self.update_summary()
|
self.update_summary()
|
||||||
|
self.initialize_user_dictionaries()
|
||||||
|
|
||||||
def update_summary(self):
|
def update_summary(self):
|
||||||
self.summary.setText(_('Misspelled words: {0} Total words: {1}').format(*self.words_model.counts))
|
self.summary.setText(_('Misspelled words: {0} Total words: {1}').format(*self.words_model.counts))
|
||||||
|
@ -9,6 +9,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import cPickle, os, glob, shutil
|
import cPickle, os, glob, shutil
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from calibre.constants import plugins, config_dir
|
from calibre.constants import plugins, config_dir
|
||||||
from calibre.utils.config import JSONConfig
|
from calibre.utils.config import JSONConfig
|
||||||
@ -23,8 +24,22 @@ if hunspell is None:
|
|||||||
dprefs = JSONConfig('dictionaries/prefs.json')
|
dprefs = JSONConfig('dictionaries/prefs.json')
|
||||||
dprefs.defaults['preferred_dictionaries'] = {}
|
dprefs.defaults['preferred_dictionaries'] = {}
|
||||||
dprefs.defaults['preferred_locales'] = {}
|
dprefs.defaults['preferred_locales'] = {}
|
||||||
|
dprefs.defaults['user_dictionaries'] = [{'name':_('Default'), 'is_active':True, 'words':[]}]
|
||||||
not_present = object()
|
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
|
ccodes, ccodemap, country_names = None, None, None
|
||||||
def get_codes():
|
def get_codes():
|
||||||
global ccodes, ccodemap, country_names
|
global ccodes, ccodemap, country_names
|
||||||
@ -169,6 +184,10 @@ class Dictionaries(object):
|
|||||||
self.default_locale = parse_lang_code('en-US')
|
self.default_locale = parse_lang_code('en-US')
|
||||||
self.ui_locale = self.default_locale
|
self.ui_locale = self.default_locale
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
if not hasattr(self, 'active_user_dictionaries'):
|
||||||
|
self.read_user_dictionaries()
|
||||||
|
|
||||||
def clear_caches(self):
|
def clear_caches(self):
|
||||||
self.dictionaries.clear(), self.word_cache.clear()
|
self.dictionaries.clear(), self.word_cache.clear()
|
||||||
|
|
||||||
@ -185,37 +204,98 @@ class Dictionaries(object):
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
def ignore_word(self, word, locale):
|
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
|
self.word_cache[(word, locale)] = True
|
||||||
|
|
||||||
def unignore_word(self, word, locale):
|
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)
|
self.word_cache.pop((word, locale), None)
|
||||||
|
|
||||||
def is_word_ignored(self, word, locale):
|
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):
|
def recognized(self, word, locale=None):
|
||||||
locale = locale or self.default_locale
|
locale = locale or self.default_locale
|
||||||
if not isinstance(locale, DictionaryLocale):
|
|
||||||
locale = parse_lang_code(locale)
|
|
||||||
key = (word, locale)
|
key = (word, locale)
|
||||||
ans = self.word_cache.get(key, None)
|
ans = self.word_cache.get(key, None)
|
||||||
if ans is None:
|
if ans is None:
|
||||||
|
lkey = (word, locale.langcode)
|
||||||
ans = False
|
ans = False
|
||||||
if key in self.ignored_words:
|
if lkey in self.ignored_words:
|
||||||
ans = True
|
ans = True
|
||||||
else:
|
else:
|
||||||
d = self.dictionary_for_locale(locale)
|
for ud in self.active_user_dictionaries:
|
||||||
if d is not None:
|
if lkey in ud.words:
|
||||||
try:
|
ans = True
|
||||||
ans = d.obj.recognized(word)
|
break
|
||||||
except ValueError:
|
else:
|
||||||
pass
|
d = self.dictionary_for_locale(locale)
|
||||||
|
if d is not None:
|
||||||
|
try:
|
||||||
|
ans = d.obj.recognized(word)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
self.word_cache[key] = ans
|
self.word_cache[key] = ans
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
dictionaries = Dictionaries()
|
dictionaries = Dictionaries()
|
||||||
print (dictionaries.recognized('recognized', 'en'))
|
dictionaries.initialize()
|
||||||
|
print (dictionaries.recognized('recognized', parse_lang_code('en')))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user