From 39e75804c75921187aaad2ae394cd7c5f9e02f13 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 13 Apr 2014 07:38:16 +0530 Subject: [PATCH] Start work on spell check dialog --- imgsrc/spell-check.svg | 558 +++++++++++++++++++++++++++ resources/images/spell-check.png | Bin 0 -> 9816 bytes src/calibre/gui2/tweak_book/spell.py | 297 +++++++++++++- 3 files changed, 849 insertions(+), 6 deletions(-) create mode 100644 imgsrc/spell-check.svg create mode 100644 resources/images/spell-check.png diff --git a/imgsrc/spell-check.svg b/imgsrc/spell-check.svg new file mode 100644 index 0000000000..cf6020410e --- /dev/null +++ b/imgsrc/spell-check.svg @@ -0,0 +1,558 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/spell-check.png b/resources/images/spell-check.png new file mode 100644 index 0000000000000000000000000000000000000000..d81996e4af1d6f2e71244de43d7340319e3ba234 GIT binary patch literal 9816 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' -import cPickle, os +import cPickle, os, sys from collections import defaultdict +from threading import Thread from PyQt4.Qt import ( - QGridLayout, QApplication, QTreeWidget, QTreeWidgetItem, Qt, QFont, + QGridLayout, QApplication, QTreeWidget, QTreeWidgetItem, Qt, QFont, QSize, QStackedLayout, QLabel, QVBoxLayout, QVariant, QWidget, QPushButton, QIcon, - QDialogButtonBox, QLineEdit, QDialog, QToolButton, QFormLayout, QHBoxLayout) + QDialogButtonBox, QLineEdit, QDialog, QToolButton, QFormLayout, QHBoxLayout, + pyqtSignal, QAbstractTableModel, QModelIndex, QTimer, QTableView) from calibre.constants import __appname__ from calibre.gui2 import choose_files, error_dialog +from calibre.gui2.progress_indicator import ProgressIndicator +from calibre.gui2.tweak_book import dictionaries, current_container, set_book_locale, tprefs 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, remove_dictionary, rename_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 +from calibre.utils.icu import sort_key, primary_sort_key, primary_contains LANG = 0 COUNTRY = 1 @@ -288,10 +292,291 @@ class ManageDictionaries(Dialog): # {{{ pl = dprefs['preferred_dictionaries'] pl[locale] = d.id dprefs['preferred_dictionaries'] = pl + + @classmethod + def test(cls): + d = cls() + d.exec_() # }}} +class WordsModel(QAbstractTableModel): + + def __init__(self, parent=None): + QAbstractTableModel.__init__(self, parent) + self.words = {} + self.spell_map = {} + self.sort_on = (0, False) + self.items = [] + self.filter_expression = None + self.show_only_misspelt = True + self.headers = (_('Word'), _('Count'), _('Language'), _('Misspelled?')) + + def rowCount(self, parent=QModelIndex()): + return len(self.items) + + def columnCount(self, parent=QModelIndex()): + return len(self.headers) + + def clear(self): + self.beginResetModel() + self.words = {} + self.spell_map = {} + self.items =[] + self.endResetModel() + + def headerData(self, section, orientation, role=Qt.DisplayRole): + if orientation == Qt.Horizontal: + if role == Qt.DisplayRole: + try: + return self.headers[section] + except IndexError: + pass + elif role == Qt.InitialSortOrderRole: + return Qt.DescendingOrder if section == 1 else Qt.AscendingOrder + + def data(self, index, role=Qt.DisplayRole): + try: + word, locale = self.items[index.row()] + except IndexError: + return + if role == Qt.DisplayRole: + col = index.column() + if col == 0: + return word + if col == 1: + return '%d' % len(self.words[(word, locale)]) + if col == 2: + pl = calibre_langcode_to_name(locale.langcode) + countrycode = locale.countrycode + if countrycode: + pl = '%s (%s)' % (pl, countrycode) + return pl + if col == 3: + return '' if self.spell_map[(word, locale)] else '✓' + if role == Qt.TextAlignmentRole: + return Qt.AlignVCenter | (Qt.AlignLeft if index.column() == 0 else Qt.AlignHCenter) + + def sort(self, column, order=Qt.AscendingOrder): + reverse = order != Qt.AscendingOrder + self.sort_on = (column, reverse) + self.beginResetModel() + self.do_sort() + self.endResetModel() + + def filter(self, filter_text, only_misspelt): + self.filter_expression = filter_text or None + self.show_only_misspelt = only_misspelt + self.beginResetModel() + self.do_filter() + self.do_sort() + self.endResetModel() + + def do_sort(self): + col, reverse = self.sort_on + if col == 0: + def key(w): + return primary_sort_key(w[0]) + elif col == 1: + def key(w): + return len(self.words[w]) + elif col == 2: + def key(w): + locale = w[1] + return (calibre_langcode_to_name(locale.langcode), locale.countrycode) + else: + key = self.spell_map.get + self.items.sort(key=key, reverse=reverse) + + def set_data(self, words, spell_map): + self.words, self.spell_map = words, spell_map + self.beginResetModel() + self.do_filter() + self.do_sort() + self.endResetModel() + + 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) + + def word_for_row(self, row): + try: + return self.items[row] + except IndexError: + pass + + def row_for_word(self, word): + try: + return self.items.index(word) + except ValueError: + return -1 + +class SpellCheck(Dialog): + + work_finished = pyqtSignal(object, object) + + def __init__(self, parent=None): + self.__current_word = None + self.thread = None + self.cancel = False + Dialog.__init__(self, _('Check spelling'), 'spell-check', parent) + self.work_finished.connect(self.work_done, type=Qt.QueuedConnection) + self.setAttribute(Qt.WA_DeleteOnClose, False) + + def setup_ui(self): + self.setWindowIcon(QIcon(I('spell-check.png'))) + self.l = l = QVBoxLayout(self) + self.setLayout(l) + self.stack = s = QStackedLayout() + l.addLayout(s) + l.addWidget(self.bb) + self.bb.clear() + self.bb.addButton(self.bb.Close) + b = self.bb.addButton(_('&Refresh'), self.bb.ActionRole) + b.setToolTip('

