Spell check: Add a context (right click) menu that allows for quick operations on all selected words

This commit is contained in:
Kovid Goyal 2014-04-20 10:50:15 +05:30
parent 0a755a2b66
commit 4f159e126d

View File

@ -9,10 +9,11 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import cPickle, os, sys import cPickle, os, sys
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
from threading import Thread from threading import Thread
from functools import partial
from PyQt4.Qt import ( from PyQt4.Qt import (
QGridLayout, QApplication, QTreeWidget, QTreeWidgetItem, Qt, QFont, QSize, QGridLayout, QApplication, QTreeWidget, QTreeWidgetItem, Qt, QFont, QSize,
QStackedLayout, QLabel, QVBoxLayout, QWidget, QPushButton, QIcon, QStackedLayout, QLabel, QVBoxLayout, QWidget, QPushButton, QIcon, QMenu,
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, QListWidget, QListWidgetItem, QInputDialog, QPlainTextEdit) QComboBox, QListWidget, QListWidgetItem, QInputDialog, QPlainTextEdit)
@ -667,6 +668,15 @@ 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 ignore_words(self, rows):
words = {self.word_for_row(r) for r in rows}
words.discard(None)
for w in words:
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 add_word(self, row, udname): def add_word(self, row, udname):
w = self.word_for_row(row) w = self.word_for_row(row)
if w is not None: if w is not None:
@ -674,6 +684,15 @@ 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_words(self, dicname, rows):
words = {self.word_for_row(r) for r in rows}
words.discard(None)
for w in words:
if not dictionaries.add_to_user_dictionary(dicname, *w):
dictionaries.remove_from_user_dictionary(dicname, [w])
self.spell_map[w] = dictionaries.recognized(*w)
self.update_word(w)
def remove_word(self, row): def remove_word(self, row):
w = self.word_for_row(row) w = self.word_for_row(row)
if w is not None: if w is not None:
@ -729,10 +748,14 @@ class WordsModel(QAbstractTableModel):
class WordsView(QTableView): class WordsView(QTableView):
ignore_all = pyqtSignal()
add_all = pyqtSignal(object)
change_to = pyqtSignal(object, object)
def __init__(self, parent=None): def __init__(self, parent=None):
QTableView.__init__(self, parent) QTableView.__init__(self, parent)
self.setSortingEnabled(True), self.setShowGrid(False), self.setAlternatingRowColors(True) self.setSortingEnabled(True), self.setShowGrid(False), self.setAlternatingRowColors(True)
self.setSelectionBehavior(self.SelectRows), self.setSelectionMode(self.SingleSelection) self.setSelectionBehavior(self.SelectRows)
self.setTabKeyNavigation(False) self.setTabKeyNavigation(False)
self.verticalHeader().close() self.verticalHeader().close()
@ -751,6 +774,27 @@ class WordsView(QTableView):
self.setCurrentIndex(idx) self.setCurrentIndex(idx)
self.scrollTo(idx) self.scrollTo(idx)
def contextMenuEvent(self, ev):
m = QMenu(self)
w = self.model().word_for_row(self.currentIndex().row())
if w is not None:
a = m.addAction(_('Change %s to') % w[0])
cm = QMenu()
a.setMenu(cm)
cm.addAction(_('Specify replacement manually'), partial(self.change_to.emit, w, None))
cm.addSeparator()
for s in dictionaries.suggestions(*w):
cm.addAction(s, partial(self.change_to.emit, w, s))
m.addAction(_('Ignore/Unignore all selected words'), self.ignore_all)
a = m.addAction(_('Add/Remove all selected words'))
am = QMenu()
a.setMenu(am)
for dic in sorted(dictionaries.active_user_dictionaries, key=lambda x:sort_key(x.name)):
am.addAction(dic.name, partial(self.add_all.emit, dic.name))
m.exec_(ev.globalPos())
class SpellCheck(Dialog): class SpellCheck(Dialog):
work_finished = pyqtSignal(object, object) work_finished = pyqtSignal(object, object)
@ -809,7 +853,10 @@ class SpellCheck(Dialog):
l.addLayout(h) l.addLayout(h)
self.words_view = w = WordsView(m) self.words_view = w = WordsView(m)
set_no_activate_on_click(w) set_no_activate_on_click(w)
w.ignore_all.connect(self.ignore_all)
w.add_all.connect(self.add_all)
w.activated.connect(self.word_activated) w.activated.connect(self.word_activated)
w.change_to.connect(self.change_to)
w.currentChanged = self.current_word_changed w.currentChanged = self.current_word_changed
state = tprefs.get('spell-check-table-state', None) state = tprefs.get('spell-check-table-state', None)
hh = self.words_view.horizontalHeader() hh = self.words_view.horizontalHeader()
@ -833,7 +880,7 @@ class SpellCheck(Dialog):
h.addLayout(l) h.addLayout(l)
h.setStretch(0, 1) h.setStretch(0, 1)
l.addWidget(b), l.addSpacing(20) l.addWidget(b), l.addSpacing(20)
self.add_button = b = QPushButton(_('Add to &dictionary:')) self.add_button = b = QPushButton(_('Add word to &dictionary:'))
b.add_text, b.remove_text = unicode(b.text()), _('Remove from &dictionaries') b.add_text, b.remove_text = unicode(b.text()), _('Remove from &dictionaries')
b.add_tt = _('Add the current word to the specified user dictionary') b.add_tt = _('Add the current word to the specified user dictionary')
b.remove_tt = _('Remove the current word from all active user dictionaries') b.remove_tt = _('Remove the current word from all active user dictionaries')
@ -962,6 +1009,16 @@ class SpellCheck(Dialog):
if w is None: if w is None:
return return
new_word = unicode(self.suggested_word.text()) new_word = unicode(self.suggested_word.text())
self.do_change_word(w, new_word)
def change_to(self, w, new_word):
if new_word is None:
self.suggested_word.setFocus(Qt.OtherFocusReason)
self.suggested_word.clear()
return
self.do_change_word(w, new_word)
def do_change_word(self, w, new_word):
changed_files = replace_word(current_container(), new_word, self.words_model.words[w], w[1]) changed_files = replace_word(current_container(), new_word, self.words_model.words[w], w[1])
if changed_files: if changed_files:
self.word_replaced.emit(changed_files) self.word_replaced.emit(changed_files)
@ -975,6 +1032,18 @@ class SpellCheck(Dialog):
if current.isValid(): if current.isValid():
self.words_model.toggle_ignored(current.row()) self.words_model.toggle_ignored(current.row())
def ignore_all(self):
rows = {i.row() for i in self.words_view.selectionModel().selectedRows()}
rows.discard(-1)
if rows:
self.words_model.ignore_words(rows)
def add_all(self, dicname):
rows = {i.row() for i in self.words_view.selectionModel().selectedRows()}
rows.discard(-1)
if rows:
self.words_model.add_words(dicname, rows)
def add_remove(self): def add_remove(self):
current = self.words_view.currentIndex() current = self.words_view.currentIndex()
if current.isValid(): if current.isValid():