Improve editor stats handling and test coverage (#1725)

This commit is contained in:
Veronica Berglyd Olsen
2024-03-03 17:20:00 +01:00
committed by GitHub
5 changed files with 313 additions and 297 deletions
+6 -6
View File
@@ -351,16 +351,16 @@ class NWTree:
return False return False
return tItem.itemType == itemType return tItem.itemType == itemType
def getItemPath(self, tHandle: str) -> list[str]: def getItemPath(self, tHandle: str, asName: bool = False) -> list[str]:
"""Iterate upwards in the tree until we find the item with """Iterate upwards in the tree until we find the item with
parent None, the root item, and return the list of handles. parent None, the root item, and return the list of handles, or
We do this with a for loop with a maximum depth to make alternatively item names. We do this with a for loop with a
infinite loops impossible. maximum depth to make infinite loops impossible.
""" """
tTree = [] tTree = []
tItem = self.__getitem__(tHandle) tItem = self.__getitem__(tHandle)
if tItem is not None: if tItem is not None:
tTree.append(tHandle) tTree.append(tItem.itemName if asName else tHandle)
for _ in range(MAX_DEPTH): for _ in range(MAX_DEPTH):
if tItem.itemParent is None: if tItem.itemParent is None:
return tTree return tTree
@@ -370,7 +370,7 @@ class NWTree:
if tItem is None: if tItem is None:
return tTree return tTree
else: else:
tTree.append(tHandle) tTree.append(tItem.itemName if asName else tHandle)
else: else:
raise RecursionError("Critical internal error") raise RecursionError("Critical internal error")
+97 -165
View File
@@ -142,9 +142,10 @@ class GuiDocEditor(QPlainTextEdit):
self._qDocument = GuiTextDocument(self) self._qDocument = GuiTextDocument(self)
self.setDocument(self._qDocument) self.setDocument(self._qDocument)
# Connect Signals # Connect Editor and Document Signals
self._qDocument.contentsChange.connect(self._docChange) self._qDocument.contentsChange.connect(self._docChange)
self.selectionChanged.connect(self._updateSelectedStatus) self.selectionChanged.connect(self._updateSelectedStatus)
self.cursorPositionChanged.connect(self._cursorMoved)
self.spellCheckStateChanged.connect(self._qDocument.setSpellCheckState) self.spellCheckStateChanged.connect(self._qDocument.setSpellCheckState)
# Document Title # Document Title
@@ -153,7 +154,7 @@ class GuiDocEditor(QPlainTextEdit):
self.docSearch = GuiDocEditSearch(self) self.docSearch = GuiDocEditSearch(self)
self.docToolBar = GuiDocToolBar(self) self.docToolBar = GuiDocToolBar(self)
# Connect Signals # Connect Widget Signals
self.docHeader.closeDocumentRequest.connect(self._closeCurrentDocument) self.docHeader.closeDocumentRequest.connect(self._closeCurrentDocument)
self.docHeader.toggleToolBarRequest.connect(self._toggleToolBarVisibility) self.docHeader.toggleToolBarRequest.connect(self._toggleToolBarVisibility)
self.docToolBar.requestDocAction.connect(self.docAction) self.docToolBar.requestDocAction.connect(self.docAction)
@@ -400,8 +401,6 @@ class GuiDocEditor(QPlainTextEdit):
self.wcTimerDoc.start() self.wcTimerDoc.start()
self.setReadOnly(False) self.setReadOnly(False)
self.docHeader.setTitleFromHandle(self._docHandle)
self.docFooter.setHandle(self._docHandle)
self.updateDocMargins() self.updateDocMargins()
if tLine is None and self._nwItem is not None: if tLine is None and self._nwItem is not None:
@@ -409,7 +408,8 @@ class GuiDocEditor(QPlainTextEdit):
elif isinstance(tLine, int): elif isinstance(tLine, int):
self.setCursorLine(tLine) self.setCursorLine(tLine)
self.docFooter.updateLineCount() self.docHeader.setTitleFromHandle(self._docHandle)
self.docFooter.setHandle(self._docHandle)
# This is a hack to fix invisible cursor on an empty document # This is a hack to fix invisible cursor on an empty document
if self._qDocument.characterCount() <= 1: if self._qDocument.characterCount() <= 1:
@@ -616,7 +616,6 @@ class GuiDocEditor(QPlainTextEdit):
cursor.setPosition(minmax(position, 0, nChars-1)) cursor.setPosition(minmax(position, 0, nChars-1))
self.setTextCursor(cursor) self.setTextCursor(cursor)
self.centerCursor() self.centerCursor()
self.docFooter.updateLineCount()
return return
def saveCursorPosition(self) -> None: def saveCursorPosition(self) -> None:
@@ -949,8 +948,6 @@ class GuiDocEditor(QPlainTextEdit):
else: else:
super().keyPressEvent(event) super().keyPressEvent(event)
self.docFooter.updateLineCount()
return return
def focusNextPrevChild(self, next: bool) -> bool: def focusNextPrevChild(self, next: bool) -> bool:
@@ -973,7 +970,6 @@ class GuiDocEditor(QPlainTextEdit):
if qApp.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier: if qApp.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier:
self._processTag(self.cursorForPosition(event.pos())) self._processTag(self.cursorForPosition(event.pos()))
super().mouseReleaseEvent(event) super().mouseReleaseEvent(event)
self.docFooter.updateLineCount()
return return
def resizeEvent(self, event: QResizeEvent) -> None: def resizeEvent(self, event: QResizeEvent) -> None:
@@ -1038,29 +1034,32 @@ class GuiDocEditor(QPlainTextEdit):
if not self.wcTimerDoc.isActive(): if not self.wcTimerDoc.isActive():
self.wcTimerDoc.start() self.wcTimerDoc.start()
block = self._qDocument.findBlock(pos) if (block := self._qDocument.findBlock(pos)).isValid():
if not block.isValid(): text = block.text()
return if text.startswith("@") and added + removed == 1:
# Only run on single keypresses, otherwise it will trigger
text = block.text() # at unwanted times when other changes are made to the document
if text.startswith("@") and added + removed == 1: cursor = self.textCursor()
# Only run on single keypresses, otherwise it will trigger bPos = cursor.positionInBlock()
# at unwanted times when other changes are made to the document if bPos > 0:
cursor = self.textCursor() show = self._completer.updateText(text, bPos)
bPos = cursor.positionInBlock() point = self.cursorRect().bottomRight()
if bPos > 0: self._completer.move(self.viewport().mapToGlobal(point))
show = self._completer.updateText(text, bPos) self._completer.setVisible(show)
point = self.cursorRect().bottomRight() else:
self._completer.move(self.viewport().mapToGlobal(point)) self._completer.setVisible(False)
self._completer.setVisible(show)
else:
self._completer.setVisible(False)
if self._doReplace and added == 1: if self._doReplace and added == 1:
self._docAutoReplace(text) self._docAutoReplace(text)
return return
@pyqtSlot()
def _cursorMoved(self):
"""Triggered when the cursor moved in the editor."""
self.docFooter.updateLineCount(self.textCursor())
return
@pyqtSlot(int, int, str) @pyqtSlot(int, int, str)
def _insertCompletion(self, pos: int, length: int, text: str) -> None: def _insertCompletion(self, pos: int, length: int, text: str) -> None:
"""Insert choice from the completer menu.""" """Insert choice from the completer menu."""
@@ -1198,19 +1197,13 @@ class GuiDocEditor(QPlainTextEdit):
@pyqtSlot(int, int, int) @pyqtSlot(int, int, int)
def _updateDocCounts(self, cCount: int, wCount: int, pCount: int) -> None: def _updateDocCounts(self, cCount: int, wCount: int, pCount: int) -> None:
"""Process the word counter's finished signal.""" """Process the word counter's finished signal."""
if self._docHandle is None or self._nwItem is None: if self._docHandle and self._nwItem:
return logger.debug("Updating word count")
self._nwItem.setCharCount(cCount)
logger.debug("Updating word count") self._nwItem.setWordCount(wCount)
self._nwItem.setParaCount(pCount)
self._nwItem.setCharCount(cCount) self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
self._nwItem.setWordCount(wCount) self.docFooter.updateWordCount(wCount, False)
self._nwItem.setParaCount(pCount)
# Must not be emitted if docHandle is None!
self.docCountsChanged.emit(self._docHandle, cCount, wCount, pCount)
self.docFooter.updateCounts()
return return
@pyqtSlot() @pyqtSlot()
@@ -1221,11 +1214,9 @@ class GuiDocEditor(QPlainTextEdit):
if self.textCursor().hasSelection(): if self.textCursor().hasSelection():
if not self.wcTimerSel.isActive(): if not self.wcTimerSel.isActive():
self.wcTimerSel.start() self.wcTimerSel.start()
self.docFooter.setHasSelection(True)
else: else:
self.wcTimerSel.stop() self.wcTimerSel.stop()
self.docFooter.setHasSelection(False) self.docFooter.updateWordCount(0, False)
self.docFooter.updateCounts()
return return
@pyqtSlot() @pyqtSlot()
@@ -1245,13 +1236,10 @@ class GuiDocEditor(QPlainTextEdit):
@pyqtSlot(int, int, int) @pyqtSlot(int, int, int)
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None: def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
"""Update the counts on the counter's finished signal.""" """Update the counts on the counter's finished signal."""
if self._docHandle is None or self._nwItem is None: if self._docHandle and self._nwItem:
return logger.debug("User selected %d words", wCount)
self.docFooter.updateWordCount(wCount, True)
logger.debug("User selected %d words", wCount) self.wcTimerSel.stop()
self.docFooter.updateCounts(wCount=wCount, cCount=cCount)
self.wcTimerSel.stop()
return return
@pyqtSlot() @pyqtSlot()
@@ -1341,7 +1329,6 @@ class GuiDocEditor(QPlainTextEdit):
cursor.setPosition(resE[resIdx], QTextCursor.MoveMode.KeepAnchor) cursor.setPosition(resE[resIdx], QTextCursor.MoveMode.KeepAnchor)
self.setTextCursor(cursor) self.setTextCursor(cursor)
self.docFooter.updateLineCount()
self.docSearch.setResultCount(resIdx + 1, len(resS)) self.docSearch.setResultCount(resIdx + 1, len(resS))
self._lastFind = (resS[resIdx], resE[resIdx]) self._lastFind = (resS[resIdx], resE[resIdx])
@@ -1438,13 +1425,7 @@ class GuiDocEditor(QPlainTextEdit):
# Make sure the selected text was selected by an actual find # Make sure the selected text was selected by an actual find
# call, and not the user. # call, and not the user.
try: if self._lastFind == (cursor.selectionStart(), cursor.selectionEnd()):
isFind = self._lastFind[0] == cursor.selectionStart()
isFind &= self._lastFind[1] == cursor.selectionEnd()
except Exception:
isFind = False
if isFind:
cursor.beginEditBlock() cursor.beginEditBlock()
cursor.removeSelectedText() cursor.removeSelectedText()
cursor.insertText(replWith) cursor.insertText(replWith)
@@ -1453,10 +1434,8 @@ class GuiDocEditor(QPlainTextEdit):
self.setTextCursor(cursor) self.setTextCursor(cursor)
logger.debug( logger.debug(
"Replaced occurrence of '%s' with '%s' on line %d", "Replaced occurrence of '%s' with '%s' on line %d",
searchFor, replWith, cursor.blockNumber() searchFor, replWith, cursor.blockNumber() + 1
) )
else:
logger.error("The selected text is not a search result, skipping replace")
self.findNext() self.findNext()
@@ -2817,6 +2796,7 @@ class GuiDocEditHeader(QWidget):
self._docHandle = None self._docHandle = None
fPx = int(0.9*SHARED.theme.fontPixelSize) fPx = int(0.9*SHARED.theme.fontPixelSize)
mPx = CONFIG.pxInt(8)
hSp = CONFIG.pxInt(6) hSp = CONFIG.pxInt(6)
iconSize = QSize(fPx, fPx) iconSize = QSize(fPx, fPx)
@@ -2824,8 +2804,7 @@ class GuiDocEditHeader(QWidget):
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
# Title Label # Title Label
self.itemTitle = QLabel() self.itemTitle = QLabel("", self)
self.itemTitle.setText("")
self.itemTitle.setIndent(0) self.itemTitle.setIndent(0)
self.itemTitle.setMargin(0) self.itemTitle.setMargin(0)
self.itemTitle.setContentsMargins(0, 0, 0, 0) self.itemTitle.setContentsMargins(0, 0, 0, 0)
@@ -2882,14 +2861,14 @@ class GuiDocEditHeader(QWidget):
self.outerBox.addWidget(self.itemTitle, 1) self.outerBox.addWidget(self.itemTitle, 1)
self.outerBox.addWidget(self.minmaxButton, 0) self.outerBox.addWidget(self.minmaxButton, 0)
self.outerBox.addWidget(self.closeButton, 0) self.outerBox.addWidget(self.closeButton, 0)
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
# Fix Margins and Size # Fix Margins and Size
# This is needed for high DPI systems. See issue #499. # This is needed for high DPI systems. See issue #499.
cM = CONFIG.pxInt(8)
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self.outerBox.setContentsMargins(cM, cM, cM, cM) self.setMinimumHeight(fPx + 2*mPx)
self.setMinimumHeight(fPx + 2*cM)
self.updateTheme() self.updateTheme()
@@ -2937,7 +2916,7 @@ class GuiDocEditHeader(QWidget):
return return
def setTitleFromHandle(self, tHandle: str | None) -> bool: def setTitleFromHandle(self, tHandle: str | None) -> None:
"""Set the document title from the handle, or alternatively, set """Set the document title from the handle, or alternatively, set
the whole document path within the project. the whole document path within the project.
""" """
@@ -2948,30 +2927,21 @@ class GuiDocEditHeader(QWidget):
self.searchButton.setVisible(False) self.searchButton.setVisible(False)
self.closeButton.setVisible(False) self.closeButton.setVisible(False)
self.minmaxButton.setVisible(False) self.minmaxButton.setVisible(False)
return True return
pTree = SHARED.project.tree
if CONFIG.showFullPath: if CONFIG.showFullPath:
tTitle = [] self.itemTitle.setText(f" {nwUnicode.U_RSAQUO} ".join(reversed(
tTree = pTree.getItemPath(tHandle) [name for name in SHARED.project.tree.getItemPath(tHandle, asName=True)]
for aHandle in reversed(tTree): )))
nwItem = pTree[aHandle]
if nwItem is not None:
tTitle.append(nwItem.itemName)
sSep = " %s " % nwUnicode.U_RSAQUO
self.itemTitle.setText(sSep.join(tTitle))
else: else:
nwItem = pTree[tHandle] self.itemTitle.setText(i.itemName if (i := SHARED.project.tree[tHandle]) else "")
if nwItem is None:
return False
self.itemTitle.setText(nwItem.itemName)
self.tbButton.setVisible(True) self.tbButton.setVisible(True)
self.searchButton.setVisible(True) self.searchButton.setVisible(True)
self.closeButton.setVisible(True) self.closeButton.setVisible(True)
self.minmaxButton.setVisible(True) self.minmaxButton.setVisible(True)
return True return
def updateFocusMode(self) -> None: def updateFocusMode(self) -> None:
"""Update the minimise/maximise icon of the Focus Mode button. """Update the minimise/maximise icon of the Focus Mode button.
@@ -3007,7 +2977,7 @@ class GuiDocEditHeader(QWidget):
selected in the project tree. selected in the project tree.
""" """
if event.button() == Qt.MouseButton.LeftButton: if event.button() == Qt.MouseButton.LeftButton:
self.docEditor.requestProjectItemSelected.emit(self._docHandle, True) self.docEditor.requestProjectItemSelected.emit(self._docHandle or "", True)
return return
# END Class GuiDocEditHeader # END Class GuiDocEditHeader
@@ -3020,26 +2990,28 @@ class GuiDocEditHeader(QWidget):
class GuiDocEditFooter(QWidget): class GuiDocEditFooter(QWidget):
def __init__(self, docEditor: GuiDocEditor) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=docEditor) super().__init__(parent=parent)
logger.debug("Create: GuiDocEditFooter") logger.debug("Create: GuiDocEditFooter")
self.docEditor = docEditor self._tItem = None
self._tItem = None
self._docHandle = None self._docHandle = None
self._docSelection = False iPx = round(0.9*SHARED.theme.baseIconSize)
self.sPx = int(round(0.9*SHARED.theme.baseIconSize))
fPx = int(0.9*SHARED.theme.fontPixelSize) fPx = int(0.9*SHARED.theme.fontPixelSize)
mPx = CONFIG.pxInt(8)
bSp = CONFIG.pxInt(4) bSp = CONFIG.pxInt(4)
hSp = CONFIG.pxInt(6) hSp = CONFIG.pxInt(6)
lblFont = self.font() lblFont = self.font()
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize) lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
# Cached Translations
self._trLineCount = self.tr("Line: {0} ({1})")
self._trWordCount = self.tr("Words: {0} ({1})")
self._trSelectCount = self.tr("Words: {0} selected")
# Main Widget Settings # Main Widget Settings
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
@@ -3047,9 +3019,9 @@ class GuiDocEditFooter(QWidget):
alLeftTop = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop alLeftTop = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
# Status # Status
self.statusIcon = QLabel("") self.statusIcon = QLabel("", self)
self.statusIcon.setContentsMargins(0, 0, 0, 0) self.statusIcon.setContentsMargins(0, 0, 0, 0)
self.statusIcon.setFixedHeight(self.sPx) self.statusIcon.setFixedHeight(iPx)
self.statusIcon.setAlignment(alLeftTop) self.statusIcon.setAlignment(alLeftTop)
self.statusText = QLabel(self.tr("Status")) self.statusText = QLabel(self.tr("Status"))
@@ -3062,12 +3034,12 @@ class GuiDocEditFooter(QWidget):
self.statusText.setFont(lblFont) self.statusText.setFont(lblFont)
# Lines # Lines
self.linesIcon = QLabel("") self.linesIcon = QLabel("", self)
self.linesIcon.setContentsMargins(0, 0, 0, 0) self.linesIcon.setContentsMargins(0, 0, 0, 0)
self.linesIcon.setFixedHeight(self.sPx) self.linesIcon.setFixedHeight(iPx)
self.linesIcon.setAlignment(alLeftTop) self.linesIcon.setAlignment(alLeftTop)
self.linesText = QLabel("") self.linesText = QLabel("", self)
self.linesText.setIndent(0) self.linesText.setIndent(0)
self.linesText.setMargin(0) self.linesText.setMargin(0)
self.linesText.setContentsMargins(0, 0, 0, 0) self.linesText.setContentsMargins(0, 0, 0, 0)
@@ -3077,12 +3049,12 @@ class GuiDocEditFooter(QWidget):
self.linesText.setFont(lblFont) self.linesText.setFont(lblFont)
# Words # Words
self.wordsIcon = QLabel("") self.wordsIcon = QLabel("", self)
self.wordsIcon.setContentsMargins(0, 0, 0, 0) self.wordsIcon.setContentsMargins(0, 0, 0, 0)
self.wordsIcon.setFixedHeight(self.sPx) self.wordsIcon.setFixedHeight(iPx)
self.wordsIcon.setAlignment(alLeftTop) self.wordsIcon.setAlignment(alLeftTop)
self.wordsText = QLabel("") self.wordsText = QLabel("", self)
self.wordsText.setIndent(0) self.wordsText.setIndent(0)
self.wordsText.setMargin(0) self.wordsText.setMargin(0)
self.wordsText.setContentsMargins(0, 0, 0, 0) self.wordsText.setContentsMargins(0, 0, 0, 0)
@@ -3102,19 +3074,20 @@ class GuiDocEditFooter(QWidget):
self.outerBox.addSpacing(hSp) self.outerBox.addSpacing(hSp)
self.outerBox.addWidget(self.wordsIcon) self.outerBox.addWidget(self.wordsIcon)
self.outerBox.addWidget(self.wordsText) self.outerBox.addWidget(self.wordsText)
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
# Fix Margins and Size # Fix Margins and Size
# This is needed for high DPI systems. See issue #499. # This is needed for high DPI systems. See issue #499.
cM = CONFIG.pxInt(8)
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self.outerBox.setContentsMargins(cM, cM, cM, cM) self.setMinimumHeight(fPx + 2*mPx)
self.setMinimumHeight(fPx + 2*cM)
# Fix the Colours # Fix the Colours
self.updateTheme() self.updateTheme()
self.updateLineCount()
self.updateCounts() # Initialise Info
self.updateWordCount(0, False)
logger.debug("Ready: GuiDocEditFooter") logger.debug("Ready: GuiDocEditFooter")
@@ -3126,8 +3099,9 @@ class GuiDocEditFooter(QWidget):
def updateTheme(self) -> None: def updateTheme(self) -> None:
"""Update theme elements.""" """Update theme elements."""
self.linesIcon.setPixmap(SHARED.theme.getPixmap("status_lines", (self.sPx, self.sPx))) iPx = round(0.9*SHARED.theme.baseIconSize)
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (self.sPx, self.sPx))) self.linesIcon.setPixmap(SHARED.theme.getPixmap("status_lines", (iPx, iPx)))
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("status_stats", (iPx, iPx)))
self.matchColours() self.matchColours()
return return
@@ -3156,27 +3130,20 @@ class GuiDocEditFooter(QWidget):
else: else:
self._tItem = SHARED.project.tree[self._docHandle] self._tItem = SHARED.project.tree[self._docHandle]
self.setHasSelection(False)
self.updateInfo() self.updateInfo()
self.updateCounts() self.updateWordCount(0, False)
return return
def setHasSelection(self, hasSelection: bool) -> None:
"""Toggle the word counter mode between full count and selection
count mode.
"""
self._docSelection = hasSelection
return
def updateInfo(self) -> None: def updateInfo(self) -> None:
"""Update the content of text labels.""" """Update the content of text labels."""
if self._tItem is None: if self._tItem is None:
sIcon = QPixmap() sIcon = QPixmap()
sText = "" sText = ""
else: else:
iPx = round(0.9*SHARED.theme.baseIconSize)
status, icon = self._tItem.getImportStatus(incIcon=True) status, icon = self._tItem.getImportStatus(incIcon=True)
sIcon = icon.pixmap(self.sPx, self.sPx) sIcon = icon.pixmap(iPx, iPx)
sText = f"{status} / {self._tItem.describeMe()}" sText = f"{status} / {self._tItem.describeMe()}"
self.statusIcon.setPixmap(sIcon) self.statusIcon.setPixmap(sIcon)
@@ -3184,62 +3151,27 @@ class GuiDocEditFooter(QWidget):
return return
def updateLineCount(self) -> None: def updateLineCount(self, cursor: QTextCursor) -> None:
"""Update the line counter.""" """Update the line and document position counter."""
if self._tItem is None: cPos = cursor.position() + 1
iLine = 0 cLine = cursor.blockNumber() + 1
iDist = 0 cCount = max(cursor.document().characterCount(), 1)
else:
cursor = self.docEditor.textCursor()
iLine = cursor.blockNumber() + 1
iDist = 100*iLine/self.docEditor._qDocument.blockCount()
self.linesText.setText( self.linesText.setText(
self.tr("Line: {0} ({1})").format(f"{iLine:n}", f"{iDist:.0f} %") self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
) )
return return
def updateCounts(self, wCount: int | None = None, cCount: int | None = None) -> None: def updateWordCount(self, wCount: int, selection: bool) -> None:
"""Select which word count display mode to use.""" """Update word counter information."""
if self._docSelection: if selection and wCount:
self._updateSelectionWordCounts(wCount, cCount) wText = self._trSelectCount.format(f"{wCount:n}")
else: elif self._tItem:
self._updateWordCounts()
return
##
# Internal Functions
##
def _updateWordCounts(self) -> None:
"""Update the word count for the whole document."""
if self._tItem is None:
wCount = 0
wDiff = 0
else:
wCount = self._tItem.wordCount wCount = self._tItem.wordCount
wDiff = wCount - self._tItem.initCount wDiff = wCount - self._tItem.initCount
wText = self._trWordCount.format(f"{wCount:n}", f"{wDiff:+n}")
self.wordsText.setText( else:
self.tr("Words: {0} ({1})").format(f"{wCount:n}", f"{wDiff:+n}") wText = self._trWordCount.format("0", "+0")
) self.wordsText.setText(wText)
byteSize = self.docEditor._qDocument.characterCount()
self.wordsText.setToolTip(
self.tr("Document size is {0} bytes").format(f"{byteSize:n}")
)
return
def _updateSelectionWordCounts(self, wCount: int | None, cCount: int | None) -> None:
"""Update the word count for a selection."""
if wCount is None or cCount is None:
return
self.wordsText.setText(
self.tr("Words: {0} selected").format(f"{wCount:n}")
)
self.wordsText.setToolTip(
self.tr("Character count: {0}").format(f"{cCount:n}")
)
return return
# END Class GuiDocEditFooter # END Class GuiDocEditFooter
+4 -11
View File
@@ -763,19 +763,12 @@ class GuiDocViewHeader(QWidget):
self.refreshButton.setVisible(False) self.refreshButton.setVisible(False)
return return
pTree = SHARED.project.tree
if CONFIG.showFullPath: if CONFIG.showFullPath:
tTitle = [] self.docTitle.setText(f" {nwUnicode.U_RSAQUO} ".join(reversed(
tTree = pTree.getItemPath(tHandle) [name for name in SHARED.project.tree.getItemPath(tHandle, asName=True)]
for aHandle in reversed(tTree): )))
nwItem = pTree[aHandle]
if nwItem is not None:
tTitle.append(nwItem.itemName)
sSep = " %s " % nwUnicode.U_RSAQUO
self.docTitle.setText(sSep.join(tTitle))
else: else:
if nwItem := pTree[tHandle]: self.docTitle.setText(i.itemName if (i := SHARED.project.tree[tHandle]) else "")
self.docTitle.setText(nwItem.itemName)
self.backButton.setVisible(True) self.backButton.setVisible(True)
self.forwardButton.setVisible(True) self.forwardButton.setVisible(True)
+3
View File
@@ -421,6 +421,9 @@ def testCoreTree_Methods(monkeypatch, mockGUI, mockItems):
assert tree.getItemPath("c000000000001") == [ assert tree.getItemPath("c000000000001") == [
"c000000000001", "b000000000001", "a000000000001" "c000000000001", "b000000000001", "a000000000001"
] ]
assert tree.getItemPath("c000000000001", asName=True) == [
"Chapter One", "Act One", "Novel"
]
# Cause recursion error # Cause recursion error
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
+203 -115
View File
@@ -55,6 +55,9 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.docEditor.verticalScrollBarPolicy() == Qt.ScrollBarAsNeeded assert nwGUI.docEditor.verticalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert nwGUI.docEditor.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded assert nwGUI.docEditor.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert nwGUI.docEditor._typPadChar == nwUnicode.U_NBSP assert nwGUI.docEditor._typPadChar == nwUnicode.U_NBSP
assert nwGUI.docEditor.docHeader.itemTitle.text() == (
"Novel \u203a New Chapter \u203a New Scene"
)
# Check that editor handles settings # Check that editor handles settings
CONFIG.textFont = "" CONFIG.textFont = ""
@@ -64,6 +67,7 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
CONFIG.hideVScroll = True CONFIG.hideVScroll = True
CONFIG.hideHScroll = True CONFIG.hideHScroll = True
CONFIG.fmtPadThin = True CONFIG.fmtPadThin = True
CONFIG.showFullPath = False
nwGUI.docEditor.initEditor() nwGUI.docEditor.initEditor()
@@ -75,6 +79,29 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
assert nwGUI.docEditor.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff assert nwGUI.docEditor.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert nwGUI.docEditor.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff assert nwGUI.docEditor.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert nwGUI.docEditor._typPadChar == nwUnicode.U_THNBSP assert nwGUI.docEditor._typPadChar == nwUnicode.U_THNBSP
assert nwGUI.docEditor.docHeader.itemTitle.text() == "New Scene"
# Header
# ======
# Select item from header
with qtbot.waitSignal(nwGUI.docEditor.requestProjectItemSelected, timeout=1000) as signal:
qtbot.mouseClick(nwGUI.docEditor.docHeader, Qt.MouseButton.LeftButton)
assert signal.args == [nwGUI.docEditor.docHeader._docHandle, True]
# Close from header
with qtbot.waitSignal(nwGUI.docEditor.docHeader.closeDocumentRequest, timeout=1000):
nwGUI.docEditor.docHeader.closeButton.click()
assert nwGUI.docEditor.docHeader.tbButton.isVisible() is False
assert nwGUI.docEditor.docHeader.searchButton.isVisible() is False
assert nwGUI.docEditor.docHeader.closeButton.isVisible() is False
assert nwGUI.docEditor.docHeader.minmaxButton.isVisible() is False
# Select item from header
with qtbot.waitSignal(nwGUI.docEditor.requestProjectItemSelected, timeout=1000) as signal:
qtbot.mouseClick(nwGUI.docEditor.docHeader, Qt.MouseButton.LeftButton)
assert signal.args == ["", True]
# qtbot.stop() # qtbot.stop()
@@ -82,7 +109,7 @@ def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
@pytest.mark.gui @pytest.mark.gui
def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumText, mockRnd): def testGuiEditor_LoadText(qtbot, nwGUI, projPath, ipsumText, mockRnd):
"""Test loading text into the editor.""" """Test loading text into the editor."""
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
@@ -92,9 +119,6 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex
assert nwGUI.saveDocument() is True assert nwGUI.saveDocument() is True
assert nwGUI.closeDocument() is True assert nwGUI.closeDocument() is True
# Load Text
# =========
# Invalid handle # Invalid handle
assert nwGUI.docEditor.loadText("abcdefghijklm") is False assert nwGUI.docEditor.loadText("abcdefghijklm") is False
@@ -123,9 +147,6 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
# Save Text
# =========
longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText) longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText)
nwGUI.docEditor.replaceText(longText) nwGUI.docEditor.replaceText(longText)
@@ -1698,17 +1719,12 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
assert SHARED.project.tree[C.hSceneDoc]._paraCount == pC # type: ignore assert SHARED.project.tree[C.hSceneDoc]._paraCount == pC # type: ignore
assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} (+{wC})" assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} (+{wC})"
# Select all text # Select all text and run the selection word counter
assert nwGUI.docEditor.docFooter._docSelection is False
nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) nwGUI.docEditor.docAction(nwDocAction.SEL_ALL)
assert nwGUI.docEditor.docFooter._docSelection is True
# Run the selection word counter
nwGUI.docEditor._runSelCounter() nwGUI.docEditor._runSelCounter()
assert threadPool.objectID() == id(nwGUI.docEditor.wCounterSel) assert threadPool.objectID() == id(nwGUI.docEditor.wCounterSel)
nwGUI.docEditor.wCounterSel.run() nwGUI.docEditor.wCounterSel.run()
# nwGUI.docEditor._updateSelCounts(cC, wC, pC)
assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} selected" assert nwGUI.docEditor.docFooter.wordsText.text() == f"Words: {wC} selected"
# qtbot.stop() # qtbot.stop()
@@ -1723,201 +1739,273 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
assert nwGUI.openProject(prjLipsum) is True assert nwGUI.openProject(prjLipsum) is True
assert nwGUI.openDocument("4c4f28287af27") is True assert nwGUI.openDocument("4c4f28287af27") is True
origText = nwGUI.docEditor.getText() docEditor = nwGUI.docEditor
docSearch = docEditor.docSearch
origText = docEditor.getText()
# Select the Word "est" # Select the Word "est"
nwGUI.docEditor.setCursorPosition(645) docEditor.setCursorPosition(645)
nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor) docEditor._makeSelection(QTextCursor.WordUnderCursor)
cursor = nwGUI.docEditor.textCursor() cursor = docEditor.textCursor()
assert cursor.selectedText() == "est" assert cursor.selectedText() == "est"
# Activate search # Activate search
nwGUI.mainMenu.aFind.activate(QAction.Trigger) nwGUI.mainMenu.aFind.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.isVisible() assert docSearch.isVisible()
assert nwGUI.docEditor.docSearch.searchText == "est" assert docSearch.searchText == "est"
# Find next by enter key # Find next by enter key
monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True) monkeypatch.setattr(docSearch.searchBox, "hasFocus", lambda: True)
qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=KEY_DELAY) qtbot.keyClick(docSearch.searchBox, Qt.Key_Return, delay=KEY_DELAY)
assert abs(nwGUI.docEditor.getCursorPosition() - 1299) < 3 assert abs(docEditor.getCursorPosition() - 1299) < 3
# Find next by button # Find next by button
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
assert abs(nwGUI.docEditor.getCursorPosition() - 1513) < 3 assert abs(docEditor.getCursorPosition() - 1513) < 3
# Activate loop search # Activate loop search
nwGUI.docEditor.docSearch.toggleLoop.activate(QAction.Trigger) docSearch.toggleLoop.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.toggleLoop.isChecked() assert docSearch.toggleLoop.isChecked()
assert nwGUI.docEditor.docSearch.doLoop is True assert docSearch.doLoop is True
# Find next by menu Search > Find Next # Find next by menu Search > Find Next
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 647) < 3 assert abs(docEditor.getCursorPosition() - 647) < 3
# Close search # Close search
nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger) docSearch.cancelSearch.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.isVisible() is False assert docSearch.isVisible() is False
nwGUI.docEditor.setCursorPosition(15) docEditor.setCursorPosition(15)
# Toggle search again with header button # Toggle search again with header button
qtbot.mouseClick(nwGUI.docEditor.docHeader.searchButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(docEditor.docHeader.searchButton, Qt.LeftButton, delay=KEY_DELAY)
nwGUI.docEditor.docSearch.setSearchText("") docSearch.setSearchText("")
assert nwGUI.docEditor.docSearch.isVisible() is True assert docSearch.isVisible() is True
# Search for non-existing # Search for non-existing
nwGUI.docEditor.setCursorPosition(0) docEditor.setCursorPosition(0)
nwGUI.docEditor.docSearch.setSearchText("abcdef") docSearch.setSearchText("abcdef")
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
assert nwGUI.docEditor.getCursorPosition() < 3 # No result assert docEditor.getCursorPosition() < 3 # No result
# Enable RegEx search # Enable RegEx search
nwGUI.docEditor.docSearch.toggleRegEx.activate(QAction.Trigger) docSearch.toggleRegEx.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.toggleRegEx.isChecked() assert docSearch.toggleRegEx.isChecked()
assert nwGUI.docEditor.docSearch.isRegEx is True assert docSearch.isRegEx is True
# Set invalid RegEx # Set invalid RegEx
nwGUI.docEditor.setCursorPosition(0) docEditor.setCursorPosition(0)
nwGUI.docEditor.docSearch.setSearchText(r"\bSus[") docSearch.setSearchText(r"\bSus[")
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
assert nwGUI.docEditor.getCursorPosition() < 3 # No result assert docEditor.getCursorPosition() < 3 # No result
# Set dangerous RegEx (issue #1015) # Set dangerous RegEx (issue #1015)
# If this doesn't get caught, the app will hang # If this doesn't get caught, the app will hang
nwGUI.docEditor.setCursorPosition(0) docEditor.setCursorPosition(0)
nwGUI.docEditor.docSearch.setSearchText(r".*") docSearch.setSearchText(r".*")
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
assert abs(nwGUI.docEditor.getCursorPosition() - 14) < 3 assert abs(docEditor.getCursorPosition() - 14) < 3
# Set valid RegEx # Set valid RegEx
nwGUI.docEditor.docSearch.setSearchText(r"\bSus") docSearch.setSearchText(r"\bSus")
qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(docSearch.searchButton, Qt.LeftButton, delay=KEY_DELAY)
assert abs(nwGUI.docEditor.getCursorPosition() - 223) < 3 assert abs(docEditor.getCursorPosition() - 223) < 3
# Find next and then prev # Find next and then prev
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 324) < 3 assert abs(docEditor.getCursorPosition() - 324) < 3
nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger) nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 223) < 3 assert abs(docEditor.getCursorPosition() - 223) < 3
# Make RegEx case sensitive # Make RegEx case sensitive
nwGUI.docEditor.docSearch.toggleCase.activate(QAction.Trigger) docSearch.toggleCase.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.toggleCase.isChecked() assert docSearch.toggleCase.isChecked()
assert nwGUI.docEditor.docSearch.isCaseSense is True assert docSearch.isCaseSense is True
# Find next/prev (one result) # Find next/prev (one result)
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 626) < 3 assert abs(docEditor.getCursorPosition() - 626) < 3
nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger) nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 626) < 3 assert abs(docEditor.getCursorPosition() - 626) < 3
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 626) < 3 assert abs(docEditor.getCursorPosition() - 626) < 3
# Trigger replace # Trigger replace
nwGUI.mainMenu.aReplace.activate(QAction.Trigger) nwGUI.mainMenu.aReplace.activate(QAction.Trigger)
nwGUI.docEditor.docSearch.setReplaceText("foo") docSearch.setReplaceText("foo")
# Disable RegEx case sensitive # Disable RegEx case sensitive
nwGUI.docEditor.docSearch.toggleCase.activate(QAction.Trigger) docSearch.toggleCase.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.toggleCase.isChecked() is False assert docSearch.toggleCase.isChecked() is False
assert nwGUI.docEditor.docSearch.isCaseSense is False assert docSearch.isCaseSense is False
# Toggle replace preserve case # Toggle replace preserve case
nwGUI.docEditor.docSearch.toggleMatchCap.activate(QAction.Trigger) docSearch.toggleMatchCap.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.toggleMatchCap.isChecked() assert docSearch.toggleMatchCap.isChecked()
assert nwGUI.docEditor.docSearch.doMatchCap is True assert docSearch.doMatchCap is True
# Replace "Sus" with "Foo" via menu # Replace "Sus" with "Foo" via menu
nwGUI.docEditor.setCursorPosition(605) docEditor.setCursorPosition(605)
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
nwGUI.mainMenu.aReplaceNext.activate(QAction.Trigger) nwGUI.mainMenu.aReplaceNext.activate(QAction.Trigger)
assert nwGUI.docEditor.getText()[623:634] == "Foopendisse" assert docEditor.getText()[623:634] == "Foopendisse"
# Find next/prev to loop file # Find next/prev to loop file
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 223) < 3 assert abs(docEditor.getCursorPosition() - 223) < 3
nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger) nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 1805) < 3 assert abs(docEditor.getCursorPosition() - 1805) < 3
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 223) < 3 assert abs(docEditor.getCursorPosition() - 223) < 3
# Replace "sus" with "foo" via replace button # Replace "sus" with "foo" via replace button
qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=KEY_DELAY) qtbot.mouseClick(docSearch.replaceButton, Qt.LeftButton, delay=KEY_DELAY)
assert nwGUI.docEditor.getText()[220:228] == "foocipit" assert docEditor.getText()[220:228] == "foocipit"
# Revert last two replaces # Revert last two replaces
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) assert docEditor.docAction(nwDocAction.UNDO)
assert nwGUI.docEditor.docAction(nwDocAction.UNDO) assert docEditor.docAction(nwDocAction.UNDO)
assert nwGUI.docEditor.getText() == origText assert docEditor.getText() == origText
# Disable RegEx search # Disable RegEx search
nwGUI.docEditor.docSearch.toggleRegEx.activate(QAction.Trigger) docSearch.toggleRegEx.activate(QAction.Trigger)
assert not nwGUI.docEditor.docSearch.toggleRegEx.isChecked() assert not docSearch.toggleRegEx.isChecked()
assert nwGUI.docEditor.docSearch.isRegEx is False assert docSearch.isRegEx is False
# Close search and select "est" again # Close search and select "est" again
nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger) docSearch.cancelSearch.activate(QAction.Trigger)
nwGUI.docEditor.setCursorPosition(645) docEditor.setCursorPosition(645)
nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor) docEditor._makeSelection(QTextCursor.WordUnderCursor)
cursor = nwGUI.docEditor.textCursor() cursor = docEditor.textCursor()
assert cursor.selectedText() == "est" assert cursor.selectedText() == "est"
# Activate search again # Activate search again
nwGUI.mainMenu.aFind.activate(QAction.Trigger) nwGUI.mainMenu.aFind.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.isVisible() assert docSearch.isVisible()
assert nwGUI.docEditor.docSearch.searchText == "est" assert docSearch.searchText == "est"
# Enable full word search # Enable full word search
nwGUI.docEditor.docSearch.toggleWord.activate(QAction.Trigger) docSearch.toggleWord.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.toggleWord.isChecked() assert docSearch.toggleWord.isChecked()
assert nwGUI.docEditor.docSearch.isWholeWord is True assert docSearch.isWholeWord is True
# Only one match # Only one match
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 647) < 3 assert abs(docEditor.getCursorPosition() - 647) < 3
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 647) < 3 assert abs(docEditor.getCursorPosition() - 647) < 3
# Enable next doc search # Enable next doc search
nwGUI.docEditor.docSearch.toggleProject.activate(QAction.Trigger) docSearch.toggleProject.activate(QAction.Trigger)
assert nwGUI.docEditor.docSearch.toggleProject.isChecked() assert docSearch.toggleProject.isChecked()
assert nwGUI.docEditor.docSearch.doNextFile is True assert docSearch.doNextFile is True
# Next match # Next match
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert nwGUI.docEditor.docHandle == "2426c6f0ca922" # Next document assert docEditor.docHandle == "2426c6f0ca922" # Next document
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3 assert abs(docEditor.getCursorPosition() - 620) < 3
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert abs(nwGUI.docEditor.getCursorPosition() - 1127) < 3 assert abs(docEditor.getCursorPosition() - 1127) < 3
# Next doc, no match # Next doc, no match
assert nwGUI.docEditor.docSearch.doNextFile is True assert docSearch.doNextFile is True
nwGUI.docEditor.docSearch.setSearchText("abcdef") docSearch.setSearchText("abcdef")
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert nwGUI.docEditor.docHandle != "2426c6f0ca922" assert docEditor.docHandle != "2426c6f0ca922"
assert nwGUI.docEditor.docHandle == "04468803b92e1" assert docEditor.docHandle == "04468803b92e1"
nwGUI.mainMenu.aFindNext.activate(QAction.Trigger) nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
assert nwGUI.docEditor.docHandle != "04468803b92e1" assert docEditor.docHandle != "04468803b92e1"
assert nwGUI.docEditor.docHandle == "7a992350f3eb6" assert docEditor.docHandle == "7a992350f3eb6"
# Toggle Replace # Toggle Replace
nwGUI.docEditor.beginReplace() docEditor.beginReplace()
# MonkeyPatch the focus cycle. We can't really test this very well, other than # MonkeyPatch the focus cycle. We can't really test this very well, other than
# check that the tabs aren't captured when the main editor has focus # check that the tabs aren't captured when the main editor has focus
monkeypatch.setattr(nwGUI.docEditor, "hasFocus", lambda: True) with monkeypatch.context() as mp:
monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: False) mp.setattr(docEditor, "hasFocus", lambda: True)
monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: False) mp.setattr(docSearch.searchBox, "hasFocus", lambda: False)
assert nwGUI.docEditor.focusNextPrevChild(True) is False mp.setattr(docSearch.replaceBox, "hasFocus", lambda: False)
assert docEditor.focusNextPrevChild(True) is False
assert docSearch.cycleFocus(True) is False
monkeypatch.setattr(nwGUI.docEditor, "hasFocus", lambda: False) with monkeypatch.context() as mp:
monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True) mp.setattr(docEditor, "hasFocus", lambda: False)
monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: False) mp.setattr(docSearch.searchBox, "hasFocus", lambda: True)
assert nwGUI.docEditor.focusNextPrevChild(True) is True mp.setattr(docSearch.replaceBox, "hasFocus", lambda: False)
assert docEditor.focusNextPrevChild(True) is True
assert docSearch.cycleFocus(True) is True
monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: False) with monkeypatch.context() as mp:
monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: True) mp.setattr(docEditor, "hasFocus", lambda: False)
assert nwGUI.docEditor.focusNextPrevChild(True) is True mp.setattr(docSearch.searchBox, "hasFocus", lambda: False)
mp.setattr(docSearch.replaceBox, "hasFocus", lambda: True)
assert docEditor.focusNextPrevChild(True) is True
assert docSearch.cycleFocus(True) is True
docSearch.closeSearch()
assert docSearch.isVisible() is False
assert docEditor.focusNextPrevChild(True) is True
# Replace Text
# ============
docSearch.toggleCase.setChecked(True)
docSearch.toggleWord.setChecked(False)
docSearch.toggleRegEx.setChecked(False)
docSearch.toggleLoop.setChecked(False)
docSearch.toggleProject.setChecked(False)
docEditor.setCursorPosition(0)
# Replace Next
docSearch.searchBox.setText("a")
docSearch.replaceBox.setText("A")
# No focus
with monkeypatch.context() as mp:
mp.setattr(docEditor, "anyFocus", lambda: False)
docEditor.findNext()
assert docEditor.textCursor().selectedText() == ""
docEditor.replaceNext()
assert docEditor.textCursor().selectedText() == ""
# Search not open
docSearch.closeSearch()
assert docSearch.isVisible() is False
docEditor.findNext()
assert docSearch.isVisible() is True
docSearch.closeSearch()
assert docSearch.isVisible() is False
docEditor.replaceNext()
assert docSearch.isVisible() is True
docEditor.toggleSearch()
assert docSearch.isVisible() is False
docEditor.toggleSearch()
assert docSearch.isVisible() is True
# Find first entry
docEditor.replaceNext()
assert docEditor.textCursor().selectedText() == "a"
assert docEditor.getCursorPosition() == 64
# Treat the search as a user selection
docEditor._lastFind = None
docEditor.replaceNext()
assert docEditor.textCursor().selectedText() == "a"
assert docEditor.getCursorPosition() == 92
# Iterate through the rest
finds = [104, 123, 175, 197, 206, 211, 220, 238, 250, 250]
for i in range(10):
docEditor.replaceNext()
assert docEditor.textCursor().selectedText() == "a"
assert docEditor.getCursorPosition() == finds[i]
assert docEditor._lastFind == (249, 250)
# Search for something that doesn't exist
docSearch.searchBox.setText("x")
docEditor._lastFind = None
docEditor.replaceNext()
assert docEditor.textCursor().selectedText() == ""
# qtbot.stop() # qtbot.stop()