Improve editor stats handling and test coverage (#1725)
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user