Added replace quotes function, and cleaned up some buggy behaviour with undo and how changes are tracked
This commit is contained in:
@@ -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
@@ -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
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user