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:
Veronica Berglyd Olsen
2021-07-05 16:10:52 +02:00
committed by GitHub
parent 1d16cc2a03
commit 36be7b0b33
7 changed files with 1892 additions and 657 deletions
+349 -331
View File
@@ -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}")
) )
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
## ##
+59
View File
@@ -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
File diff suppressed because it is too large Load Diff
+354
View File
@@ -0,0 +1,354 @@
# -*- coding: utf-8 -*-
"""
novelWriter Main GUI Editor Class Tester
==========================================
This file is a part of novelWriter
Copyright 20182021, 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