From 50f4dd503f240dc2069f80717eb6a2f38fa7eaf3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 14 Apr 2014 12:24:15 +0530 Subject: [PATCH] Implement ignoring of words --- src/calibre/gui2/tweak_book/boss.py | 1 + src/calibre/gui2/tweak_book/spell.py | 91 +++++++++++++++++++++++----- src/calibre/spell/dictionary.py | 30 +++++++-- 3 files changed, 102 insertions(+), 20 deletions(-) diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index ef599c9b60..a6d7846f1c 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -232,6 +232,7 @@ class Boss(QObject): det_msg=job.traceback, show=True) if cn: self.save_manager.clear_notify_data() + dictionaries.clear_ignored(), dictionaries.clear_caches() parse_worker.clear() container = job.result set_current_container(container) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index de1fbb311c..36c1d83095 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -304,10 +304,10 @@ class WordsModel(QAbstractTableModel): def __init__(self, parent=None): QAbstractTableModel.__init__(self, parent) self.counts = (0, 0) - self.words = {} - self.spell_map = {} + self.words = {} # Map of (word, locale) to location data for the word + self.spell_map = {} # Map of (word, locale) to dictionaries.recognized(word, locale) self.sort_on = (0, False) - self.items = [] + self.items = [] # The currently displayed items self.filter_expression = None self.show_only_misspelt = True self.headers = (_('Word'), _('Count'), _('Language'), _('Misspelled?')) @@ -371,8 +371,7 @@ class WordsModel(QAbstractTableModel): self.do_sort() self.endResetModel() - def do_sort(self): - col, reverse = self.sort_on + def sort_key(self, col): if col == 0: def key(w): return primary_sort_key(w[0]) @@ -385,7 +384,11 @@ class WordsModel(QAbstractTableModel): return (calibre_langcode_to_name(locale.langcode), locale.countrycode) else: key = self.spell_map.get - self.items.sort(key=key, reverse=reverse) + return key + + def do_sort(self): + col, reverse = self.sort_on + self.items.sort(key=self.sort_key(col), reverse=reverse) def set_data(self, words, spell_map): self.words, self.spell_map = words, spell_map @@ -395,14 +398,38 @@ class WordsModel(QAbstractTableModel): self.counts = (len([None for w, recognized in spell_map.iteritems() if not recognized]), len(self.words)) self.endResetModel() + def filter_item(self, x): + if self.show_only_misspelt and self.spell_map[x]: + return False + if self.filter_expression is not None and not primary_contains(self.filter_expression, x[0]): + return False + return True + def do_filter(self): - def filter_item(x): - if self.show_only_misspelt and self.spell_map[x]: - return False - if self.filter_expression is not None and not primary_contains(self.filter_expression, x[0]): - return False - return True - self.items = filter(filter_item, self.words) + self.items = filter(self.filter_item, self.words) + + def toggle_ignored(self, row): + w = self.word_for_row(row) + if w is not None: + ignored = dictionaries.is_word_ignored(*w) + (dictionaries.unignore_word if ignored else dictionaries.ignore_word)(*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) + if should_be_filtered and row != -1: + self.beginRemoveRows(QModelIndex(), row, row) + del self.items[row] + self.endRemoveRows() + elif not should_be_filtered and row == -1: + self.items.append(w) + self.do_sort() + row = self.row_for_word(w) + self.beginInsertRows(QModelIndex(), row, row) + self.endInsertRows() + self.dataChanged.emit(self.index(row, 3), self.index(row, 3)) def word_for_row(self, row): try: @@ -468,6 +495,7 @@ class SpellCheck(Dialog): m.h2 = h = QHBoxLayout() l.addLayout(h) self.words_view = w = QTableView(m) + w.currentChanged = self.current_word_changed state = tprefs.get('spell-check-table-state', None) hh = self.words_view.horizontalHeader() w.setSortingEnabled(True), w.setShowGrid(False), w.setAlternatingRowColors(True) @@ -476,19 +504,54 @@ class SpellCheck(Dialog): h.addWidget(w) self.words_model = m = WordsModel(self) w.setModel(m) + m.dataChanged.connect(self.current_word_changed) + m.modelReset.connect(self.current_word_changed) if state is not None: hh.restoreState(state) # Sort by the restored state, if any w.sortByColumn(hh.sortIndicatorSection(), hh.sortIndicatorOrder()) m.show_only_misspelt = hh.isSectionHidden(3) + self.ignore_button = b = QPushButton(_('&Ignore')) + b.ign_text, b.unign_text = unicode(b.text()), _('Un&ignore') + b.clicked.connect(self.toggle_ignore) + l = QVBoxLayout() + l.addStrut(250) + h.addLayout(l) + l.addWidget(b) + hh.setSectionHidden(3, m.show_only_misspelt) self.show_only_misspelled = om = QCheckBox(_('Show only misspelled words')) om.setChecked(m.show_only_misspelt) om.stateChanged.connect(self.update_show_only_misspelt) self.hb = h = QHBoxLayout() self.summary = s = QLabel('') - 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 current_word_changed(self, *args): + ignored = recognized = False + current = self.words_view.currentIndex() + current_word = '' + if current.isValid(): + row = current.row() + w = self.words_model.word_for_row(row) + if w is not None: + ignored = dictionaries.is_word_ignored(*w) + recognized = self.words_model.spell_map[w] + current_word = w[0] + + 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)) + + def toggle_ignore(self): + current = self.words_view.currentIndex() + if current.isValid(): + self.words_model.toggle_ignored(current.row()) def update_show_only_misspelt(self): m = self.words_model diff --git a/src/calibre/spell/dictionary.py b/src/calibre/spell/dictionary.py index 936427d92a..3d48505f92 100644 --- a/src/calibre/spell/dictionary.py +++ b/src/calibre/spell/dictionary.py @@ -162,6 +162,7 @@ class Dictionaries(object): def __init__(self): self.dictionaries = {} self.word_cache = {} + self.ignored_words = set() try: self.default_locale = parse_lang_code(get_lang()) except ValueError: @@ -171,6 +172,9 @@ class Dictionaries(object): def clear_caches(self): self.dictionaries.clear(), self.word_cache.clear() + def clear_ignored(self): + self.ignored_words.clear() + def dictionary_for_locale(self, locale): ans = self.dictionaries.get(locale, not_present) if ans is not_present: @@ -180,6 +184,17 @@ class Dictionaries(object): self.dictionaries[locale] = ans return ans + def ignore_word(self, word, locale): + self.ignored_words.add((word, locale)) + self.word_cache[(word, locale)] = True + + def unignore_word(self, word, locale): + self.ignored_words.discard((word, locale)) + self.word_cache.pop((word, locale), None) + + def is_word_ignored(self, word, locale): + return (word, locale) in self.ignored_words + def recognized(self, word, locale=None): locale = locale or self.default_locale if not isinstance(locale, DictionaryLocale): @@ -188,12 +203,15 @@ class Dictionaries(object): ans = self.word_cache.get(key, None) if ans is None: ans = False - d = self.dictionary_for_locale(locale) - if d is not None: - try: - ans = d.obj.recognized(word) - except ValueError: - pass + if key in self.ignored_words: + ans = True + else: + 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 return ans