Added replace quotes function, and cleaned up some buggy behaviour with undo and how changes are tracked

This commit is contained in:
Veronica K. B. Olsen
2020-06-19 21:56:37 +02:00
parent b0380adc5f
commit 03f1142e40
3 changed files with 111 additions and 22 deletions
+2
View File
@@ -93,6 +93,8 @@ class nwDocAction(Enum):
BLOCK_H4 = 22 BLOCK_H4 = 22
BLOCK_COM = 23 BLOCK_COM = 23
BLOCK_TXT = 24 BLOCK_TXT = 24
REPL_SNG = 25
REPL_DBL = 26
# END Enum nwDocAction # END Enum nwDocAction
+93 -21
View File
@@ -45,11 +45,12 @@ from PyQt5.QtWidgets import (
QFrame QFrame
) )
from nw.core import NWDoc from nw.core import NWDoc, NWSpellSimple, countWords
from nw.gui.dochighlight import GuiDocHighlighter from nw.gui.dochighlight import GuiDocHighlighter
from nw.core import NWSpellSimple, countWords
from nw.constants import nwUnicode, nwDocAction, nwDocInsert, nwInsertSymbols
from nw.common import transferCase from nw.common import transferCase
from nw.constants import (
nwAlert, nwUnicode, nwDocAction, nwDocInsert, nwInsertSymbols
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -76,6 +77,7 @@ class GuiDocEditor(QTextEdit):
self.paraCount = 0 self.paraCount = 0
self.lastEdit = 0 self.lastEdit = 0
self.bigDoc = False self.bigDoc = False
self.doReplace = False
self.nonWord = "\"'" self.nonWord = "\"'"
# Document State # Document State
@@ -157,6 +159,7 @@ class GuiDocEditor(QTextEdit):
self.paraCount = 0 self.paraCount = 0
self.lastEdit = 0 self.lastEdit = 0
self.bigDoc = False self.bigDoc = False
self.doReplace = False
self.hasSelection = False self.hasSelection = False
@@ -261,7 +264,9 @@ class GuiDocEditor(QTextEdit):
self.hLight.spellCheck = False self.hLight.spellCheck = False
bfTime = time() bfTime = time()
self._allowAutoReplace(False)
self.setPlainText(theDoc) self.setPlainText(theDoc)
self._allowAutoReplace(True)
afTime = time() afTime = time()
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime))) logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
@@ -388,8 +393,8 @@ class GuiDocEditor(QTextEdit):
""" """
if self.mainConf.verQtValue >= 50900: if self.mainConf.verQtValue >= 50900:
theText = self.qDocument.toRawText() theText = self.qDocument.toRawText()
theText = theText.replace("\u2028", "\n") # Line separators theText = theText.replace(nwUnicode.U_LSEP, "\n") # Line separators
theText = theText.replace("\u2029", "\n") # Paragraph separators theText = theText.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
else: else:
theText = self.toPlainText() theText = self.toPlainText()
return theText return theText
@@ -496,6 +501,7 @@ class GuiDocEditor(QTextEdit):
if not self.theParent.hasProject: if not self.theParent.hasProject:
logger.error("No project open") logger.error("No project open")
return False return False
self._allowAutoReplace(False)
if theAction == nwDocAction.UNDO: if theAction == nwDocAction.UNDO:
self.undo() self.undo()
elif theAction == nwDocAction.REDO: elif theAction == nwDocAction.REDO:
@@ -544,9 +550,15 @@ class GuiDocEditor(QTextEdit):
self._formatBlock(nwDocAction.BLOCK_COM) self._formatBlock(nwDocAction.BLOCK_COM)
elif theAction == nwDocAction.BLOCK_TXT: elif theAction == nwDocAction.BLOCK_TXT:
self._formatBlock(nwDocAction.BLOCK_TXT) self._formatBlock(nwDocAction.BLOCK_TXT)
elif theAction == nwDocAction.REPL_SNG:
self._replaceQuotes("'", self.typSQOpen, self.typSQClose)
elif theAction == nwDocAction.REPL_DBL:
self._replaceQuotes("\"", self.typDQOpen, self.typDQClose)
else: else:
logger.debug("Unknown or unsupported document action %s" % str(theAction)) logger.debug("Unknown or unsupported document action %s" % str(theAction))
self._allowAutoReplace(True)
return False return False
self._allowAutoReplace(True)
return True return True
def isEmpty(self): def isEmpty(self):
@@ -597,24 +609,23 @@ class GuiDocEditor(QTextEdit):
def keyPressEvent(self, keyEvent): def keyPressEvent(self, keyEvent):
"""Intercept key press events. """Intercept key press events.
We need to intercept key presses briefly to record the state of We need to intercept a few key sequences:
selection. This is in order to know whether we had a selection * The return key redirects here even if the search box has
prior to triggering the _docChange slot, as we do not want to focus. Since we need the return key to continue search, we
trigger autoreplace on selections. Autoreplace on selections block any further interaction here while it's in focus.
messes with undo/redo history. * The undo sequence bypasses the doAction pathway from the
We also need to intercept the Shift key modifier for certain key menu, so we redirect it back from here.
combinations that modifies standard keys like enter and space. * The default redo sequence is Ctrl+Shift+Z, which we don't
However, we don't want to spend a lot of time in this function use, so we block it.
as it is triggered on every keypress when typing.
""" """
self.hasSelection = self.textCursor().hasSelection()
if self.docSearch.searchBox.hasFocus(): if self.docSearch.searchBox.hasFocus():
# Block the event when the focus is on the search bar as it
# seems to be capturing the return key.
return return
elif keyEvent == QKeySequence.Redo:
QTextEdit.keyPressEvent(self, keyEvent) return
elif keyEvent == QKeySequence.Undo:
self.docAction(nwDocAction.UNDO)
else:
QTextEdit.keyPressEvent(self, keyEvent)
return return
def mouseReleaseEvent(self, mEvent): def mouseReleaseEvent(self, mEvent):
@@ -650,7 +661,7 @@ class GuiDocEditor(QTextEdit):
self.setDocumentChanged(True) self.setDocumentChanged(True)
if not self.wcTimer.isActive(): if not self.wcTimer.isActive():
self.wcTimer.start() self.wcTimer.start()
if self.mainConf.doReplace and not self.hasSelection: if self.doReplace and charsAdded == 1:
self._docAutoReplace(self.qDocument.findBlock(thePos)) self._docAutoReplace(self.qDocument.findBlock(thePos))
return return
@@ -862,6 +873,58 @@ class GuiDocEditor(QTextEdit):
return return
def _replaceQuotes(self, sQuote, oQuote, cQuote):
"""Replace all straight quotes in the selected text.
"""
theCursor = self.textCursor()
if theCursor.hasSelection():
posS = theCursor.selectionStart()
posE = theCursor.selectionEnd()
closeCheck = (
" ", "\n", nwUnicode.U_LSEP, nwUnicode.U_PSEP
)
self._allowAutoReplace(False)
for posC in range(posS, posE+1):
theCursor.setPosition(posC)
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
selText = theCursor.selectedText()
nS = len(selText)
if nS == 2:
pC = selText[0]
cC = selText[1]
elif nS == 1:
pC = " "
cC = selText[0]
else:
continue
if cC != sQuote:
continue
theCursor.clearSelection()
theCursor.setPosition(posC)
if pC in closeCheck:
theCursor.beginEditBlock()
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
theCursor.insertText(oQuote)
theCursor.endEditBlock()
else:
theCursor.beginEditBlock()
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
theCursor.insertText(cQuote)
theCursor.endEditBlock()
self._allowAutoReplace(True)
else:
self.theParent.makeAlert(
"Please selection some text before calling replace quotes.", nwAlert.ERROR
)
return
def _checkDocSize(self, theSize): def _checkDocSize(self, theSize):
"""Check if document size crosses the big document limit set in """Check if document size crosses the big document limit set in
config. If so, we will set the big document flag to True. config. If so, we will set the big document flag to True.
@@ -1225,6 +1288,15 @@ class GuiDocEditor(QTextEdit):
return return
def _allowAutoReplace(self, theState):
"""used to enable/disable the auto-replace feature temporarily.
"""
if theState:
self.doReplace = self.mainConf.doReplace
else:
self.doReplace = False
return
# END Class GuiDocEditor # END Class GuiDocEditor
# =============================================================================================== # # =============================================================================================== #
+16 -1
View File
@@ -577,7 +577,7 @@ class GuiMainMenu(QMenuBar):
self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE)) self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
self.fmtMenu.addAction(self.aFmtSQuote) self.fmtMenu.addAction(self.aFmtSQuote)
# Edit > Separator # Format > Separator
self.fmtMenu.addSeparator() self.fmtMenu.addSeparator()
# Format > Header 1 # Format > Header 1
@@ -622,6 +622,21 @@ class GuiMainMenu(QMenuBar):
self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT)) self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT))
self.fmtMenu.addAction(self.aFmtNoFormat) self.fmtMenu.addAction(self.aFmtNoFormat)
# Format > Separator
self.fmtMenu.addSeparator()
# Format > Replace Single Quotes
self.aFmtReplSng = QAction("Replace Single Quotes", self)
self.aFmtReplSng.setStatusTip("Replace all straight single quotes in selected text")
self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG))
self.fmtMenu.addAction(self.aFmtReplSng)
# Format > Replace Double Quotes
self.aFmtReplDbl = QAction("Replace Double Quotes", self)
self.aFmtReplDbl.setStatusTip("Replace all straight double quotes in selected text")
self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
self.fmtMenu.addAction(self.aFmtReplDbl)
return return
def _buildViewMenu(self): def _buildViewMenu(self):