Add controls to show next occurrence of selected word

This commit is contained in:
Kovid Goyal 2014-04-17 11:07:38 +05:30
parent e591101202
commit 303d5f04fe
4 changed files with 99 additions and 6 deletions

View File

@ -38,6 +38,7 @@ from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime
from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data, NewBook from calibre.gui2.tweak_book.editor.insert_resource import get_resource_data, NewBook
from calibre.gui2.tweak_book.preferences import Preferences from calibre.gui2.tweak_book.preferences import Preferences
from calibre.gui2.tweak_book.search import validate_search_request, run_search from calibre.gui2.tweak_book.search import validate_search_request, run_search
from calibre.gui2.tweak_book.spell import find_next as find_next_word
from calibre.gui2.tweak_book.widgets import ( from calibre.gui2.tweak_book.widgets import (
RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink, RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink,
InsertSemantics, BusyCursor, InsertTag) InsertSemantics, BusyCursor, InsertTag)
@ -110,6 +111,7 @@ class Boss(QObject):
self.gui.saved_searches.run_saved_searches.connect(self.run_saved_searches) self.gui.saved_searches.run_saved_searches.connect(self.run_saved_searches)
self.gui.central.search_panel.save_search.connect(self.save_search) self.gui.central.search_panel.save_search.connect(self.save_search)
self.gui.central.search_panel.show_saved_searches.connect(self.show_saved_searches) self.gui.central.search_panel.show_saved_searches.connect(self.show_saved_searches)
self.gui.spell_check.find_word.connect(self.find_word)
def preferences(self): def preferences(self):
p = Preferences(self.gui) p = Preferences(self.gui)
@ -696,6 +698,16 @@ class Boss(QObject):
run_search(state, action, ed, name, searchable_names, run_search(state, action, ed, name, searchable_names,
self.gui, self.show_editor, self.edit_file, self.show_current_diff, self.add_savepoint, self.rewind_savepoint, self.set_modified) self.gui, self.show_editor, self.edit_file, self.show_current_diff, self.add_savepoint, self.rewind_savepoint, self.set_modified)
def find_word(self, word, locations):
' Go to a word from the spell check dialog '
ed = self.gui.central.current_editor
name = None
for n, x in editors.iteritems():
if x is ed:
name = n
break
find_next_word(word, locations, ed, name, self.gui, self.show_editor, self.edit_file)
def saved_searches(self): def saved_searches(self):
self.gui.saved_searches.show(), self.gui.saved_searches.raise_() self.gui.saved_searches.show(), self.gui.saved_searches.raise_()
@ -1002,9 +1014,10 @@ class Boss(QObject):
editor.modification_state_changed.connect(self.editor_modification_state_changed) editor.modification_state_changed.connect(self.editor_modification_state_changed)
self.gui.central.add_editor(name, editor) self.gui.central.add_editor(name, editor)
def edit_file(self, name, syntax, use_template=None): def edit_file(self, name, syntax=None, use_template=None):
editor = editors.get(name, None) editor = editors.get(name, None)
if editor is None: if editor is None:
syntax = syntax or syntax_from_mime(name, guess_type(name))
if use_template is None: if use_template is None:
data = current_container().raw_data(name) data = current_container().raw_data(name)
if isbytestring(data) and syntax in {'html', 'css', 'text', 'xml'}: if isbytestring(data) and syntax in {'html', 'css', 'text', 'xml'}:

View File

