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