mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Spell check: Add a context (right click) menu that allows for quick operations on all selected words
This commit is contained in:
parent
0a755a2b66
commit
4f159e126d
@ -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():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user