Test Coverage of the Editor (#787)
* Drop the _qDocument class variables * Split up the current doceditor test file * Add tests for editor init, load and save * Add tests for editor meta functions and actions * Add tests for editor insert functions * Add tests for editor text manipulation and block formatting * Fix code style * Add text coverage for alignment and indent
This commit is contained in:
committed by
GitHub
parent
1d16cc2a03
commit
36be7b0b33
+349
-331
@@ -111,9 +111,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self._typPadChar = " "
|
self._typPadChar = " "
|
||||||
|
|
||||||
# Core Elements and Signals
|
# Core Elements and Signals
|
||||||
self._qDocument = self.document()
|
qDoc = self.document()
|
||||||
self._qDocument.contentsChange.connect(self._docChange)
|
qDoc.contentsChange.connect(self._docChange)
|
||||||
self._qDocument.documentLayout().documentSizeChanged.connect(self._docSizeChanged)
|
qDoc.documentLayout().documentSizeChanged.connect(self._docSizeChanged)
|
||||||
|
|
||||||
# Document Title
|
# Document Title
|
||||||
self.docHeader = GuiDocEditHeader(self)
|
self.docHeader = GuiDocEditHeader(self)
|
||||||
@@ -121,7 +121,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.docSearch = GuiDocEditSearch(self)
|
self.docSearch = GuiDocEditSearch(self)
|
||||||
|
|
||||||
# Syntax
|
# Syntax
|
||||||
self.hLight = GuiDocHighlighter(self._qDocument, self.theParent)
|
self.hLight = GuiDocHighlighter(qDoc, self.theParent)
|
||||||
|
|
||||||
# Context Menu
|
# Context Menu
|
||||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
@@ -221,9 +221,10 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
# Set font
|
# Set font
|
||||||
theFont = QFont()
|
theFont = QFont()
|
||||||
|
qDoc = self.document()
|
||||||
if self.mainConf.textFont is None:
|
if self.mainConf.textFont is None:
|
||||||
# If none is defined, set the default back to config
|
# If none is defined, set the default back to config
|
||||||
self.mainConf.textFont = self._qDocument.defaultFont().family()
|
self.mainConf.textFont = qDoc.defaultFont().family()
|
||||||
|
|
||||||
theFont.setFamily(self.mainConf.textFont)
|
theFont.setFamily(self.mainConf.textFont)
|
||||||
theFont.setPointSize(self.mainConf.textSize)
|
theFont.setPointSize(self.mainConf.textSize)
|
||||||
@@ -246,7 +247,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
# Set default text margins
|
# Set default text margins
|
||||||
cM = self.mainConf.getTextMargin()
|
cM = self.mainConf.getTextMargin()
|
||||||
self._qDocument.setDocumentMargin(0)
|
qDoc.setDocumentMargin(0)
|
||||||
self.setViewportMargins(cM, cM, cM, cM)
|
self.setViewportMargins(cM, cM, cM, cM)
|
||||||
|
|
||||||
# Also set the document text options for the document text flow
|
# Also set the document text options for the document text flow
|
||||||
@@ -259,7 +260,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
if self.mainConf.showLineEndings:
|
if self.mainConf.showLineEndings:
|
||||||
theOpt.setFlags(theOpt.flags() | QTextOption.ShowLineAndParagraphSeparators)
|
theOpt.setFlags(theOpt.flags() | QTextOption.ShowLineAndParagraphSeparators)
|
||||||
|
|
||||||
self._qDocument.setDefaultTextOption(theOpt)
|
qDoc.setDefaultTextOption(theOpt)
|
||||||
|
|
||||||
# Scroll bars
|
# Scroll bars
|
||||||
if self.mainConf.hideVScroll:
|
if self.mainConf.hideVScroll:
|
||||||
@@ -275,7 +276,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
# Refresh the tab stops
|
# Refresh the tab stops
|
||||||
if self.mainConf.verQtValue >= 51000:
|
if self.mainConf.verQtValue >= 51000:
|
||||||
self.setTabStopDistance(self.mainConf.getTabWidth())
|
self.setTabStopDistance(self.mainConf.getTabWidth())
|
||||||
else:
|
else: # pragma: no cover
|
||||||
self.setTabStopWidth(self.mainConf.getTabWidth())
|
self.setTabStopWidth(self.mainConf.getTabWidth())
|
||||||
|
|
||||||
# Initialise the syntax highlighter
|
# Initialise the syntax highlighter
|
||||||
@@ -370,7 +371,6 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.setCursorLine(tLine)
|
self.setCursorLine(tLine)
|
||||||
|
|
||||||
self.docFooter.updateLineCount()
|
self.docFooter.updateLineCount()
|
||||||
self.lengthLast = self._qDocument.characterCount()
|
|
||||||
self._docHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
self._docHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
||||||
|
|
||||||
qApp.processEvents()
|
qApp.processEvents()
|
||||||
@@ -378,7 +378,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
|
|
||||||
# This is a hack to fix invisble cursor on an empty document
|
# This is a hack to fix invisble cursor on an empty document
|
||||||
if self._qDocument.characterCount() <= 1:
|
if self.document().characterCount() <= 1:
|
||||||
self.setPlainText("\n")
|
self.setPlainText("\n")
|
||||||
self.setPlainText("")
|
self.setPlainText("")
|
||||||
self.setCursorPosition(0)
|
self.setCursorPosition(0)
|
||||||
@@ -391,7 +391,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def updateTagHighLighting(self, forceBigDoc=False):
|
def updateTagHighLighting(self):
|
||||||
"""Rerun the syntax highlighter on all meta data lines.
|
"""Rerun the syntax highlighter on all meta data lines.
|
||||||
"""
|
"""
|
||||||
self.hLight.rehighlightByType(GuiDocHighlighter.BLOCK_META)
|
self.hLight.rehighlightByType(GuiDocHighlighter.BLOCK_META)
|
||||||
@@ -400,7 +400,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
def redrawText(self):
|
def redrawText(self):
|
||||||
"""Redraw the text by marking the document content as "dirty".
|
"""Redraw the text by marking the document content as "dirty".
|
||||||
"""
|
"""
|
||||||
self._qDocument.markContentsDirty(0, self._qDocument.characterCount())
|
self.document().markContentsDirty(0, self.document().characterCount())
|
||||||
self.updateDocMargins()
|
self.updateDocMargins()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -532,9 +532,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
tmpDocChanged = self._docChanged
|
tmpDocChanged = self._docChanged
|
||||||
if self.mainConf.scrollPastEnd:
|
if self.mainConf.scrollPastEnd:
|
||||||
docFrame = self._qDocument.rootFrame().frameFormat()
|
docFrame = self.document().rootFrame().frameFormat()
|
||||||
docFrame.setBottomMargin(max(0, 0.9*(wH - uM - lM - 4*tB)))
|
docFrame.setBottomMargin(max(0, 0.9*(wH - uM - lM - 4*tB)))
|
||||||
self._qDocument.rootFrame().setFrameFormat(docFrame)
|
self.document().rootFrame().setFrameFormat(docFrame)
|
||||||
|
|
||||||
# This is needed as the setFrameFormat function itself will
|
# This is needed as the setFrameFormat function itself will
|
||||||
# trigger the contetsChanged signal which sets _docChanged, so we
|
# trigger the contetsChanged signal which sets _docChanged, so we
|
||||||
@@ -576,7 +576,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
def isEmpty(self):
|
def isEmpty(self):
|
||||||
"""Wrapper function to check if the current document is empty.
|
"""Wrapper function to check if the current document is empty.
|
||||||
"""
|
"""
|
||||||
return self._qDocument.isEmpty()
|
return self.document().isEmpty()
|
||||||
|
|
||||||
def currentDictionary(self):
|
def currentDictionary(self):
|
||||||
"""Return the current dictionary object.
|
"""Return the current dictionary object.
|
||||||
@@ -596,7 +596,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
See: https://doc.qt.io/qt-5/qtextdocument.html#toPlainText
|
See: https://doc.qt.io/qt-5/qtextdocument.html#toPlainText
|
||||||
"""
|
"""
|
||||||
if self.mainConf.verQtValue >= 50900:
|
if self.mainConf.verQtValue >= 50900:
|
||||||
theText = self._qDocument.toRawText()
|
theText = self.document().toRawText()
|
||||||
theText = theText.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
theText = theText.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
||||||
theText = theText.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
theText = theText.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
||||||
else:
|
else:
|
||||||
@@ -628,7 +628,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
if not isinstance(thePosition, int):
|
if not isinstance(thePosition, int):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
nChars = self._qDocument.characterCount()
|
nChars = self.document().characterCount()
|
||||||
if nChars > 1:
|
if nChars > 1:
|
||||||
theCursor = self.textCursor()
|
theCursor = self.textCursor()
|
||||||
theCursor.setPosition(min(max(thePosition, 0), nChars-1))
|
theCursor.setPosition(min(max(thePosition, 0), nChars-1))
|
||||||
@@ -652,7 +652,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if theLine >= 0:
|
if theLine >= 0:
|
||||||
theBlock = self._qDocument.findBlockByLineNumber(theLine)
|
theBlock = self.document().findBlockByLineNumber(theLine)
|
||||||
if theBlock:
|
if theBlock:
|
||||||
self.setCursorPosition(theBlock.position())
|
self.setCursorPosition(theBlock.position())
|
||||||
self.docFooter.updateLineCount()
|
self.docFooter.updateLineCount()
|
||||||
@@ -710,7 +710,8 @@ class GuiDocEditor(QTextEdit):
|
|||||||
def spellCheckDocument(self):
|
def spellCheckDocument(self):
|
||||||
"""Rerun the highlighter to update spell checking status of the
|
"""Rerun the highlighter to update spell checking status of the
|
||||||
currently loaded text. The fastest way to do this, at least as
|
currently loaded text. The fastest way to do this, at least as
|
||||||
of Qt 5.13, is to clear the text and put it back.
|
of Qt 5.13, is to clear the text and put it back. This clears
|
||||||
|
the undo stack, so we only do it for big documents.
|
||||||
"""
|
"""
|
||||||
logger.verbose("Running spell checker")
|
logger.verbose("Running spell checker")
|
||||||
if self._spellCheck:
|
if self._spellCheck:
|
||||||
@@ -739,11 +740,16 @@ class GuiDocEditor(QTextEdit):
|
|||||||
passed to it without having to consider the internal logic of
|
passed to it without having to consider the internal logic of
|
||||||
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)
|
|
||||||
if self._docHandle is None:
|
if self._docHandle is None:
|
||||||
logger.error("No document open")
|
logger.error("No document open")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not isinstance(theAction, nwDocAction):
|
||||||
|
logger.error("Not a document action")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.verbose("Requesting action: %s" % theAction.name)
|
||||||
|
|
||||||
self._allowAutoReplace(False)
|
self._allowAutoReplace(False)
|
||||||
if theAction == nwDocAction.UNDO:
|
if theAction == nwDocAction.UNDO:
|
||||||
self.undo()
|
self.undo()
|
||||||
@@ -1012,7 +1018,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self._lastEdit = time()
|
self._lastEdit = time()
|
||||||
self._lastFind = None
|
self._lastFind = None
|
||||||
|
|
||||||
if self._qDocument.characterCount() > nwConst.MAX_DOCSIZE:
|
if self.document().characterCount() > nwConst.MAX_DOCSIZE:
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"The document has grown too big and you cannot add more text to it. "
|
"The document has grown too big and you cannot add more text to it. "
|
||||||
"The maximum size of a single novelWriter document is {0} MB."
|
"The maximum size of a single novelWriter document is {0} MB."
|
||||||
@@ -1029,7 +1035,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.wcTimer.start()
|
self.wcTimer.start()
|
||||||
|
|
||||||
if self._doReplace and chrAdd == 1:
|
if self._doReplace and chrAdd == 1:
|
||||||
self._docAutoReplace(self._qDocument.findBlock(thePos))
|
self._docAutoReplace(self.document().findBlock(thePos))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1198,7 +1204,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
# Must not be emitted if docHandle is None!
|
# Must not be emitted if docHandle is None!
|
||||||
self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
|
self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
|
||||||
|
|
||||||
self._checkDocSize(self._qDocument.characterCount())
|
self._checkDocSize(self.document().characterCount())
|
||||||
self.docFooter.updateCounts()
|
self.docFooter.updateCounts()
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1211,7 +1217,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
moved to has been drawn before the move is made.
|
moved to has been drawn before the move is made.
|
||||||
"""
|
"""
|
||||||
if self._queuePos is not None:
|
if self._queuePos is not None:
|
||||||
thePos = self._qDocument.documentLayout().hitTest(
|
thePos = self.document().documentLayout().hitTest(
|
||||||
QPointF(theSize.width(), theSize.height()), Qt.FuzzyHit
|
QPointF(theSize.width(), theSize.height()), Qt.FuzzyHit
|
||||||
)
|
)
|
||||||
if self._queuePos <= thePos:
|
if self._queuePos <= thePos:
|
||||||
@@ -1362,226 +1368,74 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions : Text Manipulation
|
||||||
##
|
##
|
||||||
|
|
||||||
def _followTag(self, theCursor=None, loadTag=True):
|
def _toggleFormat(self, fLen, fChar):
|
||||||
"""Activated by Ctrl+Enter. Checks that we're in a block
|
"""Toggle the formatting of a specific type for a piece of text.
|
||||||
starting with '@'. We then find the word under the cursor and
|
If more than one block is selected, the formatting is applied to
|
||||||
check that it is after the ':'. If all this is fine, we have a
|
the first block.
|
||||||
tag and can tell the document viewer to try and find and load
|
|
||||||
the file where the tag is defined.
|
|
||||||
"""
|
"""
|
||||||
if theCursor is None:
|
theCursor = self._autoSelect()
|
||||||
theCursor = self.textCursor()
|
if not theCursor.hasSelection():
|
||||||
|
logger.warning("No selection made, nothing to do")
|
||||||
theBlock = theCursor.block()
|
|
||||||
theText = theBlock.text()
|
|
||||||
|
|
||||||
if len(theText) == 0:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if theText.startswith("@"):
|
posS = theCursor.selectionStart()
|
||||||
|
posE = theCursor.selectionEnd()
|
||||||
|
|
||||||
theCursor.select(QTextCursor.WordUnderCursor)
|
blockS = self.document().findBlock(posS)
|
||||||
theWord = theCursor.selectedText()
|
blockE = self.document().findBlock(posE)
|
||||||
cPos = theText.find(":")
|
|
||||||
wPos = theCursor.selectionStart() - theBlock.position()
|
|
||||||
if wPos <= cPos:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if loadTag:
|
if blockS != blockE:
|
||||||
logger.verbose("Attempting to follow tag '%s'", theWord)
|
posE = blockS.position() + blockS.length() - 1
|
||||||
self.theParent.docViewer.loadFromTag(theWord)
|
theCursor.clearSelection()
|
||||||
|
theCursor.setPosition(posS, QTextCursor.MoveAnchor)
|
||||||
|
theCursor.setPosition(posE, QTextCursor.KeepAnchor)
|
||||||
|
self.setTextCursor(theCursor)
|
||||||
|
|
||||||
|
numB = 0
|
||||||
|
for n in range(fLen):
|
||||||
|
if self.document().characterAt(posS-n-1) == fChar:
|
||||||
|
numB += 1
|
||||||
else:
|
else:
|
||||||
logger.verbose("Potential tag '%s'", theWord)
|
break
|
||||||
|
|
||||||
return True
|
numA = 0
|
||||||
|
for n in range(fLen):
|
||||||
return False
|
if self.document().characterAt(posE+n) == fChar:
|
||||||
|
numA += 1
|
||||||
def _openSpellContext(self):
|
|
||||||
"""Opens the spell check context menu at the current point of
|
|
||||||
the cursor.
|
|
||||||
"""
|
|
||||||
self._openContextMenu(self.cursorRect().center())
|
|
||||||
return
|
|
||||||
|
|
||||||
def _docAutoReplace(self, theBlock):
|
|
||||||
"""Auto-replace text elements based on main configuration.
|
|
||||||
"""
|
|
||||||
if not theBlock.isValid():
|
|
||||||
return
|
|
||||||
|
|
||||||
theText = theBlock.text()
|
|
||||||
theCursor = self.textCursor()
|
|
||||||
thePos = theCursor.positionInBlock()
|
|
||||||
theLen = len(theText)
|
|
||||||
|
|
||||||
if theLen < 1 or thePos-1 > theLen:
|
|
||||||
return
|
|
||||||
|
|
||||||
theOne = theText[thePos-1:thePos]
|
|
||||||
theTwo = theText[thePos-2:thePos]
|
|
||||||
theThree = theText[thePos-3:thePos]
|
|
||||||
|
|
||||||
if not theOne: # Makes Neo sad
|
|
||||||
return
|
|
||||||
|
|
||||||
nDelete = 0
|
|
||||||
tInsert = theOne
|
|
||||||
|
|
||||||
if self.mainConf.doReplaceDQuote and theTwo == ' "':
|
|
||||||
nDelete = 1
|
|
||||||
tInsert = self._typDQOpen
|
|
||||||
|
|
||||||
elif self.mainConf.doReplaceDQuote and theOne == '"':
|
|
||||||
nDelete = 1
|
|
||||||
if thePos == 1:
|
|
||||||
tInsert = self._typDQOpen
|
|
||||||
else:
|
else:
|
||||||
tInsert = self._typDQClose
|
break
|
||||||
|
|
||||||
elif self.mainConf.doReplaceSQuote and theTwo == " '":
|
|
||||||
nDelete = 1
|
|
||||||
tInsert = self._typSQOpen
|
|
||||||
|
|
||||||
elif self.mainConf.doReplaceSQuote and theOne == "'":
|
|
||||||
nDelete = 1
|
|
||||||
if thePos == 1:
|
|
||||||
tInsert = self._typSQOpen
|
|
||||||
else:
|
|
||||||
tInsert = self._typSQClose
|
|
||||||
|
|
||||||
elif self.mainConf.doReplaceDash and theThree == "---":
|
|
||||||
nDelete = 3
|
|
||||||
tInsert = nwUnicode.U_EMDASH
|
|
||||||
|
|
||||||
elif self.mainConf.doReplaceDash and theTwo == "--":
|
|
||||||
nDelete = 2
|
|
||||||
tInsert = nwUnicode.U_ENDASH
|
|
||||||
|
|
||||||
elif self.mainConf.doReplaceDash and theTwo == nwUnicode.U_ENDASH + "-":
|
|
||||||
nDelete = 2
|
|
||||||
tInsert = nwUnicode.U_EMDASH
|
|
||||||
|
|
||||||
elif self.mainConf.doReplaceDots and theThree == "...":
|
|
||||||
nDelete = 3
|
|
||||||
tInsert = nwUnicode.U_HELLIP
|
|
||||||
|
|
||||||
tCheck = tInsert
|
|
||||||
if tCheck in self.mainConf.fmtPadBefore:
|
|
||||||
nDelete = max(nDelete, 1)
|
|
||||||
tInsert = self._typPadChar + tInsert
|
|
||||||
|
|
||||||
if tCheck in self.mainConf.fmtPadAfter:
|
|
||||||
nDelete = max(nDelete, 1)
|
|
||||||
tInsert = tInsert + self._typPadChar
|
|
||||||
|
|
||||||
if nDelete > 0:
|
|
||||||
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, nDelete)
|
|
||||||
theCursor.insertText(tInsert)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def _updateHeaders(self, checkPos=False, checkLevel=False):
|
|
||||||
"""Update the headers record and return True if anything
|
|
||||||
changed, if a check flag was provided.
|
|
||||||
"""
|
|
||||||
if self._docHandle is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
newHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
|
||||||
if checkPos:
|
|
||||||
newPos = [x[0] for x in newHeaders]
|
|
||||||
oldPos = [x[0] for x in self._docHeaders]
|
|
||||||
if checkLevel:
|
|
||||||
newLev = [x[1] for x in newHeaders]
|
|
||||||
oldLev = [x[1] for x in self._docHeaders]
|
|
||||||
|
|
||||||
self._docHeaders = newHeaders
|
|
||||||
|
|
||||||
if checkPos:
|
|
||||||
return newPos != oldPos
|
|
||||||
if checkLevel:
|
|
||||||
return newLev != oldLev
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
if fLen == min(numA, numB):
|
||||||
|
self._clearSurrounding(theCursor, fLen)
|
||||||
else:
|
else:
|
||||||
self.theParent.makeAlert(self.tr(
|
self._wrapSelection(fChar*fLen)
|
||||||
"Please select some text before calling replace quotes."
|
|
||||||
), nwAlert.ERROR)
|
|
||||||
|
|
||||||
return
|
return True
|
||||||
|
|
||||||
def _checkDocSize(self, theSize):
|
def _clearSurrounding(self, theCursor, nChars):
|
||||||
"""Check if document size crosses the big document limit set in
|
"""Clears n characters before and after the cursor.
|
||||||
config. If so, we will set the big document flag to True.
|
|
||||||
"""
|
"""
|
||||||
bigLim = self.mainConf.bigDocLimit*1000
|
if not theCursor.hasSelection():
|
||||||
newState = theSize > bigLim
|
logger.warning("No selection made, nothing to do")
|
||||||
|
return False
|
||||||
|
|
||||||
if newState != self._bigDoc:
|
posS = theCursor.selectionStart()
|
||||||
if newState:
|
posE = theCursor.selectionEnd()
|
||||||
logger.info(
|
theCursor.clearSelection()
|
||||||
f"The document size is {theSize:n} > {bigLim:n}, "
|
theCursor.beginEditBlock()
|
||||||
f"big doc mode has been enabled"
|
theCursor.setPosition(posS)
|
||||||
)
|
for i in range(nChars):
|
||||||
else:
|
theCursor.deletePreviousChar()
|
||||||
logger.info(
|
theCursor.setPosition(posE)
|
||||||
f"The document size is {theSize:n} <= {bigLim:n}, "
|
for i in range(nChars):
|
||||||
f"big doc mode has been disabled"
|
theCursor.deletePreviousChar()
|
||||||
)
|
theCursor.endEditBlock()
|
||||||
|
theCursor.clearSelection()
|
||||||
|
|
||||||
self._bigDoc = newState
|
return True
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def _wrapSelection(self, tBefore, tAfter=None):
|
def _wrapSelection(self, tBefore, tAfter=None):
|
||||||
"""Wraps the selected text in whatever is in tBefore and tAfter.
|
"""Wraps the selected text in whatever is in tBefore and tAfter.
|
||||||
@@ -1593,120 +1447,84 @@ class GuiDocEditor(QTextEdit):
|
|||||||
tAfter = tBefore
|
tAfter = tBefore
|
||||||
|
|
||||||
theCursor = self._autoSelect()
|
theCursor = self._autoSelect()
|
||||||
if theCursor.hasSelection():
|
if not theCursor.hasSelection():
|
||||||
posS = theCursor.selectionStart()
|
|
||||||
posE = theCursor.selectionEnd()
|
|
||||||
|
|
||||||
blockS = self._qDocument.findBlock(posS)
|
|
||||||
blockE = self._qDocument.findBlock(posE)
|
|
||||||
if blockS != blockE:
|
|
||||||
posE = blockS.position() + blockS.length() - 1
|
|
||||||
|
|
||||||
theCursor.clearSelection()
|
|
||||||
theCursor.beginEditBlock()
|
|
||||||
theCursor.setPosition(posE)
|
|
||||||
theCursor.insertText(tAfter)
|
|
||||||
theCursor.setPosition(posS)
|
|
||||||
theCursor.insertText(tBefore)
|
|
||||||
theCursor.endEditBlock()
|
|
||||||
|
|
||||||
theCursor.setPosition(posE + len(tBefore), QTextCursor.MoveAnchor)
|
|
||||||
theCursor.setPosition(posS + len(tBefore), QTextCursor.KeepAnchor)
|
|
||||||
self.setTextCursor(theCursor)
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.warning("No selection made, nothing to do")
|
logger.warning("No selection made, nothing to do")
|
||||||
return
|
return False
|
||||||
|
|
||||||
def _clearSurrounding(self, theCursor, nChars):
|
posS = theCursor.selectionStart()
|
||||||
"""Clears n characters before and after the cursor.
|
posE = theCursor.selectionEnd()
|
||||||
"""
|
|
||||||
if theCursor.hasSelection():
|
|
||||||
posS = theCursor.selectionStart()
|
|
||||||
posE = theCursor.selectionEnd()
|
|
||||||
theCursor.clearSelection()
|
|
||||||
theCursor.beginEditBlock()
|
|
||||||
theCursor.setPosition(posS)
|
|
||||||
for i in range(nChars):
|
|
||||||
theCursor.deletePreviousChar()
|
|
||||||
theCursor.setPosition(posE)
|
|
||||||
for i in range(nChars):
|
|
||||||
theCursor.deletePreviousChar()
|
|
||||||
theCursor.endEditBlock()
|
|
||||||
theCursor.clearSelection()
|
|
||||||
else:
|
|
||||||
logger.warning("No selection made, nothing to do")
|
|
||||||
return
|
|
||||||
|
|
||||||
def _autoSelect(self):
|
qDoc = self.document()
|
||||||
"""Returns a cursor which may or may not have a selection based
|
blockS = qDoc.findBlock(posS)
|
||||||
on user settings and document action.
|
blockE = qDoc.findBlock(posE)
|
||||||
|
if blockS != blockE:
|
||||||
|
posE = blockS.position() + blockS.length() - 1
|
||||||
|
|
||||||
|
theCursor.clearSelection()
|
||||||
|
theCursor.beginEditBlock()
|
||||||
|
theCursor.setPosition(posE)
|
||||||
|
theCursor.insertText(tAfter)
|
||||||
|
theCursor.setPosition(posS)
|
||||||
|
theCursor.insertText(tBefore)
|
||||||
|
theCursor.endEditBlock()
|
||||||
|
|
||||||
|
theCursor.setPosition(posE + len(tBefore), QTextCursor.MoveAnchor)
|
||||||
|
theCursor.setPosition(posS + len(tBefore), QTextCursor.KeepAnchor)
|
||||||
|
self.setTextCursor(theCursor)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _replaceQuotes(self, sQuote, oQuote, cQuote):
|
||||||
|
"""Replace all straight quotes in the selected text.
|
||||||
"""
|
"""
|
||||||
theCursor = self.textCursor()
|
theCursor = self.textCursor()
|
||||||
if self.mainConf.autoSelect and not theCursor.hasSelection():
|
if not theCursor.hasSelection():
|
||||||
theCursor.select(QTextCursor.WordUnderCursor)
|
self.theParent.makeAlert(self.tr(
|
||||||
posS = theCursor.selectionStart()
|
"Please select some text before calling replace quotes."
|
||||||
posE = theCursor.selectionEnd()
|
), nwAlert.ERROR)
|
||||||
|
return False
|
||||||
|
|
||||||
# Underscore counts as a part of the word, so check that the
|
posS = theCursor.selectionStart()
|
||||||
# selection isn't wrapped in italics markers.
|
posE = theCursor.selectionEnd()
|
||||||
reSelect = False
|
closeCheck = (
|
||||||
if self._qDocument.characterAt(posS) == "_":
|
" ", "\n", nwUnicode.U_LSEP, nwUnicode.U_PSEP
|
||||||
posS += 1
|
)
|
||||||
reSelect = True
|
|
||||||
if self._qDocument.characterAt(posE) == "_":
|
|
||||||
posE -= 1
|
|
||||||
reSelect = True
|
|
||||||
if reSelect:
|
|
||||||
theCursor.clearSelection()
|
|
||||||
theCursor.setPosition(posS, QTextCursor.MoveAnchor)
|
|
||||||
theCursor.setPosition(posE-1, QTextCursor.KeepAnchor)
|
|
||||||
|
|
||||||
self.setTextCursor(theCursor)
|
self._allowAutoReplace(False)
|
||||||
|
for posC in range(posS, posE+1):
|
||||||
|
theCursor.setPosition(posC)
|
||||||
|
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
|
||||||
|
selText = theCursor.selectedText()
|
||||||
|
|
||||||
return theCursor
|
nS = len(selText)
|
||||||
|
if nS == 2:
|
||||||
|
pC = selText[0]
|
||||||
|
cC = selText[1]
|
||||||
|
elif nS == 1:
|
||||||
|
pC = " "
|
||||||
|
cC = selText[0]
|
||||||
|
else: # pragma: no cover
|
||||||
|
continue
|
||||||
|
|
||||||
def _toggleFormat(self, fLen, fChar):
|
if cC != sQuote:
|
||||||
"""Toggle the formatting of a specific type for a piece of text.
|
continue
|
||||||
If more than one block is selected, the formatting is applied to
|
|
||||||
the first block.
|
|
||||||
"""
|
|
||||||
theCursor = self._autoSelect()
|
|
||||||
if theCursor.hasSelection():
|
|
||||||
posS = theCursor.selectionStart()
|
|
||||||
posE = theCursor.selectionEnd()
|
|
||||||
|
|
||||||
blockS = self._qDocument.findBlock(posS)
|
theCursor.clearSelection()
|
||||||
blockE = self._qDocument.findBlock(posE)
|
theCursor.setPosition(posC)
|
||||||
|
if pC in closeCheck:
|
||||||
if blockS != blockE:
|
theCursor.beginEditBlock()
|
||||||
posE = blockS.position() + blockS.length() - 1
|
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
|
||||||
theCursor.clearSelection()
|
theCursor.insertText(oQuote)
|
||||||
theCursor.setPosition(posS, QTextCursor.MoveAnchor)
|
theCursor.endEditBlock()
|
||||||
theCursor.setPosition(posE, QTextCursor.KeepAnchor)
|
|
||||||
self.setTextCursor(theCursor)
|
|
||||||
|
|
||||||
numB = 0
|
|
||||||
for n in range(fLen):
|
|
||||||
if self._qDocument.characterAt(posS-n-1) == fChar:
|
|
||||||
numB += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
numA = 0
|
|
||||||
for n in range(fLen):
|
|
||||||
if self._qDocument.characterAt(posE+n) == fChar:
|
|
||||||
numA += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
cLevel = min(numB, numA)
|
|
||||||
if cLevel == fLen:
|
|
||||||
self._clearSurrounding(theCursor, fLen)
|
|
||||||
else:
|
else:
|
||||||
self._wrapSelection(fChar*fLen)
|
theCursor.beginEditBlock()
|
||||||
|
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
|
||||||
|
theCursor.insertText(cQuote)
|
||||||
|
theCursor.endEditBlock()
|
||||||
|
|
||||||
return
|
self._allowAutoReplace(True)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _formatBlock(self, docAction):
|
def _formatBlock(self, docAction):
|
||||||
"""Changes the block format of the block under the cursor.
|
"""Changes the block format of the block under the cursor.
|
||||||
@@ -1874,6 +1692,206 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
##
|
||||||
|
# Internal Functions
|
||||||
|
##
|
||||||
|
|
||||||
|
def _followTag(self, theCursor=None, loadTag=True):
|
||||||
|
"""Activated by Ctrl+Enter. Checks that we're in a block
|
||||||
|
starting with '@'. We then find the word under the cursor and
|
||||||
|
check that it is after the ':'. If all this is fine, we have a
|
||||||
|
tag and can tell the document viewer to try and find and load
|
||||||
|
the file where the tag is defined.
|
||||||
|
"""
|
||||||
|
if theCursor is None:
|
||||||
|
theCursor = self.textCursor()
|
||||||
|
|
||||||
|
theBlock = theCursor.block()
|
||||||
|
theText = theBlock.text()
|
||||||
|
|
||||||
|
if len(theText) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if theText.startswith("@"):
|
||||||
|
|
||||||
|
theCursor.select(QTextCursor.WordUnderCursor)
|
||||||
|
theWord = theCursor.selectedText()
|
||||||
|
cPos = theText.find(":")
|
||||||
|
wPos = theCursor.selectionStart() - theBlock.position()
|
||||||
|
if wPos <= cPos:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if loadTag:
|
||||||
|
logger.verbose("Attempting to follow tag '%s'" % theWord)
|
||||||
|
self.theParent.docViewer.loadFromTag(theWord)
|
||||||
|
else:
|
||||||
|
logger.verbose("Potential tag '%s'" % theWord)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _openSpellContext(self):
|
||||||
|
"""Opens the spell check context menu at the current point of
|
||||||
|
the cursor.
|
||||||
|
"""
|
||||||
|
self._openContextMenu(self.cursorRect().center())
|
||||||
|
return
|
||||||
|
|
||||||
|
def _docAutoReplace(self, theBlock):
|
||||||
|
"""Auto-replace text elements based on main configuration.
|
||||||
|
"""
|
||||||
|
if not theBlock.isValid():
|
||||||
|
return
|
||||||
|
|
||||||
|
theText = theBlock.text()
|
||||||
|
theCursor = self.textCursor()
|
||||||
|
thePos = theCursor.positionInBlock()
|
||||||
|
theLen = len(theText)
|
||||||
|
|
||||||
|
if theLen < 1 or thePos-1 > theLen:
|
||||||
|
return
|
||||||
|
|
||||||
|
theOne = theText[thePos-1:thePos]
|
||||||
|
theTwo = theText[thePos-2:thePos]
|
||||||
|
theThree = theText[thePos-3:thePos]
|
||||||
|
|
||||||
|
if not theOne:
|
||||||
|
# Sorry, Neo and Zathras
|
||||||
|
return
|
||||||
|
|
||||||
|
nDelete = 0
|
||||||
|
tInsert = theOne
|
||||||
|
|
||||||
|
if self.mainConf.doReplaceDQuote and theTwo == ' "':
|
||||||
|
nDelete = 1
|
||||||
|
tInsert = self._typDQOpen
|
||||||
|
|
||||||
|
elif self.mainConf.doReplaceDQuote and theOne == '"':
|
||||||
|
nDelete = 1
|
||||||
|
if thePos == 1:
|
||||||
|
tInsert = self._typDQOpen
|
||||||
|
else:
|
||||||
|
tInsert = self._typDQClose
|
||||||
|
|
||||||
|
elif self.mainConf.doReplaceSQuote and theTwo == " '":
|
||||||
|
nDelete = 1
|
||||||
|
tInsert = self._typSQOpen
|
||||||
|
|
||||||
|
elif self.mainConf.doReplaceSQuote and theOne == "'":
|
||||||
|
nDelete = 1
|
||||||
|
if thePos == 1:
|
||||||
|
tInsert = self._typSQOpen
|
||||||
|
else:
|
||||||
|
tInsert = self._typSQClose
|
||||||
|
|
||||||
|
elif self.mainConf.doReplaceDash and theThree == "---":
|
||||||
|
nDelete = 3
|
||||||
|
tInsert = nwUnicode.U_EMDASH
|
||||||
|
|
||||||
|
elif self.mainConf.doReplaceDash and theTwo == "--":
|
||||||
|
nDelete = 2
|
||||||
|
tInsert = nwUnicode.U_ENDASH
|
||||||
|
|
||||||
|
elif self.mainConf.doReplaceDash and theTwo == nwUnicode.U_ENDASH + "-":
|
||||||
|
nDelete = 2
|
||||||
|
tInsert = nwUnicode.U_EMDASH
|
||||||
|
|
||||||
|
elif self.mainConf.doReplaceDots and theThree == "...":
|
||||||
|
nDelete = 3
|
||||||
|
tInsert = nwUnicode.U_HELLIP
|
||||||
|
|
||||||
|
tCheck = tInsert
|
||||||
|
if tCheck in self.mainConf.fmtPadBefore:
|
||||||
|
nDelete = max(nDelete, 1)
|
||||||
|
tInsert = self._typPadChar + tInsert
|
||||||
|
|
||||||
|
if tCheck in self.mainConf.fmtPadAfter:
|
||||||
|
nDelete = max(nDelete, 1)
|
||||||
|
tInsert = tInsert + self._typPadChar
|
||||||
|
|
||||||
|
if nDelete > 0:
|
||||||
|
theCursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, nDelete)
|
||||||
|
theCursor.insertText(tInsert)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _updateHeaders(self, checkPos=False, checkLevel=False):
|
||||||
|
"""Update the headers record and return True if anything
|
||||||
|
changed, if a check flag was provided.
|
||||||
|
"""
|
||||||
|
if self._docHandle is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
newHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
||||||
|
if checkPos:
|
||||||
|
newPos = [x[0] for x in newHeaders]
|
||||||
|
oldPos = [x[0] for x in self._docHeaders]
|
||||||
|
if checkLevel:
|
||||||
|
newLev = [x[1] for x in newHeaders]
|
||||||
|
oldLev = [x[1] for x in self._docHeaders]
|
||||||
|
|
||||||
|
self._docHeaders = newHeaders
|
||||||
|
|
||||||
|
if checkPos:
|
||||||
|
return newPos != oldPos
|
||||||
|
if checkLevel:
|
||||||
|
return newLev != oldLev
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _checkDocSize(self, theSize):
|
||||||
|
"""Check if document size crosses the big document limit set in
|
||||||
|
config. If so, we will set the big document flag to True.
|
||||||
|
"""
|
||||||
|
bigLim = round(self.mainConf.bigDocLimit*1000)
|
||||||
|
newState = theSize > bigLim
|
||||||
|
|
||||||
|
if newState != self._bigDoc:
|
||||||
|
if newState:
|
||||||
|
logger.info(
|
||||||
|
f"The document size is {theSize:n} > {bigLim:n}, "
|
||||||
|
f"big doc mode has been enabled"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
f"The document size is {theSize:n} <= {bigLim:n}, "
|
||||||
|
f"big doc mode has been disabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._bigDoc = newState
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _autoSelect(self):
|
||||||
|
"""Returns a cursor which may or may not have a selection based
|
||||||
|
on user settings and document action.
|
||||||
|
"""
|
||||||
|
theCursor = self.textCursor()
|
||||||
|
if self.mainConf.autoSelect and not theCursor.hasSelection():
|
||||||
|
theCursor.select(QTextCursor.WordUnderCursor)
|
||||||
|
posS = theCursor.selectionStart()
|
||||||
|
posE = theCursor.selectionEnd()
|
||||||
|
|
||||||
|
# Underscore counts as a part of the word, so check that the
|
||||||
|
# selection isn't wrapped in italics markers.
|
||||||
|
reSelect = False
|
||||||
|
qDoc = self.document()
|
||||||
|
if qDoc.characterAt(posS) == "_":
|
||||||
|
posS += 1
|
||||||
|
reSelect = True
|
||||||
|
if qDoc.characterAt(posE) == "_":
|
||||||
|
posE -= 1
|
||||||
|
reSelect = True
|
||||||
|
if reSelect:
|
||||||
|
theCursor.clearSelection()
|
||||||
|
theCursor.setPosition(posS, QTextCursor.MoveAnchor)
|
||||||
|
theCursor.setPosition(posE-1, QTextCursor.KeepAnchor)
|
||||||
|
|
||||||
|
self.setTextCursor(theCursor)
|
||||||
|
|
||||||
|
return theCursor
|
||||||
|
|
||||||
def _makeSelection(self, selMode):
|
def _makeSelection(self, selMode):
|
||||||
"""Wrapper function to select text based on a selection mode.
|
"""Wrapper function to select text based on a selection mode.
|
||||||
"""
|
"""
|
||||||
@@ -2753,7 +2771,7 @@ class GuiDocEditFooter(QWidget):
|
|||||||
else:
|
else:
|
||||||
theCursor = self.docEditor.textCursor()
|
theCursor = self.docEditor.textCursor()
|
||||||
iLine = theCursor.blockNumber() + 1
|
iLine = theCursor.blockNumber() + 1
|
||||||
iDist = 100*iLine/self.docEditor._qDocument.blockCount()
|
iDist = 100*iLine/self.docEditor.document().blockCount()
|
||||||
|
|
||||||
self.linesText.setText(
|
self.linesText.setText(
|
||||||
self.tr("Line: {0} ({1})").format(f"{iLine:n}", f"{iDist:.0f} %")
|
self.tr("Line: {0} ({1})").format(f"{iLine:n}", f"{iDist:.0f} %")
|
||||||
@@ -2775,7 +2793,7 @@ class GuiDocEditFooter(QWidget):
|
|||||||
self.tr("Words: {0} ({1})").format(f"{wCount:n}", f"{wDiff:+n}")
|
self.tr("Words: {0} ({1})").format(f"{wCount:n}", f"{wDiff:+n}")
|
||||||
)
|
)
|
||||||
|
|
||||||
byteSize = self.docEditor._qDocument.characterCount()
|
byteSize = self.docEditor.document().characterCount()
|
||||||
self.wordsText.setToolTip(
|
self.wordsText.setToolTip(
|
||||||
self.tr("Document size is {0} bytes").format(f"{byteSize:n}")
|
self.tr("Document size is {0} bytes").format(f"{byteSize:n}")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -261,11 +261,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
"""Loop through all blocks and rehighlight those of a given
|
"""Loop through all blocks and rehighlight those of a given
|
||||||
content type.
|
content type.
|
||||||
"""
|
"""
|
||||||
qDocument = self.document()
|
qDoc = self.document()
|
||||||
nBlocks = qDocument.blockCount()
|
nBlocks = qDoc.blockCount()
|
||||||
bfTime = time()
|
bfTime = time()
|
||||||
for i in range(nBlocks):
|
for i in range(nBlocks):
|
||||||
theBlock = qDocument.findBlockByNumber(i)
|
theBlock = qDoc.findBlockByNumber(i)
|
||||||
if theBlock.userState() & theType > 0:
|
if theBlock.userState() & theType > 0:
|
||||||
self.rehighlightBlock(theBlock)
|
self.rehighlightBlock(theBlock)
|
||||||
afTime = time()
|
afTime = time()
|
||||||
|
|||||||
+6
-7
@@ -61,7 +61,6 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
|
|
||||||
# Internal Variables
|
# Internal Variables
|
||||||
self._docHandle = None
|
self._docHandle = None
|
||||||
self._qDocument = self.document()
|
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
self.setMinimumWidth(self.mainConf.pxInt(300))
|
self.setMinimumWidth(self.mainConf.pxInt(300))
|
||||||
@@ -106,7 +105,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
theFont = QFont()
|
theFont = QFont()
|
||||||
if self.mainConf.textFont is None:
|
if self.mainConf.textFont is None:
|
||||||
# If none is defined, set the default back to config
|
# If none is defined, set the default back to config
|
||||||
self.mainConf.textFont = self._qDocument.defaultFont().family()
|
self.mainConf.textFont = self.document().defaultFont().family()
|
||||||
theFont.setFamily(self.mainConf.textFont)
|
theFont.setFamily(self.mainConf.textFont)
|
||||||
theFont.setPointSize(self.mainConf.textSize)
|
theFont.setPointSize(self.mainConf.textSize)
|
||||||
self.setFont(theFont)
|
self.setFont(theFont)
|
||||||
@@ -127,11 +126,11 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self.docFooter.matchColours()
|
self.docFooter.matchColours()
|
||||||
|
|
||||||
# Set default text margins
|
# Set default text margins
|
||||||
self._qDocument.setDocumentMargin(0)
|
self.document().setDocumentMargin(0)
|
||||||
theOpt = QTextOption()
|
theOpt = QTextOption()
|
||||||
if self.mainConf.doJustify:
|
if self.mainConf.doJustify:
|
||||||
theOpt.setAlignment(Qt.AlignJustify)
|
theOpt.setAlignment(Qt.AlignJustify)
|
||||||
self._qDocument.setDefaultTextOption(theOpt)
|
self.document().setDefaultTextOption(theOpt)
|
||||||
|
|
||||||
# Scroll bars
|
# Scroll bars
|
||||||
if self.mainConf.hideVScroll:
|
if self.mainConf.hideVScroll:
|
||||||
@@ -234,7 +233,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
def redrawText(self):
|
def redrawText(self):
|
||||||
"""Redraw the text by marking the document content as "dirty".
|
"""Redraw the text by marking the document content as "dirty".
|
||||||
"""
|
"""
|
||||||
self._qDocument.markContentsDirty(0, self._qDocument.characterCount())
|
self.document().markContentsDirty(0, self.document().characterCount())
|
||||||
self.updateDocMargins()
|
self.updateDocMargins()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -373,7 +372,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
if not isinstance(theLine, int):
|
if not isinstance(theLine, int):
|
||||||
return False
|
return False
|
||||||
if theLine >= 0:
|
if theLine >= 0:
|
||||||
theBlock = self._qDocument.findBlockByLineNumber(theLine)
|
theBlock = self.document().findBlockByLineNumber(theLine)
|
||||||
if theBlock:
|
if theBlock:
|
||||||
self.setCursorPosition(theBlock.position())
|
self.setCursorPosition(theBlock.position())
|
||||||
logger.verbose("Cursor moved to line %d", theLine)
|
logger.verbose("Cursor moved to line %d", theLine)
|
||||||
@@ -563,7 +562,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
mColG=self.theTheme.colMod[1],
|
mColG=self.theTheme.colMod[1],
|
||||||
mColB=self.theTheme.colMod[2],
|
mColB=self.theTheme.colMod[2],
|
||||||
)
|
)
|
||||||
self._qDocument.setDefaultStyleSheet(styleSheet)
|
self.document().setDefaultStyleSheet(styleSheet)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
+9
-10
@@ -980,7 +980,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
thePrinter.setFontEmbeddingEnabled(True)
|
thePrinter.setFontEmbeddingEnabled(True)
|
||||||
thePrinter.setColorMode(QPrinter.Color)
|
thePrinter.setColorMode(QPrinter.Color)
|
||||||
thePrinter.setOutputFileName(savePath)
|
thePrinter.setOutputFileName(savePath)
|
||||||
self.docView.qDocument.print(thePrinter)
|
self.docView.document().print(thePrinter)
|
||||||
wSuccess = True
|
wSuccess = True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1018,7 +1018,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
"""
|
"""
|
||||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
thePrinter.setOrientation(QPrinter.Portrait)
|
thePrinter.setOrientation(QPrinter.Portrait)
|
||||||
self.docView.qDocument.print(thePrinter)
|
self.docView.document().print(thePrinter)
|
||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1195,8 +1195,7 @@ class GuiBuildNovelDocView(QTextBrowser):
|
|||||||
self.setMinimumWidth(40*self.theParent.theTheme.textNWidth)
|
self.setMinimumWidth(40*self.theParent.theTheme.textNWidth)
|
||||||
self.setOpenExternalLinks(False)
|
self.setOpenExternalLinks(False)
|
||||||
|
|
||||||
self.qDocument = self.document()
|
self.document().setDocumentMargin(self.mainConf.getTextMargin())
|
||||||
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
|
|
||||||
self.setPlaceholderText(self.tr(
|
self.setPlaceholderText(self.tr(
|
||||||
"This area will show the content of the document to be "
|
"This area will show the content of the document to be "
|
||||||
"exported or printed. Press the \"Build Preview\" button "
|
"exported or printed. Press the \"Build Preview\" button "
|
||||||
@@ -1206,7 +1205,7 @@ class GuiBuildNovelDocView(QTextBrowser):
|
|||||||
theFont = QFont()
|
theFont = QFont()
|
||||||
if self.mainConf.textFont is None:
|
if self.mainConf.textFont is None:
|
||||||
# If none is defined, set the default back to config
|
# If none is defined, set the default back to config
|
||||||
self.mainConf.textFont = self.qDocument.defaultFont().family()
|
self.mainConf.textFont = self.document().defaultFont().family()
|
||||||
theFont.setFamily(self.mainConf.textFont)
|
theFont.setFamily(self.mainConf.textFont)
|
||||||
theFont.setPointSize(self.mainConf.textSize)
|
theFont.setPointSize(self.mainConf.textSize)
|
||||||
self.setFont(theFont)
|
self.setFont(theFont)
|
||||||
@@ -1256,12 +1255,12 @@ class GuiBuildNovelDocView(QTextBrowser):
|
|||||||
def setJustify(self, doJustify):
|
def setJustify(self, doJustify):
|
||||||
"""Set the justify text option.
|
"""Set the justify text option.
|
||||||
"""
|
"""
|
||||||
theOpt = self.qDocument.defaultTextOption()
|
theOpt = self.document().defaultTextOption()
|
||||||
if doJustify:
|
if doJustify:
|
||||||
theOpt.setAlignment(Qt.AlignJustify)
|
theOpt.setAlignment(Qt.AlignJustify)
|
||||||
else:
|
else:
|
||||||
theOpt.setAlignment(Qt.AlignAbsolute)
|
theOpt.setAlignment(Qt.AlignAbsolute)
|
||||||
self.qDocument.setDefaultTextOption(theOpt)
|
self.document().setDefaultTextOption(theOpt)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setTextFont(self, textFont, textSize):
|
def setTextFont(self, textFont, textSize):
|
||||||
@@ -1298,7 +1297,7 @@ class GuiBuildNovelDocView(QTextBrowser):
|
|||||||
|
|
||||||
# Since we change the content while it may still be rendering, we mark
|
# Since we change the content while it may still be rendering, we mark
|
||||||
# the document dirty again to make sure it's re-rendered properly.
|
# the document dirty again to make sure it's re-rendered properly.
|
||||||
self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
|
self.document().markContentsDirty(0, self.document().characterCount())
|
||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1312,14 +1311,14 @@ class GuiBuildNovelDocView(QTextBrowser):
|
|||||||
theStyles.append("a {color: rgb(66, 113, 174);}")
|
theStyles.append("a {color: rgb(66, 113, 174);}")
|
||||||
theStyles.append(".tags {color: rgb(245, 135, 31); font-weight: bold;}")
|
theStyles.append(".tags {color: rgb(245, 135, 31); font-weight: bold;}")
|
||||||
|
|
||||||
self.qDocument.setDefaultStyleSheet("\n".join(theStyles))
|
self.document().setDefaultStyleSheet("\n".join(theStyles))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def clearStyleSheet(self):
|
def clearStyleSheet(self):
|
||||||
"""Clears the document stylesheet.
|
"""Clears the document stylesheet.
|
||||||
"""
|
"""
|
||||||
self.qDocument.setDefaultStyleSheet("")
|
self.document().setDefaultStyleSheet("")
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -240,3 +240,62 @@ def nwOldProj(tmpDir):
|
|||||||
shutil.rmtree(dstDir)
|
shutil.rmtree(dstDir)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Useful Fixtures
|
||||||
|
##
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def ipsumText():
|
||||||
|
"""Return five paragraphs of Lorem Ipsum text.
|
||||||
|
"""
|
||||||
|
thatIpsum = [(
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc maximus justo non dictum co"
|
||||||
|
"mmodo. Curabitur lacinia tempor orci vel luctus. Phasellus porta metus eu massa luctus, e"
|
||||||
|
"get euismod risus rhoncus. Vestibulum sed arcu nisi. Maecenas pretium facilisis velit, ve"
|
||||||
|
"l semper lacus aliquam sit amet. Vestibulum vulputate neque ligula, rhoncus blandit turpi"
|
||||||
|
"s consequat id. Mauris sagittis vehicula imperdiet. Duis sed nunc pretium, ornare purus v"
|
||||||
|
"el, sodales augue. Maecenas a suscipit risus. Quisque volutpat justo eleifend est ullamco"
|
||||||
|
"rper fermentum. Donec ullamcorper et tortor a laoreet. Nam id risus nisi. Vivamus non imp"
|
||||||
|
"erdiet erat, sit amet imperdiet felis. Mauris vitae neque et est aliquam scelerisque non "
|
||||||
|
"non ipsum."
|
||||||
|
), (
|
||||||
|
"Nullam laoreet lorem nec malesuada vehicula. Vivamus tempus sodales lectus sed viverra. A"
|
||||||
|
"enean lacinia sollicitudin quam, quis tempus eros suscipit id. Duis sed rutrum nisi, ut p"
|
||||||
|
"ulvinar magna. Nam et cursus tortor. Phasellus ac odio tellus. Nullam in iaculis ipsum. V"
|
||||||
|
"ivamus ante sem, ultricies sed varius quis, tristique nec tellus. Nullam eu urna vitae la"
|
||||||
|
"cus hendrerit gravida. Quisque pulvinar erat ex, id efficitur velit sodales vitae. Proin "
|
||||||
|
"vestibulum, sapien eget mattis euismod, tortor quam viverra risus, at congue mauris torto"
|
||||||
|
"r eu nunc. Mauris pellentesque elit leo, quis eleifend sem placerat a. Vivamus iaculis du"
|
||||||
|
"i eget tellus volutpat, ac varius nisi facilisis."
|
||||||
|
), (
|
||||||
|
"Nullam a nisl magna. Praesent commodo nec diam aliquet vestibulum. In sapien velit, sodal"
|
||||||
|
"es feugiat porta ut, rhoncus a elit. Quisque egestas nisi eu eros laoreet, quis facilisis"
|
||||||
|
" est pretium. Nullam bibendum sed tellus nec lobortis. Duis elit massa, volutpat a lacini"
|
||||||
|
"a a, ullamcorper in dui. Suspendisse ac laoreet dui. Curabitur elementum, tortor elementu"
|
||||||
|
"m ultricies laoreet, nunc massa vulputate augue, vitae tincidunt nunc enim eget nisl."
|
||||||
|
), (
|
||||||
|
"Pellentesque nibh urna, volutpat et feugiat porta, rutrum sed lectus. Aliquam eget risus "
|
||||||
|
"id orci tincidunt condimentum et sit amet purus. Curabitur tincidunt odio vel ante feugia"
|
||||||
|
"t feugiat. Proin nunc lorem, molestie a sapien et, varius elementum nunc. Donec non ferme"
|
||||||
|
"ntum nisl. In et massa placerat, faucibus felis eu, congue nisi. Proin sed tortor non lor"
|
||||||
|
"em mattis cursus. Vestibulum magna neque, bibendum vel nibh et, tincidunt rhoncus nisi. D"
|
||||||
|
"uis pulvinar mi a quam rutrum maximus. Nunc sollicitudin, urna in cursus facilisis, augue"
|
||||||
|
" neque imperdiet metus, ac finibus lorem ante id nulla. Sed maximus eleifend justo id feu"
|
||||||
|
"giat. Cras eget diam vel est blandit tempor nec a leo. Mauris risus est, fringilla in ali"
|
||||||
|
"quam a, sagittis vel enim. Nullam sodales id erat placerat lobortis."
|
||||||
|
), (
|
||||||
|
"Integer ac gravida quam. Quisque eleifend nisl nec pretium tincidunt. Quisque sollicitudi"
|
||||||
|
"n nisi in hendrerit scelerisque. Sed ornare nisl lacus, sit amet consectetur lectus egest"
|
||||||
|
"as et. Vivamus nec arcu lorem. Donec rhoncus, purus a porta accumsan, nunc lectus iaculis"
|
||||||
|
" libero, et fringilla tellus augue et velit. Integer varius felis scelerisque, vulputate "
|
||||||
|
"tellus eu, laoreet justo. Suspendisse sit amet sem vehicula, auctor odio sed, aliquet eni"
|
||||||
|
"m. In ac tortor sed tortor fringilla elementum. Nulla non odio at magna vulputate sceleri"
|
||||||
|
"sque. Nam elementum diam eu rutrum scelerisque. Sed fermentum, felis quis vulputate ferme"
|
||||||
|
"ntum, libero metus sollicitudin est, in faucibus purus nulla non dolor. Ut vitae felis po"
|
||||||
|
"rta, feugiat nunc et, bibendum neque. Nullam nec lorem nec metus ullamcorper malesuada ut"
|
||||||
|
" a nisl. Etiam eget tristique dui. Nulla sed mi finibus, venenatis tellus non, maximus en"
|
||||||
|
"im."
|
||||||
|
)]
|
||||||
|
return thatIpsum
|
||||||
|
|||||||
+1112
-306
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,354 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
novelWriter – Main GUI Editor Class Tester
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
This file is a part of novelWriter
|
||||||
|
Copyright 2018–2021, Veronica Berglyd Olsen
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import os
|
||||||
|
|
||||||
|
from shutil import copyfile
|
||||||
|
from tools import cmpFiles
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtWidgets import QMessageBox, QDialog
|
||||||
|
|
||||||
|
from nw.dialogs.itemeditor import GuiItemEditor
|
||||||
|
from nw.gui.doceditor import GuiDocEditor
|
||||||
|
from nw.gui.projtree import GuiProjectTree
|
||||||
|
from nw.enum import nwItemType, nwWidget
|
||||||
|
|
||||||
|
keyDelay = 2
|
||||||
|
typeDelay = 1
|
||||||
|
stepDelay = 20
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
|
||||||
|
"""Test the document editor.
|
||||||
|
"""
|
||||||
|
# Block message box
|
||||||
|
monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
|
||||||
|
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *args: None)
|
||||||
|
monkeypatch.setattr(GuiItemEditor, "result", lambda *args: QDialog.Accepted)
|
||||||
|
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *args: True)
|
||||||
|
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *args: True)
|
||||||
|
|
||||||
|
# Create new, save, close project
|
||||||
|
nwGUI.theProject.projTree.setSeed(42)
|
||||||
|
assert nwGUI.newProject({"projPath": fncProj})
|
||||||
|
assert nwGUI.saveProject()
|
||||||
|
assert nwGUI.closeProject()
|
||||||
|
|
||||||
|
assert len(nwGUI.theProject.projTree) == 0
|
||||||
|
assert len(nwGUI.theProject.projTree._treeOrder) == 0
|
||||||
|
assert len(nwGUI.theProject.projTree._treeRoots) == 0
|
||||||
|
assert nwGUI.theProject.projTree.trashRoot() is None
|
||||||
|
assert nwGUI.theProject.projPath is None
|
||||||
|
assert nwGUI.theProject.projMeta is None
|
||||||
|
assert nwGUI.theProject.projFile == "nwProject.nwx"
|
||||||
|
assert nwGUI.theProject.projName == ""
|
||||||
|
assert nwGUI.theProject.bookTitle == ""
|
||||||
|
assert len(nwGUI.theProject.bookAuthors) == 0
|
||||||
|
assert not nwGUI.theProject.spellCheck
|
||||||
|
|
||||||
|
# Check the files
|
||||||
|
projFile = os.path.join(fncProj, "nwProject.nwx")
|
||||||
|
testFile = os.path.join(outDir, "guiEditor_Main_Initial_nwProject.nwx")
|
||||||
|
compFile = os.path.join(refDir, "guiEditor_Main_Initial_nwProject.nwx")
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
|
|
||||||
|
# Re-open project
|
||||||
|
assert nwGUI.openProject(fncProj)
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
# Check that we loaded the data
|
||||||
|
assert len(nwGUI.theProject.projTree) == 8
|
||||||
|
assert len(nwGUI.theProject.projTree._treeOrder) == 8
|
||||||
|
assert len(nwGUI.theProject.projTree._treeRoots) == 4
|
||||||
|
assert nwGUI.theProject.projTree.trashRoot() is None
|
||||||
|
assert nwGUI.theProject.projPath == fncProj
|
||||||
|
assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
|
||||||
|
assert nwGUI.theProject.projFile == "nwProject.nwx"
|
||||||
|
assert nwGUI.theProject.projName == "New Project"
|
||||||
|
assert nwGUI.theProject.bookTitle == ""
|
||||||
|
assert len(nwGUI.theProject.bookAuthors) == 0
|
||||||
|
assert not nwGUI.theProject.spellCheck
|
||||||
|
|
||||||
|
# Check that tree items have been created
|
||||||
|
assert nwGUI.treeView._getTreeItem("73475cb40a568") is not None
|
||||||
|
assert nwGUI.treeView._getTreeItem("25fc0e7096fc6") is not None
|
||||||
|
assert nwGUI.treeView._getTreeItem("31489056e0916") is not None
|
||||||
|
assert nwGUI.treeView._getTreeItem("98010bd9270f9") is not None
|
||||||
|
assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
|
||||||
|
assert nwGUI.treeView._getTreeItem("44cb730c42048") is not None
|
||||||
|
assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") is not None
|
||||||
|
assert nwGUI.treeView._getTreeItem("811786ad1ae74") is not None
|
||||||
|
|
||||||
|
nwGUI.mainMenu.aSpellCheck.setChecked(True)
|
||||||
|
assert nwGUI.mainMenu._toggleSpellCheck()
|
||||||
|
|
||||||
|
# Change some settings
|
||||||
|
nwGUI.mainConf.hideHScroll = True
|
||||||
|
nwGUI.mainConf.hideVScroll = True
|
||||||
|
nwGUI.mainConf.scrollPastEnd = True
|
||||||
|
nwGUI.mainConf.autoScrollPos = 80
|
||||||
|
nwGUI.mainConf.autoScroll = True
|
||||||
|
|
||||||
|
# Add a Character File
|
||||||
|
nwGUI.switchFocus(nwWidget.TREE)
|
||||||
|
nwGUI.treeView.clearSelection()
|
||||||
|
nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
|
||||||
|
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
||||||
|
assert nwGUI.openSelectedItem()
|
||||||
|
|
||||||
|
# Type something into the document
|
||||||
|
nwGUI.switchFocus(nwWidget.EDITOR)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
|
||||||
|
for c in "# Jane Doe":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@tag: Jane":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "This is a file about Jane.":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
# Add a Plot File
|
||||||
|
nwGUI.switchFocus(nwWidget.TREE)
|
||||||
|
nwGUI.treeView.clearSelection()
|
||||||
|
nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True)
|
||||||
|
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
||||||
|
assert nwGUI.openSelectedItem()
|
||||||
|
|
||||||
|
# Type something into the document
|
||||||
|
nwGUI.switchFocus(nwWidget.EDITOR)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
|
||||||
|
for c in "# Main Plot":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@tag: MainPlot":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "This is a file detailing the main plot.":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
# Add a World File
|
||||||
|
nwGUI.switchFocus(nwWidget.TREE)
|
||||||
|
nwGUI.treeView.clearSelection()
|
||||||
|
nwGUI.treeView._getTreeItem("811786ad1ae74").setSelected(True)
|
||||||
|
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
||||||
|
assert nwGUI.openSelectedItem()
|
||||||
|
|
||||||
|
# Add Some Text
|
||||||
|
nwGUI.docEditor.replaceText("Hello World!")
|
||||||
|
assert nwGUI.docEditor.getText() == "Hello World!"
|
||||||
|
nwGUI.docEditor.replaceText("")
|
||||||
|
|
||||||
|
# Type something into the document
|
||||||
|
nwGUI.switchFocus(nwWidget.EDITOR)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
|
||||||
|
for c in "# Main Location":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@tag: Home":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "This is a file describing Jane's home.":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
# Trigger autosaves before making more changes
|
||||||
|
nwGUI._autoSaveDocument()
|
||||||
|
nwGUI._autoSaveProject()
|
||||||
|
|
||||||
|
# Select the 'New Scene' file
|
||||||
|
nwGUI.switchFocus(nwWidget.TREE)
|
||||||
|
nwGUI.treeView.clearSelection()
|
||||||
|
nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True)
|
||||||
|
nwGUI.treeView._getTreeItem("31489056e0916").setExpanded(True)
|
||||||
|
nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True)
|
||||||
|
assert nwGUI.openSelectedItem()
|
||||||
|
|
||||||
|
# Type something into the document
|
||||||
|
nwGUI.switchFocus(nwWidget.EDITOR)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
|
||||||
|
for c in "# Novel":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "## Chapter":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "@pov: Jane":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@plot: MainPlot":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "### Scene":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "% How about a comment?":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@pov: Jane":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@plot: MainPlot":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@location: Home":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "#### Some Section":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "@char: Jane":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "This is a paragraph of nonsense text.":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in (
|
||||||
|
"This is another paragraph of much longer nonsense text. "
|
||||||
|
"It is in fact 1 very very NONSENSICAL nonsense text! "
|
||||||
|
):
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
for c in "We can also try replacing \"quotes\", even single 'quotes' are replaced. ":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
for c in "Isn't that nice? ":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
for c in "Ellipsis? Not a problem either ... ":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
for c in "How about three hyphens - -":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=keyDelay)
|
||||||
|
for c in "- for long dash? It works too.":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "\"Full line double quoted text.\"":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "'Full line single quoted text.'":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
nwGUI.docEditor.wCounter.run()
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
# Save the document
|
||||||
|
assert nwGUI.docEditor.docChanged()
|
||||||
|
assert nwGUI.saveDocument()
|
||||||
|
assert not nwGUI.docEditor.docChanged()
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
nwGUI.rebuildIndex()
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
# Open and view the edited document
|
||||||
|
nwGUI.switchFocus(nwWidget.VIEWER)
|
||||||
|
assert nwGUI.openDocument("0e17daca5f3e1")
|
||||||
|
assert nwGUI.viewDocument("0e17daca5f3e1")
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
assert nwGUI.saveProject()
|
||||||
|
assert nwGUI.closeDocViewer()
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
# Check a Quick Create and Delete
|
||||||
|
assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
||||||
|
newHandle = nwGUI.treeView.getSelectedHandle()
|
||||||
|
assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
|
||||||
|
assert nwGUI.treeView.deleteItem()
|
||||||
|
assert nwGUI.treeView.setSelectedHandle(newHandle)
|
||||||
|
assert nwGUI.treeView.deleteItem()
|
||||||
|
assert nwGUI.theProject.projTree["2fca346db6561"] is not None # Trash
|
||||||
|
assert nwGUI.saveProject()
|
||||||
|
|
||||||
|
# Check the files
|
||||||
|
projFile = os.path.join(fncProj, "nwProject.nwx")
|
||||||
|
testFile = os.path.join(outDir, "guiEditor_Main_Final_nwProject.nwx")
|
||||||
|
compFile = os.path.join(refDir, "guiEditor_Main_Final_nwProject.nwx")
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
|
||||||
|
|
||||||
|
projFile = os.path.join(fncProj, "content", "031b4af5197ec.nwd")
|
||||||
|
testFile = os.path.join(outDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
|
||||||
|
compFile = os.path.join(refDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, compFile)
|
||||||
|
|
||||||
|
projFile = os.path.join(fncProj, "content", "1a6562590ef19.nwd")
|
||||||
|
testFile = os.path.join(outDir, "guiEditor_Main_Final_1a6562590ef19.nwd")
|
||||||
|
compFile = os.path.join(refDir, "guiEditor_Main_Final_1a6562590ef19.nwd")
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, compFile)
|
||||||
|
|
||||||
|
projFile = os.path.join(fncProj, "content", "0e17daca5f3e1.nwd")
|
||||||
|
testFile = os.path.join(outDir, "guiEditor_Main_Final_0e17daca5f3e1.nwd")
|
||||||
|
compFile = os.path.join(refDir, "guiEditor_Main_Final_0e17daca5f3e1.nwd")
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, compFile)
|
||||||
|
|
||||||
|
projFile = os.path.join(fncProj, "content", "41cfc0d1f2d12.nwd")
|
||||||
|
testFile = os.path.join(outDir, "guiEditor_Main_Final_41cfc0d1f2d12.nwd")
|
||||||
|
compFile = os.path.join(refDir, "guiEditor_Main_Final_41cfc0d1f2d12.nwd")
|
||||||
|
copyfile(projFile, testFile)
|
||||||
|
assert cmpFiles(testFile, compFile)
|
||||||
|
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
|
|
||||||
|
# END Test testGuiMain_Editing
|
||||||
Reference in New Issue
Block a user