@ -25,7 +25,8 @@ from calibre.gui2.tweak_book.editor.syntax.html import HTMLHighlighter, XMLHighl
from calibre.gui2.tweak_book.editor.syntax.css import CSSHighlighter from calibre.gui2.tweak_book.editor.syntax.css import CSSHighlighter
from calibre.gui2.tweak_book.editor.smart import NullSmarts from calibre.gui2.tweak_book.editor.smart import NullSmarts
from calibre.gui2.tweak_book.editor.smart.html import HTMLSmarts from calibre.gui2.tweak_book.editor.smart.html import HTMLSmarts
from calibre.utils.icu import safe_chr from calibre.spell.break_iterator import index_of
from calibre.utils.icu import safe_chr, string_length
PARAGRAPH_SEPARATOR = '\u2029' PARAGRAPH_SEPARATOR = '\u2029'
entity_pat = re.compile(r'&(#{0,1}[a-zA-Z0-9]{1,8});') entity_pat = re.compile(r'&(#{0,1}[a-zA-Z0-9]{1,8});')
@ -377,6 +378,29 @@ class TextEdit(PlainTextEdit):
self.saved_matches[save_match] = (pat, m) self.saved_matches[save_match] = (pat, m)
return True return True
def find_word_in_line(self, word, lang, lnum, from_cursor=True):
c = self.textCursor()
c.setPosition(c.position())
if not from_cursor or c.blockNumber() != lnum - 1:
lnum = max(1, min(self.blockCount(), lnum))
c.movePosition(c.Start)
c.movePosition(c.NextBlock, n=lnum - 1)
c.movePosition(c.StartOfLine)
c.movePosition(c.EndOfBlock, c.KeepAnchor)
offset = c.block().position()
else:
offset = c.block().position() + c.positionInBlock()
c.movePosition(c.EndOfBlock, c.KeepAnchor)
text = unicode(c.selectedText()).rstrip('\0')
idx = index_of(word, text, lang=lang)
if idx == -1:
return False
c.setPosition(offset + idx)
c.setPosition(c.position() + string_length(word), c.KeepAnchor)
self.setTextCursor(c)
self.centerCursor()
return True
def replace(self, pat, template, saved_match='gui'): def replace(self, pat, template, saved_match='gui'):
c = self.textCursor() c = self.textCursor()
raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0') raw = unicode(c.selectedText()).replace(PARAGRAPH_SEPARATOR, '\n').rstrip('\0')

View File

@ -189,6 +189,9 @@ class Editor(QMainWindow):
def find(self, *args, **kwargs): def find(self, *args, **kwargs):
return self.editor.find(*args, **kwargs) return self.editor.find(*args, **kwargs)
def find_word_in_line(self, *args, **kwargs):
return self.editor.find_word_in_line(*args, **kwargs)
def replace(self, *args, **kwargs): def replace(self, *args, **kwargs):
return self.editor.replace(*args, **kwargs) return self.editor.replace(*args, **kwargs)

View File

