Make some improvements to the doc editor code
This commit is contained in:
@@ -219,7 +219,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.changeFocusState = self.docHeader.changeFocusState
|
||||
|
||||
# Finalise
|
||||
self.updateSyntaxColours()
|
||||
self.updateSyntaxColors()
|
||||
self.initEditor()
|
||||
|
||||
logger.debug("Ready: GuiDocEditor")
|
||||
@@ -287,7 +287,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.docToolBar.updateTheme()
|
||||
return
|
||||
|
||||
def updateSyntaxColours(self) -> None:
|
||||
def updateSyntaxColors(self) -> None:
|
||||
"""Update the syntax highlighting theme."""
|
||||
syntax = SHARED.theme.syntaxTheme
|
||||
|
||||
@@ -304,7 +304,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
viewport.setPalette(palette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
self.docFooter.matchColours()
|
||||
self.docFooter.matchColors()
|
||||
|
||||
return
|
||||
|
||||
@@ -339,14 +339,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
# Also set the document text options for the document text flow
|
||||
options = QTextOption()
|
||||
|
||||
if CONFIG.doJustify:
|
||||
options.setAlignment(QtAlignJustify)
|
||||
if CONFIG.showTabsNSpaces:
|
||||
options.setFlags(options.flags() | QTextOption.Flag.ShowTabsAndSpaces)
|
||||
if CONFIG.showLineEndings:
|
||||
options.setFlags(options.flags() | QTextOption.Flag.ShowLineAndParagraphSeparators)
|
||||
|
||||
self._qDocument.setDefaultTextOption(options)
|
||||
|
||||
# Scrolling
|
||||
@@ -379,19 +377,19 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
"""Load text from a document into the editor. If we have an I/O
|
||||
error, we must handle this and clear the editor so that we don't
|
||||
risk overwriting the file if it exists. This can for instance
|
||||
happen of the file contains binary elements or an encoding that
|
||||
happen if the file contains binary elements or an encoding that
|
||||
novelWriter does not support. If loading is successful, or the
|
||||
document is new (empty string), we set up the editor for editing
|
||||
the file.
|
||||
"""
|
||||
self._nwDocument = SHARED.project.storage.getDocument(tHandle)
|
||||
self._nwItem = self._nwDocument.nwItem
|
||||
if not ((nwItem := self._nwItem) and nwItem.itemType == nwItemType.FILE):
|
||||
if not (self._nwItem and self._nwItem.itemType == nwItemType.FILE):
|
||||
logger.debug("Requested item '%s' is not a document", tHandle)
|
||||
self.clearEditor()
|
||||
return False
|
||||
|
||||
if (docText := self._nwDocument.readDocument()) is None:
|
||||
if (text := self._nwDocument.readDocument()) is None:
|
||||
# There was an I/O error
|
||||
self.clearEditor()
|
||||
return False
|
||||
@@ -400,7 +398,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self._docHandle = tHandle
|
||||
|
||||
self._allowAutoReplace(False)
|
||||
self._qDocument.setTextContent(docText, tHandle)
|
||||
self._qDocument.setTextContent(text, tHandle)
|
||||
self._allowAutoReplace(True)
|
||||
QApplication.processEvents()
|
||||
|
||||
@@ -415,7 +413,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
if isinstance(tLine, int):
|
||||
self.setCursorLine(tLine)
|
||||
else:
|
||||
self.setCursorPosition(nwItem.cursorPos)
|
||||
self.setCursorPosition(self._nwItem.cursorPos)
|
||||
|
||||
self.docHeader.setHandle(tHandle)
|
||||
self.docFooter.setHandle(tHandle)
|
||||
@@ -438,7 +436,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
# Finalise
|
||||
QApplication.restoreOverrideCursor()
|
||||
self.updateStatusMessage.emit(
|
||||
self.tr("Opened Document: {0}").format(nwItem.itemName)
|
||||
self.tr("Opened Document: {0}").format(self._nwItem.itemName)
|
||||
)
|
||||
|
||||
return True
|
||||
@@ -469,19 +467,17 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
)
|
||||
return False
|
||||
|
||||
docText = self.getText()
|
||||
cC, wC, pC = standardCounter(docText)
|
||||
text = self.getText()
|
||||
cC, wC, pC = standardCounter(text)
|
||||
self._updateDocCounts(cC, wC, pC)
|
||||
|
||||
if not self._nwDocument.writeDocument(docText):
|
||||
if not self._nwDocument.writeDocument(text):
|
||||
saveOk = False
|
||||
if self._nwDocument.hashError:
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"This document has been changed outside of novelWriter "
|
||||
"while it was open. Overwrite the file on disk?"
|
||||
))
|
||||
if msgYes:
|
||||
saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
|
||||
if self._nwDocument.hashError and SHARED.question(self.tr(
|
||||
"This document has been changed outside of novelWriter "
|
||||
"while it was open. Overwrite the file on disk?"
|
||||
)):
|
||||
saveOk = self._nwDocument.writeDocument(text, forceWrite=True)
|
||||
|
||||
if not saveOk:
|
||||
SHARED.error(
|
||||
@@ -494,10 +490,8 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.setDocumentChanged(False)
|
||||
self.docTextChanged.emit(self._docHandle, self._lastEdit)
|
||||
|
||||
oldHeader = self._nwItem.mainHeading
|
||||
oldCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
||||
SHARED.project.index.scanText(tHandle, docText)
|
||||
newHeader = self._nwItem.mainHeading
|
||||
SHARED.project.index.scanText(tHandle, text)
|
||||
newCount = SHARED.project.index.getHandleHeaderCount(tHandle)
|
||||
|
||||
if self._nwItem.itemClass == nwItemClass.NOVEL:
|
||||
@@ -506,9 +500,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
else:
|
||||
self.novelStructureChanged.emit()
|
||||
|
||||
if oldHeader != newHeader:
|
||||
self.docFooter.updateInfo()
|
||||
|
||||
# Update the status bar
|
||||
self.updateStatusMessage.emit(self.tr("Saved Document: {0}").format(self._nwItem.itemName))
|
||||
|
||||
@@ -589,7 +580,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
QTextDocument->toRawText instead of toPlainText. The former preserves
|
||||
non-breaking spaces, the latter does not. We still want to get rid of
|
||||
paragraph and line separators though.
|
||||
See: https://doc.qt.io/qt-5/qtextdocument.html#toPlainText
|
||||
See: https://doc.qt.io/qt-6/qtextdocument.html#toPlainText
|
||||
"""
|
||||
text = self._qDocument.toRawText()
|
||||
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
||||
@@ -627,10 +618,9 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
def setCursorPosition(self, position: int) -> None:
|
||||
"""Move the cursor to a given position in the document."""
|
||||
nChars = self._qDocument.characterCount()
|
||||
if nChars > 1 and isinstance(position, int):
|
||||
if (chars := self._qDocument.characterCount()) > 1 and isinstance(position, int):
|
||||
cursor = self.textCursor()
|
||||
cursor.setPosition(minmax(position, 0, nChars-1))
|
||||
cursor.setPosition(minmax(position, 0, chars-1))
|
||||
self.setTextCursor(cursor)
|
||||
self.centerCursor()
|
||||
return
|
||||
@@ -693,12 +683,10 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
def spellCheckDocument(self) -> None:
|
||||
"""Rerun the highlighter to update spell checking status of the
|
||||
currently loaded text. The fastest way to do this, at least as
|
||||
of Qt 5.13, is to clear the text and put it back. This clears
|
||||
the undo stack, so we only do it for big documents.
|
||||
currently loaded text.
|
||||
"""
|
||||
logger.debug("Running spell checker")
|
||||
start = time()
|
||||
logger.debug("Running spell checker")
|
||||
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
||||
self._qDocument.syntaxHighlighter.rehighlight()
|
||||
QApplication.restoreOverrideCursor()
|
||||
@@ -939,9 +927,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
* We also handle automatic scrolling here.
|
||||
"""
|
||||
self._lastActive = time()
|
||||
isReturn = event.key() == Qt.Key.Key_Return
|
||||
isReturn |= event.key() == Qt.Key.Key_Enter
|
||||
if isReturn and self.docSearch.anyFocus():
|
||||
if self.docSearch.anyFocus() and event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
||||
return
|
||||
elif event == QKeySequence.StandardKey.Redo:
|
||||
self.docAction(nwDocAction.REDO)
|
||||
@@ -1094,7 +1080,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
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
|
||||
# Only run on single character changes, or it will trigger
|
||||
# at unwanted times when other changes are made to the document
|
||||
cursor = self.textCursor()
|
||||
bPos = cursor.positionInBlock()
|
||||
@@ -1106,10 +1092,10 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
else:
|
||||
self._completer.setVisible(False)
|
||||
|
||||
if self._doReplace and added == 1:
|
||||
cursor = self.textCursor()
|
||||
if self._autoReplace.process(text, cursor):
|
||||
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
|
||||
if self._doReplace and added == 1:
|
||||
cursor = self.textCursor()
|
||||
if self._autoReplace.process(text, cursor):
|
||||
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
|
||||
|
||||
return
|
||||
|
||||
@@ -1123,11 +1109,10 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
def _insertCompletion(self, pos: int, length: int, text: str) -> None:
|
||||
"""Insert choice from the completer menu."""
|
||||
cursor = self.textCursor()
|
||||
block = cursor.block()
|
||||
if block.isValid():
|
||||
pos += block.position()
|
||||
cursor.setPosition(pos, QtMoveAnchor)
|
||||
cursor.setPosition(pos + length, QtKeepAnchor)
|
||||
if (block := cursor.block()).isValid():
|
||||
check = pos + block.position()
|
||||
cursor.setPosition(check, QtMoveAnchor)
|
||||
cursor.setPosition(check + length, QtKeepAnchor)
|
||||
cursor.insertText(text)
|
||||
self._completer.hide()
|
||||
return
|
||||
@@ -1220,7 +1205,8 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
# Execute the context menu
|
||||
if viewport := self.viewport():
|
||||
ctxMenu.exec(viewport.mapToGlobal(pos))
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
ctxMenu.setParent(None)
|
||||
|
||||
return
|
||||
|
||||
@@ -1277,15 +1263,11 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
@pyqtSlot()
|
||||
def _runSelCounter(self) -> None:
|
||||
"""Update the selection word count."""
|
||||
if self._docHandle is None:
|
||||
return
|
||||
|
||||
if self._wCounterSel.isRunning():
|
||||
logger.debug("Selection word counter is busy")
|
||||
return
|
||||
|
||||
SHARED.runInThreadPool(self._wCounterSel)
|
||||
|
||||
if self._docHandle:
|
||||
if self._wCounterSel.isRunning():
|
||||
logger.debug("Selection word counter is busy")
|
||||
return
|
||||
SHARED.runInThreadPool(self._wCounterSel)
|
||||
return
|
||||
|
||||
@pyqtSlot(int, int, int)
|
||||
@@ -1565,8 +1547,10 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
return
|
||||
|
||||
def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None,
|
||||
select: _SelectAction = _SelectAction.NO_DECISION) -> None:
|
||||
def _wrapSelection(
|
||||
self, before: str, after: str | None = None, pos: int | None = None,
|
||||
select: _SelectAction = _SelectAction.NO_DECISION
|
||||
) -> None:
|
||||
"""Wrap the selected text in whatever is in tBefore and tAfter.
|
||||
If there is no selection, the autoSelect setting decides the
|
||||
action. AutoSelect will select the word under the cursor before
|
||||
@@ -1928,8 +1912,9 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
|
||||
return
|
||||
|
||||
def _processTag(self, cursor: QTextCursor | None = None,
|
||||
follow: bool = True, create: bool = False) -> nwTrinary:
|
||||
def _processTag(
|
||||
self, cursor: QTextCursor | None = None, follow: bool = True, create: bool = False
|
||||
) -> nwTrinary:
|
||||
"""Activated by Ctrl+Enter. Checks that we're in a block
|
||||
starting with '@'. We then find the tag under the cursor and
|
||||
check that it is not the tag itself. If all this is fine, we
|
||||
@@ -2163,13 +2148,10 @@ class BackgroundWordCounter(QRunnable):
|
||||
|
||||
def __init__(self, docEditor: GuiDocEditor, forSelection: bool = False) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._docEditor = docEditor
|
||||
self._forSelection = forSelection
|
||||
self._isRunning = False
|
||||
|
||||
self.signals = BackgroundWordCounterSignals()
|
||||
|
||||
return
|
||||
|
||||
def isRunning(self) -> bool:
|
||||
@@ -3079,42 +3061,30 @@ class GuiDocEditFooter(QWidget):
|
||||
|
||||
# Status
|
||||
self.statusIcon = QLabel("", self)
|
||||
self.statusIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.statusIcon.setFixedHeight(iPx)
|
||||
self.statusIcon.setAlignment(QtAlignLeftTop)
|
||||
|
||||
self.statusText = QLabel(self.tr("Status"), self)
|
||||
self.statusText.setIndent(0)
|
||||
self.statusText.setMargin(0)
|
||||
self.statusText.setContentsMargins(0, 0, 0, 0)
|
||||
self.statusText = QLabel("", self)
|
||||
self.statusText.setAutoFillBackground(True)
|
||||
self.statusText.setFixedHeight(fPx)
|
||||
self.statusText.setAlignment(QtAlignLeftTop)
|
||||
|
||||
# Lines
|
||||
self.linesIcon = QLabel("", self)
|
||||
self.linesIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.linesIcon.setFixedHeight(iPx)
|
||||
self.linesIcon.setAlignment(QtAlignLeftTop)
|
||||
|
||||
self.linesText = QLabel("", self)
|
||||
self.linesText.setIndent(0)
|
||||
self.linesText.setMargin(0)
|
||||
self.linesText.setContentsMargins(0, 0, 0, 0)
|
||||
self.linesText.setAutoFillBackground(True)
|
||||
self.linesText.setFixedHeight(fPx)
|
||||
self.linesText.setAlignment(QtAlignLeftTop)
|
||||
|
||||
# Words
|
||||
self.wordsIcon = QLabel("", self)
|
||||
self.wordsIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.wordsIcon.setFixedHeight(iPx)
|
||||
self.wordsIcon.setAlignment(QtAlignLeftTop)
|
||||
|
||||
self.wordsText = QLabel("", self)
|
||||
self.wordsText.setIndent(0)
|
||||
self.wordsText.setMargin(0)
|
||||
self.wordsText.setContentsMargins(0, 0, 0, 0)
|
||||
self.wordsText.setAutoFillBackground(True)
|
||||
self.wordsText.setFixedHeight(fPx)
|
||||
self.wordsText.setAlignment(QtAlignLeftTop)
|
||||
@@ -3167,10 +3137,10 @@ class GuiDocEditFooter(QWidget):
|
||||
iPx = round(0.9*SHARED.theme.baseIconHeight)
|
||||
self.linesIcon.setPixmap(SHARED.theme.getPixmap("lines", (iPx, iPx)))
|
||||
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("stats", (iPx, iPx)))
|
||||
self.matchColours()
|
||||
self.matchColors()
|
||||
return
|
||||
|
||||
def matchColours(self) -> None:
|
||||
def matchColors(self) -> None:
|
||||
"""Update the colours of the widget to match those of the syntax
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user