diff --git a/src/calibre/ebooks/oeb/polish/spell.py b/src/calibre/ebooks/oeb/polish/spell.py index 986a33e3c514..e1bea5ae34b3 100644 --- a/src/calibre/ebooks/oeb/polish/spell.py +++ b/src/calibre/ebooks/oeb/polish/spell.py @@ -221,7 +221,7 @@ def replace(text, original_word, new_word, lang): text = text[:idx] + new_word + text[idx+len(original_word):] return text, bool(indices) -def replace_word(container, new_word, locations, locale): +def replace_word(container, new_word, locations, locale, undo_cache=None): changed = set() for loc in locations: node = loc.location_node @@ -231,16 +231,26 @@ def replace_word(container, new_word, locations, locale): else: text = getattr(node, attr) replacement = loc.elided_prefix + new_word - text, replaced = replace(text, loc.original_word, replacement, locale.langcode) + rtext, replaced = replace(text, loc.original_word, replacement, locale.langcode) if replaced: + if undo_cache is not None: + undo_cache[(loc.file_name, node, is_attr, attr)] = text if is_attr: - node.set(attr, text) + node.set(attr, rtext) else: - setattr(node, attr, text) + setattr(node, attr, rtext) container.replace(loc.file_name, node.getroottree().getroot()) changed.add(loc.file_name) return changed +def undo_replace_word(container, undo_cache): + changed = set() + for (file_name, node, is_attr, attr), text in undo_cache.iteritems(): + node.set(attr, text) if is_attr else setattr(node, attr, text) + container.replace(file_name, node.getroottree().getroot()) + changed.add(file_name) + return changed + if __name__ == '__main__': import pprint from calibre.gui2.tweak_book import set_book_locale, dictionaries diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 97e7fc99f313..665ccf85c6e7 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -20,7 +20,7 @@ QT_VERSION_STR) from calibre.constants import __appname__, plugins -from calibre.ebooks.oeb.polish.spell import replace_word, get_all_words, merge_locations, get_checkable_file_names +from calibre.ebooks.oeb.polish.spell import replace_word, get_all_words, merge_locations, get_checkable_file_names, undo_replace_word from calibre.gui2 import choose_files, error_dialog from calibre.gui2.complete2 import LineEdit from calibre.gui2.languages import LanguagesEdit @@ -883,6 +883,7 @@ def __init__(self, parent=None): Dialog.__init__(self, _('Check spelling'), 'spell-check', parent) self.work_finished.connect(self.work_done, type=Qt.QueuedConnection) self.setAttribute(Qt.WA_DeleteOnClose, False) + self.undo_cache = {} def setup_ui(self): self.state_name = 'spell-check-table-state-' + QT_VERSION_STR.partition('.')[0] @@ -899,6 +900,10 @@ def setup_ui(self): 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(partial(self.refresh, change_request=None)) + b = self.bb.addButton(_('&Undo last change'), self.bb.ActionRole) + b.setToolTip('

' + _('Undo the last spell check word replacement, if any')) + b.setIcon(QIcon(I('edit-undo.png'))) + b.clicked.connect(self.undo_last_change) self.progress = p = QWidget(self) s.addWidget(p) @@ -1110,7 +1115,8 @@ def change_to(self, w, new_word): self.change_requested.emit(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]) + self.undo_cache.clear() + changed_files = replace_word(current_container(), new_word, self.words_model.words[w], w[1], undo_cache=self.undo_cache) if changed_files: self.word_replaced.emit(changed_files) w = self.words_model.replace_word(w, new_word) @@ -1118,6 +1124,16 @@ def do_change_word(self, w, new_word): if row > -1: self.words_view.highlight_row(row) + def undo_last_change(self): + if not self.undo_cache: + return error_dialog(self, _('No changed word'), _( + 'There is no spelling replacement to undo'), show=True) + changed_files = undo_replace_word(current_container(), self.undo_cache) + self.undo_cache.clear() + if changed_files: + self.word_replaced.emit(changed_files) + self.refresh() + def toggle_ignore(self): current = self.words_view.currentIndex() if current.isValid():