@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import cPickle, os, sys import cPickle, os, sys
from collections import defaultdict from collections import defaultdict, OrderedDict
from threading import Thread from threading import Thread
from PyQt4.Qt import ( from PyQt4.Qt import (
@ -22,7 +22,7 @@ from calibre.gui2 import choose_files, error_dialog
from calibre.gui2.complete2 import LineEdit from calibre.gui2.complete2 import LineEdit
from calibre.gui2.languages import LanguagesEdit from calibre.gui2.languages import LanguagesEdit
from calibre.gui2.progress_indicator import ProgressIndicator 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 import dictionaries, current_container, set_book_locale, tprefs, editors
from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.tweak_book.widgets import Dialog
from calibre.spell.dictionary import ( from calibre.spell.dictionary import (
builtin_dictionaries, custom_dictionaries, best_locale_for_language, builtin_dictionaries, custom_dictionaries, best_locale_for_language,
@ -670,6 +670,7 @@ class WordsModel(QAbstractTableModel):
class SpellCheck(Dialog): class SpellCheck(Dialog):
work_finished = pyqtSignal(object, object) work_finished = pyqtSignal(object, object)
find_word = pyqtSignal(object, object)
def __init__(self, parent=None): def __init__(self, parent=None):
self.__current_word = None self.__current_word = None
@ -681,6 +682,7 @@ class SpellCheck(Dialog):
self.setAttribute(Qt.WA_DeleteOnClose, False) self.setAttribute(Qt.WA_DeleteOnClose, False)
def setup_ui(self): def setup_ui(self):
set_no_activate_on_click = plugins['progress_indicator'][0].set_no_activate_on_click
self.setWindowIcon(QIcon(I('spell-check.png'))) self.setWindowIcon(QIcon(I('spell-check.png')))
self.l = l = QVBoxLayout(self) self.l = l = QVBoxLayout(self)
self.setLayout(l) self.setLayout(l)
@ -720,6 +722,8 @@ class SpellCheck(Dialog):
m.h2 = h = QHBoxLayout() m.h2 = h = QHBoxLayout()
l.addLayout(h) l.addLayout(h)
self.words_view = w = QTableView(m) self.words_view = w = QTableView(m)
set_no_activate_on_click(w)
w.activated.connect(self.word_activated)
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()
@ -760,6 +764,11 @@ class SpellCheck(Dialog):
self.initialize_user_dictionaries() self.initialize_user_dictionaries()
d.setMinimumContentsLength(25) d.setMinimumContentsLength(25)
l.addWidget(b), l.addWidget(d), l.addWidget(la) l.addWidget(b), l.addWidget(d), l.addWidget(la)
self.next_occurrence = b = QPushButton(_('Show &next occurrence'), self)
b.setToolTip('<p>' + _(
'Show the next occurrence of the selected word in the editor, so you can edit it manually'))
b.clicked.connect(self.show_next_occurrence)
l.addSpacing(20), l.addWidget(b)
l.addStretch(1) l.addStretch(1)
self.change_button = b = QPushButton(_('&Change selected word to:'), self) self.change_button = b = QPushButton(_('&Change selected word to:'), self)
@ -772,8 +781,7 @@ class SpellCheck(Dialog):
self.suggested_list = sl = QListWidget(self) self.suggested_list = sl = QListWidget(self)
sl.currentItemChanged.connect(self.current_suggestion_changed) sl.currentItemChanged.connect(self.current_suggestion_changed)
sl.itemActivated.connect(self.change_word) sl.itemActivated.connect(self.change_word)
pi = plugins['progress_indicator'][0] set_no_activate_on_click(sl)
pi.set_no_activate_on_click(sl)
l.addWidget(sl) l.addWidget(sl)
hh.setSectionHidden(3, m.show_only_misspelt) hh.setSectionHidden(3, m.show_only_misspelt)
@ -784,6 +792,15 @@ class SpellCheck(Dialog):
self.summary = s = QLabel('') self.summary = s = QLabel('')
self.main.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 show_next_occurrence(self):
self.word_activated(self.words_view.currentIndex())
def word_activated(self, index):
w = self.words_model.word_for_row(index.row())
if w is None:
return
self.find_word.emit(w, self.words_model.words[w])
def initialize_user_dictionaries(self): def initialize_user_dictionaries(self):
ct = unicode(self.user_dictionaries.currentText()) ct = unicode(self.user_dictionaries.currentText())
self.user_dictionaries.clear() self.user_dictionaries.clear()
@ -961,6 +978,42 @@ class SpellCheck(Dialog):
d.exec_() d.exec_()
# }}} # }}}
def find_next(word, locations, current_editor, current_editor_name,
gui_parent, show_editor, edit_file):
files = OrderedDict()
for l in locations:
try:
files[l.file_name].append(l)
except KeyError:
files[l.file_name] = [l]
start_locations = set()
if current_editor_name not in files:
current_editor = current_editor_name = None
else:
# Re-order the list of locations to search so that we search int he
# current editor first
lfiles = list(files)
idx = lfiles.index(current_editor_name)
before, after = lfiles[:idx], lfiles[idx+1:]
lfiles = after + before + [current_editor_name]
lnum = current_editor.current_line
start_locations = [l for l in files[current_editor_name] if l.sourceline >= lnum]
locations = list(start_locations)
for fname in lfiles:
locations.extend(files[fname])
start_locations = set(start_locations)
for location in locations:
ed = editors.get(location.file_name, None)
if ed is None:
edit_file(location.file_name)
ed = editors[location.file_name]
if ed.find_word_in_line(location.original_word, word[1].langcode, location.sourceline, from_cursor=location in start_locations):
show_editor(location.file_name)
return True
return False
if __name__ == '__main__': if __name__ == '__main__':
app = QApplication([]) app = QApplication([])
dictionaries.initialize() dictionaries.initialize()