Make the editor search/replace bypass the docAction pipeline and be called directly

This commit is contained in:
Veronica K. B. Olsen
2021-03-17 16:43:45 +01:00
parent 3b92c3d75c
commit f6c92dab24
4 changed files with 172 additions and 166 deletions
+8 -13
View File
@@ -81,19 +81,14 @@ class nwDocAction(Enum):
D_QUOTE = 10 D_QUOTE = 10
SEL_ALL = 11 SEL_ALL = 11
SEL_PARA = 12 SEL_PARA = 12
FIND = 13 BLOCK_H1 = 13
REPLACE = 14 BLOCK_H2 = 14
GO_NEXT = 15 BLOCK_H3 = 15
GO_PREV = 16 BLOCK_H4 = 16
REPL_NEXT = 17 BLOCK_COM = 17
BLOCK_H1 = 18 BLOCK_TXT = 18
BLOCK_H2 = 19 REPL_SNG = 19
BLOCK_H3 = 20 REPL_DBL = 20
BLOCK_H4 = 21
BLOCK_COM = 22
BLOCK_TXT = 23
REPL_SNG = 24
REPL_DBL = 25
# END Enum nwDocAction # END Enum nwDocAction
+151 -144
View File
@@ -660,10 +660,6 @@ class GuiDocEditor(QTextEdit):
this class when calling these actions from other classes. this class when calling these actions from other classes.
""" """
logger.verbose("Requesting action: %s" % theAction.name) logger.verbose("Requesting action: %s" % theAction.name)
if not self.hasFocus():
logger.verbose("Editor does not have focus")
return False
if self.theHandle is None: if self.theHandle is None:
logger.error("No document open") logger.error("No document open")
return False return False
@@ -693,16 +689,6 @@ class GuiDocEditor(QTextEdit):
self._makeSelection(QTextCursor.Document) self._makeSelection(QTextCursor.Document)
elif theAction == nwDocAction.SEL_PARA: elif theAction == nwDocAction.SEL_PARA:
self._makeSelection(QTextCursor.BlockUnderCursor) self._makeSelection(QTextCursor.BlockUnderCursor)
elif theAction == nwDocAction.FIND:
self._beginSearch()
elif theAction == nwDocAction.REPLACE:
self._beginReplace()
elif theAction == nwDocAction.GO_NEXT:
self._findNext()
elif theAction == nwDocAction.GO_PREV:
self._findNext(isBackward=True)
elif theAction == nwDocAction.REPL_NEXT:
self._replaceNext()
elif theAction == nwDocAction.BLOCK_H1: elif theAction == nwDocAction.BLOCK_H1:
self._formatBlock(nwDocAction.BLOCK_H1) self._formatBlock(nwDocAction.BLOCK_H1)
elif theAction == nwDocAction.BLOCK_H2: elif theAction == nwDocAction.BLOCK_H2:
@@ -734,6 +720,15 @@ class GuiDocEditor(QTextEdit):
""" """
return self.qDocument.isEmpty() return self.qDocument.isEmpty()
def anyFocus(self):
"""Check if any widget or child widget has focus.
"""
if self.hasFocus():
return True
if self.isAncestorOf(qApp.focusWidget()):
return True
return False
def revealLocation(self): def revealLocation(self):
"""Tell the user where on the file system the file in the editor """Tell the user where on the file system the file in the editor
is saved. is saved.
@@ -827,7 +822,7 @@ class GuiDocEditor(QTextEdit):
if self.docSearch.isVisible(): if self.docSearch.isVisible():
self.docSearch.closeSearch() self.docSearch.closeSearch()
else: else:
self._beginSearch() self.beginSearch()
return return
## ##
@@ -1142,6 +1137,144 @@ class GuiDocEditor(QTextEdit):
) )
return return
##
# Search & Replace
##
def beginSearch(self):
"""Sets the selected text as the search text for the search bar.
"""
theCursor = self.textCursor()
if theCursor.hasSelection():
self.docSearch.setSearchText(theCursor.selectedText())
else:
self.docSearch.setSearchText(None)
self.updateDocMargins()
return
def beginReplace(self):
"""Opens the replace line of the search bar and sets the find
text if a selection has been made, and resets the replace text.
"""
theCursor = self.textCursor()
if theCursor.hasSelection():
self.docSearch.setSearchText(theCursor.selectedText())
else:
self.docSearch.setSearchText(None)
self.docSearch.setReplaceText("")
self.updateDocMargins()
return
def findNext(self, goBack=False):
"""Searches for the next or previous occurrence of the search
bar text in the document. Wraps around if not found and loop is
enabled, or continues to next file if next file is enabled.
"""
if not self.anyFocus():
logger.debug("Editor does not have focus")
return False
if not self.docSearch.isVisible():
self.beginSearch()
return
findOpt = QTextDocument.FindFlag(0)
if goBack:
findOpt |= QTextDocument.FindBackward
if self.docSearch.isCaseSense:
findOpt |= QTextDocument.FindCaseSensitively
if self.docSearch.isWholeWord:
findOpt |= QTextDocument.FindWholeWords
searchFor = self.docSearch.getSearchObject()
wasFound = self.find(searchFor, findOpt)
if not wasFound:
if self.docSearch.doNextFile and not goBack:
self.theParent.openNextDocument(
self.theHandle, wrapAround=self.docSearch.doLoop
)
elif self.docSearch.doLoop:
theCursor = self.textCursor()
theCursor.movePosition(
QTextCursor.End if goBack else QTextCursor.Start
)
self.setTextCursor(theCursor)
wasFound = self.find(searchFor, findOpt)
if wasFound:
theCursor = self.textCursor()
self.lastFind = (theCursor.selectionStart(), theCursor.selectionEnd())
return
def replaceNext(self):
"""Searches for the next occurrence of the search bar text in
the document and replaces it with the replace text. Calls search
next automatically when done.
"""
if not self.anyFocus():
logger.debug("Editor does not have focus")
return False
if not self.docSearch.isVisible():
# The search tool is not active, so we activate it.
self.beginSearch()
return
theCursor = self.textCursor()
if not theCursor.hasSelection():
# We have no text selected at all, so just make this a
# regular find next call.
self.findNext()
return
if self.lastFind is None and theCursor.hasSelection():
# If we have a selection but no search, it may have been the
# text we triggered the search with, in which case we search
# again from the beginning of that selection to make sure we
# have a valid result.
sPos = theCursor.selectionStart()
theCursor.clearSelection()
theCursor.setPosition(sPos)
self.setTextCursor(theCursor)
self.findNext()
theCursor = self.textCursor()
if self.lastFind is None:
# In case the above didn't find a result, we give up here.
return
searchFor = self.docSearch.getSearchText()
replWith = self.docSearch.getReplaceText()
if self.docSearch.doMatchCap:
replWith = transferCase(theCursor.selectedText(), replWith)
# Make sure the selected text was selected by an actual find
# call, and not the user.
try:
isFind = self.lastFind[0] == theCursor.selectionStart()
isFind &= self.lastFind[1] == theCursor.selectionEnd()
except Exception:
isFind = False
if isFind:
theCursor.beginEditBlock()
theCursor.removeSelectedText()
theCursor.insertText(replWith)
theCursor.endEditBlock()
theCursor.setPosition(theCursor.selectionEnd())
self.setTextCursor(theCursor)
logger.verbose("Replaced occurrence of '%s' with '%s' on line %d" % (
searchFor, replWith, theCursor.blockNumber()
))
else:
logger.error("The selected text is not a search result, skipping replace")
self.findNext()
return
## ##
# Internal Functions # Internal Functions
## ##
@@ -1585,132 +1718,6 @@ class GuiDocEditor(QTextEdit):
self._makeSelection(selMode) self._makeSelection(selMode)
return return
def _beginSearch(self):
"""Sets the selected text as the search text for the search bar.
"""
theCursor = self.textCursor()
if theCursor.hasSelection():
self.docSearch.setSearchText(theCursor.selectedText())
else:
self.docSearch.setSearchText(None)
self.updateDocMargins()
return
def _beginReplace(self):
"""Opens the replace line of the search bar and sets the find
text if a selection has been made, and resets the replace text.
"""
theCursor = self.textCursor()
if theCursor.hasSelection():
self.docSearch.setSearchText(theCursor.selectedText())
else:
self.docSearch.setSearchText(None)
self.docSearch.setReplaceText("")
self.updateDocMargins()
return
def _findNext(self, isBackward=False):
"""Searches for the next or previous occurrence of the search
bar text in the document. Wraps around if not found and loop is
enabled, or continues to next file if next file is enabled.
"""
if not self.docSearch.isVisible():
self._beginSearch()
return
findOpt = QTextDocument.FindFlag(0)
if isBackward:
findOpt |= QTextDocument.FindBackward
if self.docSearch.isCaseSense:
findOpt |= QTextDocument.FindCaseSensitively
if self.docSearch.isWholeWord:
findOpt |= QTextDocument.FindWholeWords
searchFor = self.docSearch.getSearchObject()
wasFound = self.find(searchFor, findOpt)
if not wasFound:
if self.docSearch.doNextFile and not isBackward:
self.theParent.openNextDocument(
self.theHandle, wrapAround=self.docSearch.doLoop
)
elif self.docSearch.doLoop:
theCursor = self.textCursor()
theCursor.movePosition(
QTextCursor.End if isBackward else QTextCursor.Start
)
self.setTextCursor(theCursor)
wasFound = self.find(searchFor, findOpt)
if wasFound:
theCursor = self.textCursor()
self.lastFind = (theCursor.selectionStart(), theCursor.selectionEnd())
return
def _replaceNext(self):
"""Searches for the next occurrence of the search bar text in
the document and replaces it with the replace text. Calls search
next automatically when done.
"""
if not self.docSearch.isVisible():
# The search tool is not active, so we activate it.
self._beginSearch()
return
theCursor = self.textCursor()
if not theCursor.hasSelection():
# We have no text selected at all, so just make this a
# regular find next call.
self._findNext()
return
if self.lastFind is None and theCursor.hasSelection():
# If we have a selection but no search, it may have been the
# text we triggered the search with, in which case we search
# again from the beginning of that selection to make sure we
# have a valid result.
sPos = theCursor.selectionStart()
theCursor.clearSelection()
theCursor.setPosition(sPos)
self.setTextCursor(theCursor)
self._findNext()
theCursor = self.textCursor()
if self.lastFind is None:
# In case the above didn't find a result, we give up here.
return
searchFor = self.docSearch.getSearchText()
replWith = self.docSearch.getReplaceText()
if self.docSearch.doMatchCap:
replWith = transferCase(theCursor.selectedText(), replWith)
# Make sure the selected text was selected by an actual find
# call, and not the user.
try:
isFind = self.lastFind[0] == theCursor.selectionStart()
isFind &= self.lastFind[1] == theCursor.selectionEnd()
except Exception:
isFind = False
if isFind:
theCursor.beginEditBlock()
theCursor.removeSelectedText()
theCursor.insertText(replWith)
theCursor.endEditBlock()
theCursor.setPosition(theCursor.selectionEnd())
self.setTextCursor(theCursor)
logger.verbose("Replaced occurrence of '%s' with '%s' on line %d" % (
searchFor, replWith, theCursor.blockNumber()
))
else:
logger.error("The selected text is not a search result, skipping replace")
self._findNext()
return
def _setupSpellChecking(self): def _setupSpellChecking(self):
"""Create the spell checking object based on the spellTool """Create the spell checking object based on the spellTool
setting in config. setting in config.
@@ -2074,15 +2081,15 @@ class GuiDocEditSearch(QFrame):
""" """
modKey = qApp.keyboardModifiers() modKey = qApp.keyboardModifiers()
if modKey == Qt.ShiftModifier: if modKey == Qt.ShiftModifier:
self.docEditor.docAction(nwDocAction.GO_PREV) self.docEditor.findNext(goBack=True)
else: else:
self.docEditor.docAction(nwDocAction.GO_NEXT) self.docEditor.findNext()
return return
def _doReplace(self): def _doReplace(self):
"""Call the replace action function for the document editor. """Call the replace action function for the document editor.
""" """
self.docEditor.docAction(nwDocAction.REPL_NEXT) self.docEditor.replaceNext()
return return
def _doToggleReplace(self, theState): def _doToggleReplace(self, theState):
+5 -5
View File
@@ -756,7 +756,7 @@ class GuiMainMenu(QMenuBar):
self.aFind = QAction("Find", self) self.aFind = QAction("Find", self)
self.aFind.setStatusTip("Find text in document") self.aFind.setStatusTip("Find text in document")
self.aFind.setShortcut("Ctrl+F") self.aFind.setShortcut("Ctrl+F")
self.aFind.triggered.connect(lambda: self._docAction(nwDocAction.FIND)) self.aFind.triggered.connect(lambda: self.theParent.docEditor.beginSearch())
self.srcMenu.addAction(self.aFind) self.srcMenu.addAction(self.aFind)
# Search > Replace # Search > Replace
@@ -766,7 +766,7 @@ class GuiMainMenu(QMenuBar):
self.aReplace.setShortcut("Ctrl+=") self.aReplace.setShortcut("Ctrl+=")
else: else:
self.aReplace.setShortcut("Ctrl+H") self.aReplace.setShortcut("Ctrl+H")
self.aReplace.triggered.connect(lambda: self._docAction(nwDocAction.REPLACE)) self.aReplace.triggered.connect(lambda: self.theParent.docEditor.beginReplace())
self.srcMenu.addAction(self.aReplace) self.srcMenu.addAction(self.aReplace)
# Search > Find Next # Search > Find Next
@@ -776,7 +776,7 @@ class GuiMainMenu(QMenuBar):
self.aFindNext.setShortcuts(["Ctrl+G", "F3"]) self.aFindNext.setShortcuts(["Ctrl+G", "F3"])
else: else:
self.aFindNext.setShortcuts(["F3", "Ctrl+G"]) self.aFindNext.setShortcuts(["F3", "Ctrl+G"])
self.aFindNext.triggered.connect(lambda: self._docAction(nwDocAction.GO_NEXT)) self.aFindNext.triggered.connect(lambda: self.theParent.docEditor.findNext())
self.srcMenu.addAction(self.aFindNext) self.srcMenu.addAction(self.aFindNext)
# Search > Find Prev # Search > Find Prev
@@ -786,14 +786,14 @@ class GuiMainMenu(QMenuBar):
self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"]) self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"])
else: else:
self.aFindPrev.setShortcuts(["Shift+F3", "Ctrl+Shift+G"]) self.aFindPrev.setShortcuts(["Shift+F3", "Ctrl+Shift+G"])
self.aFindPrev.triggered.connect(lambda: self._docAction(nwDocAction.GO_PREV)) self.aFindPrev.triggered.connect(lambda: self.theParent.docEditor.findNext(goBack=True))
self.srcMenu.addAction(self.aFindPrev) self.srcMenu.addAction(self.aFindPrev)
# Search > Replace Next # Search > Replace Next
self.aReplaceNext = QAction("Replace Next", self) self.aReplaceNext = QAction("Replace Next", self)
self.aReplaceNext.setStatusTip("Find and replace next occurrence text in document") self.aReplaceNext.setStatusTip("Find and replace next occurrence text in document")
self.aReplaceNext.setShortcut("Ctrl+Shift+1") self.aReplaceNext.setShortcut("Ctrl+Shift+1")
self.aReplaceNext.triggered.connect(lambda: self._docAction(nwDocAction.REPL_NEXT)) self.aReplaceNext.triggered.connect(lambda: self.theParent.docEditor.replaceNext())
self.srcMenu.addAction(self.aReplaceNext) self.srcMenu.addAction(self.aReplaceNext)
return return
+8 -4
View File
@@ -757,14 +757,18 @@ class GuiMain(QMainWindow):
return True return True
def passDocumentAction(self, theAction): def passDocumentAction(self, theAction):
"""Pass on document action theAction to the document viewer if """Pass on document action to the document viewer if it has
it has focus, otherwise pass it to the document editor. focus, or pass it to the document editor if it or any of
its clid widgets have focus. If neither has focus, ignore the
action.
""" """
if self.docViewer.hasFocus(): if self.docViewer.hasFocus():
self.docViewer.docAction(theAction) self.docViewer.docAction(theAction)
else: elif self.docEditor.hasFocus():
self.docEditor.docAction(theAction) self.docEditor.docAction(theAction)
return True else:
logger.debug("Action cancelled as neither editor nor viewer has focus")
return
## ##
# Tree Item Actions # Tree Item Actions