' + _('Re-scan the book for words, useful if you have edited the book since opening this dialog')) + b.setIcon(QIcon(I('view-refresh.png'))) + b.clicked.connect(self.refresh) + + self.progress = p = QWidget(self) + s.addWidget(p) + p.l = l = QVBoxLayout(p) + l.setAlignment(Qt.AlignCenter) + self.progress_indicator = pi = ProgressIndicator(self, 256) + l.addWidget(pi, alignment=Qt.AlignHCenter), l.addSpacing(10) + p.la = la = QLabel(_('Checking, please wait...')) + la.setStyleSheet('QLabel { font-size: 30pt; font-weight: bold }') + l.addWidget(la, alignment=Qt.AlignHCenter) + + self.main = m = QWidget(self) + s.addWidget(m) + m.l = l = QVBoxLayout(m) + m.h1 = h = QHBoxLayout() + l.addLayout(h) + self.filter_text = t = QLineEdit(self) + t.setPlaceholderText(_('Filter the list of words')) + t.textChanged.connect(self.do_filter) + m.fc = b = QToolButton(m) + b.setIcon(QIcon(I('clear_left.png'))), b.setToolTip(_('Clear filter')) + b.clicked.connect(t.clear) + h.addWidget(t), h.addWidget(b) + + m.h2 = h = QHBoxLayout() + l.addLayout(h) + self.words_view = w = QTableView(m) + state = tprefs.get('spell-check-table-state', None) + hh = self.words_view.horizontalHeader() + w.setSortingEnabled(True), w.setShowGrid(False), w.setAlternatingRowColors(True) + w.setSelectionBehavior(w.SelectRows) + w.verticalHeader().close() + h.addWidget(w) + self.words_model = m = WordsModel(self) + w.setModel(m) + if state is not None: + hh.restoreState(state) + # Sort by the restored state, if any + w.sortByColumn(hh.sortIndicatorSection(), hh.sortIndicatorOrder()) + + def highlight_row(self, row): + idx = self.words_model.index(row, 0) + if idx.isValid(): + self.words_view.selectRow(row) + self.words_view.setCurrentIndex(idx) + self.words_view.scrollTo(idx) + + def __enter__(self): + idx = self.words_view.currentIndex().row() + self.__current_word = self.words_model.word_for_row(idx) + + def __exit__(self, *args): + if self.__current_word is not None: + row = self.words_model.row_for_word(self.__current_word) + self.highlight_row(max(0, row)) + self.__current_word = None + + def do_filter(self): + text = unicode(self.filter_text.text()).strip() + with self: + self.words_model.filter(text, True) + + def refresh(self): + if not self.isVisible(): + return + self.cancel = True + if self.thread is not None: + self.thread.join() + self.stack.setCurrentIndex(0) + self.progress_indicator.startAnimation() + self.thread = Thread(target=self.get_words) + self.thread.daemon = True + self.cancel = False + self.thread.start() + + def get_words(self): + from calibre.ebooks.oeb.polish.spell import get_all_words + + try: + words = get_all_words(current_container(), dictionaries.default_locale) + spell_map = {w:dictionaries.recognized(*w) for w in words} + except: + import traceback + traceback.print_exc() + words = traceback.format_exc() + spell_map = {} + + if self.cancel: + self.end_work() + else: + self.work_finished.emit(words, spell_map) + + def end_work(self): + self.stack.setCurrentIndex(1) + self.progress_indicator.stopAnimation() + self.words_model.clear() + + def work_done(self, words, spell_map): + self.end_work() + if not isinstance(words, dict): + return error_dialog(self, _('Failed to check spelling'), _( + 'Failed to check spelling, click "Show details" for the full error information.'), + det_msg=words, show=True) + if not self.isVisible(): + return + self.words_model.set_data(words, spell_map) + col, reverse = self.words_model.sort_on + self.words_view.horizontalHeader().setSortIndicator( + col, Qt.DescendingOrder if reverse else Qt.AscendingOrder) + self.highlight_row(0) + + def sizeHint(self): + return QSize(1000, 650) + + def show(self): + Dialog.show(self) + QTimer.singleShot(0, self.refresh) + + def accept(self): + tprefs['spell-check-table-state'] = bytearray(self.words_view.horizontalHeader().saveState()) + Dialog.accept(self) + + def reject(self): + tprefs['spell-check-table-state'] = bytearray(self.words_view.horizontalHeader().saveState()) + Dialog.reject(self) + + @classmethod + def test(cls): + from calibre.ebooks.oeb.polish.container import get_container + from calibre.gui2.tweak_book import set_current_container + set_current_container(get_container(sys.argv[-1], tweak_mode=True)) + set_book_locale(current_container().mi.language) + d = cls() + QTimer.singleShot(0, d.refresh) + d.exec_() +# }}} if __name__ == '__main__': app = QApplication([]) - d = ManageDictionaries() - d.exec_() + SpellCheck.test() del app