Update linting for main code

This commit is contained in:
Veronica Berglyd Olsen
2025-08-27 21:00:09 +02:00
parent 84ff1f4640
commit c174f8f931
80 changed files with 461 additions and 1653 deletions
+23 -134
View File
@@ -29,7 +29,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import bisect
@@ -98,7 +98,7 @@ class _TagAction(IntFlag):
class GuiDocEditor(QPlainTextEdit):
"""Gui Widget: Main Document Editor"""
"""Gui Widget: Main Document Editor."""
__slots__ = (
"_autoReplace", "_completer", "_doReplace", "_docChanged", "_docHandle", "_followTag1",
@@ -236,8 +236,6 @@ class GuiDocEditor(QPlainTextEdit):
logger.debug("Ready: GuiDocEditor")
return
##
# Properties
##
@@ -290,15 +288,12 @@ class GuiDocEditor(QPlainTextEdit):
self.itemHandleChanged.emit("")
return
def updateTheme(self) -> None:
"""Update theme elements."""
self.docSearch.updateTheme()
self.docHeader.updateTheme()
self.docFooter.updateTheme()
self.docToolBar.updateTheme()
return
def updateSyntaxColors(self) -> None:
"""Update the syntax highlighting theme."""
@@ -323,8 +318,6 @@ class GuiDocEditor(QPlainTextEdit):
self._selection.format.setBackground(self._lineColor)
self._selection.format.setProperty(QTextFormat.Property.FullWidthSelection, True)
return
def initEditor(self) -> None:
"""Initialise or re-initialise the editor with the user's
settings. This function is both called when the editor is
@@ -392,8 +385,6 @@ class GuiDocEditor(QPlainTextEdit):
else:
self.clearEditor()
return
def loadText(self, tHandle: str, tLine: int | None = None) -> bool:
"""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
@@ -471,7 +462,6 @@ class GuiDocEditor(QPlainTextEdit):
self.updateDocMargins()
self.setDocumentChanged(True)
QApplication.restoreOverrideCursor()
return
def saveText(self) -> bool:
"""Save the text currently in the editor to the NWDocument
@@ -539,7 +529,6 @@ class GuiDocEditor(QPlainTextEdit):
vBar.setValue(vBar.value() + 1)
count += 1
QApplication.processEvents()
return
def updateDocMargins(self) -> None:
"""Automatically adjust the margins so the text is centred if
@@ -580,8 +569,6 @@ class GuiDocEditor(QPlainTextEdit):
lM = max(self._vpMargin, fH)
self.setViewportMargins(tM, uM, tM, lM)
return
##
# Getters
##
@@ -591,20 +578,19 @@ 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-6/qtextdocument.html#toPlainText
"""
text = self._qDocument.toRawText()
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
return text
return text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
def getSelectedText(self) -> str:
"""Get currently selected text."""
if (cursor := self.textCursor()).hasSelection():
text = cursor.selectedText()
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
return text
return text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
return ""
def getCursorPosition(self) -> int:
@@ -625,7 +611,6 @@ class GuiDocEditor(QPlainTextEdit):
logger.debug("Document changed status is '%s'", state)
self._docChanged = state
self.editedStatusChanged.emit(self._docChanged)
return
def setCursorPosition(self, position: int) -> None:
"""Move the cursor to a given position in the document."""
@@ -634,14 +619,12 @@ class GuiDocEditor(QPlainTextEdit):
cursor.setPosition(minmax(position, 0, chars-1))
self.setTextCursor(cursor)
self.centerCursor()
return
def saveCursorPosition(self) -> None:
"""Save the cursor position to the current project item."""
if self._nwItem is not None:
cursPos = self.getCursorPosition()
self._nwItem.setCursorPos(cursPos)
return
def setCursorLine(self, line: int | None) -> None:
"""Move the cursor to a given line in the document."""
@@ -650,7 +633,6 @@ class GuiDocEditor(QPlainTextEdit):
if block:
self.setCursorPosition(block.position())
logger.debug("Cursor moved to line %d", line)
return
def setCursorSelection(self, start: int, length: int) -> None:
"""Make a text selection."""
@@ -659,14 +641,15 @@ class GuiDocEditor(QPlainTextEdit):
cursor.setPosition(start, QtMoveAnchor)
cursor.setPosition(start + length, QtKeepAnchor)
self.setTextCursor(cursor)
return
##
# Spell Checking
##
def toggleSpellCheck(self, state: bool | None) -> None:
"""This is the main spell check setting function, and this one
"""Toggle spell checking.
This is the main spell check setting function, and this one
should call all other setSpellCheck functions in other classes.
If the spell check state is not defined (None), then toggle the
current status saved in this class.
@@ -690,8 +673,6 @@ class GuiDocEditor(QPlainTextEdit):
logger.debug("Spell check is set to '%s'", str(state))
return
def spellCheckDocument(self) -> None:
"""Rerun the highlighter to update spell checking status of the
currently loaded text.
@@ -703,7 +684,6 @@ class GuiDocEditor(QPlainTextEdit):
QApplication.restoreOverrideCursor()
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
self.updateStatusMessage.emit(self.tr("Spell check complete"))
return
##
# General Class Methods
@@ -832,7 +812,6 @@ class GuiDocEditor(QPlainTextEdit):
details=self.tr("File Location: {0}").format(self._nwDocument.fileLocation),
log=False
)
return
def insertText(self, insert: str | nwDocInsert) -> None:
"""Insert a specific type of text at the cursor position."""
@@ -974,7 +953,6 @@ class GuiDocEditor(QPlainTextEdit):
event.acceptProposedAction()
else:
super().dragEnterEvent(event)
return
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
"""Overload drag move event to handle dragged items."""
@@ -982,7 +960,6 @@ class GuiDocEditor(QPlainTextEdit):
event.acceptProposedAction()
else:
super().dragMoveEvent(event)
return
def dropEvent(self, event: QDropEvent) -> None:
"""Overload drop event to handle dragged items."""
@@ -992,7 +969,6 @@ class GuiDocEditor(QPlainTextEdit):
self.openDocumentRequest.emit(handles[0], nwDocMode.EDIT, "", True)
else:
super().dropEvent(event)
return
def focusNextPrevChild(self, _next: bool) -> bool:
"""Capture the focus request from the tab key on the text
@@ -1019,7 +995,6 @@ class GuiDocEditor(QPlainTextEdit):
else:
self._processTag(cursor)
super().mouseReleaseEvent(event)
return
def resizeEvent(self, event: QResizeEvent) -> None:
"""If the text editor is resized, we must make sure the document
@@ -1027,7 +1002,6 @@ class GuiDocEditor(QPlainTextEdit):
"""
self.updateDocMargins()
super().resizeEvent(event)
return
##
# Public Slots
@@ -1035,14 +1009,13 @@ class GuiDocEditor(QPlainTextEdit):
@pyqtSlot(str, Enum)
def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
"""Called when an item label is changed to check if the document
title bar needs updating,
"""Process project item change. Called when an item label is
changed to check if the document title bar needs updating.
"""
if tHandle == self._docHandle and change == nwChange.UPDATE:
self.docHeader.setHandle(tHandle)
self.docFooter.updateInfo()
self.updateDocMargins()
return
@pyqtSlot(str)
def insertKeyWord(self, keyword: str) -> bool:
@@ -1053,8 +1026,7 @@ class GuiDocEditor(QPlainTextEdit):
logger.error("Invalid keyword '%s'", keyword)
return False
logger.debug("Inserting keyword '%s'", keyword)
state = self.insertNewBlock(f"{keyword}: ")
return state
return self.insertNewBlock(f"{keyword}: ")
@pyqtSlot()
def toggleSearch(self) -> None:
@@ -1063,14 +1035,12 @@ class GuiDocEditor(QPlainTextEdit):
self.closeSearch()
else:
self.beginSearch()
return
@pyqtSlot(list, list)
def updateChangedTags(self, updated: list[str], deleted: list[str]) -> None:
"""Tags have changed, so just in case we rehighlight them."""
if updated or deleted:
self._qDocument.syntaxHighlighter.rehighlightByType(BLOCK_META)
return
##
# Private Slots
@@ -1116,8 +1086,6 @@ class GuiDocEditor(QPlainTextEdit):
if self._autoReplace.process(text, cursor):
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
return
@pyqtSlot()
def _cursorMoved(self) -> None:
"""Triggered when the cursor moved in the editor."""
@@ -1126,7 +1094,6 @@ class GuiDocEditor(QPlainTextEdit):
self._selection.cursor = self.textCursor()
self._selection.cursor.clearSelection()
self.setExtraSelections([self._selection])
return
@pyqtSlot(int, int, str)
def _insertCompletion(self, pos: int, length: int, text: str) -> None:
@@ -1138,13 +1105,11 @@ class GuiDocEditor(QPlainTextEdit):
cursor.setPosition(check + length, QtKeepAnchor)
cursor.insertText(text)
self._completer.hide()
return
@pyqtSlot()
def _openContextFromCursor(self) -> None:
"""Open the spell check context menu at the cursor."""
self._openContextMenu(self.cursorRect().center())
return
@pyqtSlot("QPoint")
def _openContextMenu(self, pos: QPoint) -> None:
@@ -1231,8 +1196,6 @@ class GuiDocEditor(QPlainTextEdit):
ctxMenu.setParent(None)
return
@pyqtSlot()
def _runDocumentTasks(self) -> None:
"""Run timer document tasks."""
@@ -1269,11 +1232,10 @@ class GuiDocEditor(QPlainTextEdit):
if not self.textCursor().hasSelection():
# Selection counter should take precedence (#2155)
self.docFooter.updateMainCount(mCount, False)
return
@pyqtSlot()
def _updateSelectedStatus(self) -> None:
"""The user made a change in text selection. Forward this
"""Process user change in text selection. Forward this
information to the footer, and start the selection word counter.
"""
if self.textCursor().hasSelection():
@@ -1282,7 +1244,6 @@ class GuiDocEditor(QPlainTextEdit):
else:
self._timerSel.stop()
self.docFooter.updateMainCount(0, False)
return
@pyqtSlot()
def _runSelCounter(self) -> None:
@@ -1300,14 +1261,12 @@ class GuiDocEditor(QPlainTextEdit):
if self._docHandle and self._nwItem:
self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
self._timerSel.stop()
return
@pyqtSlot()
def _closeCurrentDocument(self) -> None:
"""Close the document. Forwarded to the main Gui."""
self.closeEditorRequest.emit()
self.docToolBar.setVisible(False)
return
@pyqtSlot()
def _toggleToolBarVisibility(self) -> None:
@@ -1315,7 +1274,6 @@ class GuiDocEditor(QPlainTextEdit):
state = not self.docToolBar.isVisible()
self.docToolBar.setVisible(state)
CONFIG.showEditToolBar = state
return
##
# Search & Replace
@@ -1326,14 +1284,12 @@ class GuiDocEditor(QPlainTextEdit):
self.docSearch.setSearchText(self.getSelectedText() or None)
resS, _ = self.findAllOccurences()
self.docSearch.setResultCount(None, len(resS))
return
def beginReplace(self) -> None:
"""Initialise the search box and reset the replace text box."""
self.beginSearch()
self.docSearch.setReplaceText("")
self.updateDocMargins()
return
def findNext(self, goBack: bool = False) -> None:
"""Search for the next or previous occurrence of the search bar
@@ -1621,8 +1577,6 @@ class GuiDocEditor(QPlainTextEdit):
self.setTextCursor(cursor)
return
def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> None:
"""Replace all straight quotes in the selected text."""
cursor = self.textCursor()
@@ -1882,8 +1836,6 @@ class GuiDocEditor(QPlainTextEdit):
cursor.insertText(cleanText.rstrip() + "\n")
cursor.endEditBlock()
return
def _insertCommentStructure(self, style: nwComment) -> None:
"""Insert a shortcut/comment combo."""
if self._docHandle and style == nwComment.FOOTNOTE:
@@ -1925,7 +1877,6 @@ class GuiDocEditor(QPlainTextEdit):
cursor.endEditBlock()
cursor.setPosition(pos)
self.setTextCursor(cursor)
return
def _addWord(self, word: str, block: QTextBlock, save: bool) -> None:
"""Slot for the spell check context menu triggered when the user
@@ -1934,7 +1885,6 @@ class GuiDocEditor(QPlainTextEdit):
logger.debug("Added '%s' to project dictionary, %s", word, "saved" if save else "unsaved")
SHARED.spelling.addWord(word, save=save)
self._qDocument.syntaxHighlighter.rehighlightBlock(block)
return
def _processTag(
self, cursor: QTextCursor | None = None, follow: bool = True, create: bool = False
@@ -2008,7 +1958,6 @@ class GuiDocEditor(QPlainTextEdit):
if self._docHandle:
text = block.text().lstrip("#").lstrip("!").strip()
self.requestProjectItemRenamed.emit(self._docHandle, text)
return
def _autoSelect(self) -> QTextCursor:
"""Return a cursor which may or may not have a selection based
@@ -2078,14 +2027,11 @@ class GuiDocEditor(QPlainTextEdit):
self.setTextCursor(cursor)
return
def _makePosSelection(self, mode: QTextCursor.SelectionType, pos: QPoint) -> None:
"""Select text based on selection mode, but first move cursor."""
cursor = self.cursorForPosition(pos)
self.setTextCursor(cursor)
self._makeSelection(mode)
return
def _allowAutoReplace(self, state: bool) -> None:
"""Enable/disable the auto-replace feature temporarily."""
@@ -2093,11 +2039,10 @@ class GuiDocEditor(QPlainTextEdit):
self._doReplace = CONFIG.doReplace
else:
self._doReplace = False
return
class CommandCompleter(QMenu):
"""GuiWidget: Command Completer Menu
"""GuiWidget: Command Completer Menu.
This is a context menu with options populated from the user's
defined tags and keys. It also helps to type the meta data keyword
@@ -2109,7 +2054,6 @@ class CommandCompleter(QMenu):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
return
def updateMetaText(self, text: str, pos: int) -> bool:
"""Update the menu options based on the line of text."""
@@ -2203,7 +2147,6 @@ class CommandCompleter(QMenu):
super().keyPressEvent(event)
elif isinstance(parent, GuiDocEditor):
parent.keyPressEvent(event)
return
##
# Internal Functions
@@ -2212,11 +2155,10 @@ class CommandCompleter(QMenu):
def _emitComplete(self, pos: int, length: int, value: str) -> None:
"""Emit the signal to indicate a selection has been made."""
self.complete.emit(pos, length, value)
return
class BackgroundWordCounter(QRunnable):
"""The Off-GUI Thread Word Counter
"""The Off-GUI Thread Word Counter.
A runnable for the word counter to be run in the thread pool off the
main GUI thread.
@@ -2228,9 +2170,9 @@ class BackgroundWordCounter(QRunnable):
self._forSelection = forSelection
self._isRunning = False
self.signals = BackgroundWordCounterSignals()
return
def isRunning(self) -> bool:
"""Return True if the word counter is already running."""
return self._isRunning
@pyqtSlot()
@@ -2248,17 +2190,17 @@ class BackgroundWordCounter(QRunnable):
self.signals.countsReady.emit(cC, wC, pC)
self._isRunning = False
return
class BackgroundWordCounterSignals(QObject):
"""The QRunnable cannot emit a signal, so we need a simple QObject
to hold the word counter signal.
"""
countsReady = pyqtSignal(int, int, int)
class TextAutoReplace:
"""Encapsulates the editor auto replace feature."""
__slots__ = (
"_doPadAfter", "_doPadBefore", "_padAfter", "_padBefore", "_padChar",
@@ -2268,7 +2210,6 @@ class TextAutoReplace:
def __init__(self) -> None:
self.initSettings()
return
def initSettings(self) -> None:
"""Initialise the auto-replace settings from config."""
@@ -2287,7 +2228,6 @@ class TextAutoReplace:
self._padAfter = CONFIG.fmtPadAfter
self._doPadBefore = bool(CONFIG.fmtPadBefore)
self._doPadAfter = bool(CONFIG.fmtPadAfter)
return
def process(self, text: str, cursor: QTextCursor) -> bool:
"""Auto-replace text elements based on main configuration.
@@ -2401,7 +2341,7 @@ class TextAutoReplace:
class GuiDocToolBar(QWidget):
"""The Formatting and Options Fold Out Menu
"""The Formatting and Options Fold Out Menu.
Only used by DocEditor, and is opened by the first button in the
header.
@@ -2513,8 +2453,6 @@ class GuiDocToolBar(QWidget):
logger.debug("Ready: GuiDocToolBar")
return
def updateTheme(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
syntax = SHARED.theme.syntaxTheme
@@ -2537,11 +2475,9 @@ class GuiDocToolBar(QWidget):
self.tbSuperscript.setThemeIcon("fmt_superscript")
self.tbSubscript.setThemeIcon("fmt_subscript")
return
class GuiDocEditSearch(QFrame):
"""The Embedded Document Search/Replace Feature
"""The Embedded Document Search/Replace Feature.
Only used by DocEditor, and is at a fixed position in the
QTextEdit's viewport.
@@ -2671,8 +2607,6 @@ class GuiDocEditSearch(QFrame):
logger.debug("Ready: GuiDocEditSearch")
return
##
# Properties
##
@@ -2721,14 +2655,12 @@ class GuiDocEditSearch(QFrame):
self.searchBox.selectAll()
if CONFIG.searchRegEx:
self._alertSearchValid(True)
return
def setReplaceText(self, text: str) -> None:
"""Set the replace text."""
self.showReplace.setChecked(True)
self.replaceBox.setFocus()
self.replaceBox.setText(text)
return
def setResultCount(self, currRes: int | None, resCount: int | None) -> None:
"""Set the count values for the current search."""
@@ -2743,7 +2675,6 @@ class GuiDocEditSearch(QFrame):
self.resultLabel.setMinimumWidth(minWidth)
self.adjustSize()
self.docEditor.updateDocMargins()
return
##
# Methods
@@ -2759,7 +2690,6 @@ class GuiDocEditSearch(QFrame):
self.resultLabel.setMinimumWidth(
SHARED.theme.getTextWidth("?/?", SHARED.theme.guiFontSmall)
)
return
def updateTheme(self) -> None:
"""Update theme elements."""
@@ -2784,11 +2714,9 @@ class GuiDocEditSearch(QFrame):
self.searchOpt.setStyleSheet("QToolBar {padding: 0;}")
self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
return
def cycleFocus(self) -> bool:
"""The tab key just alternates focus between the two input
boxes, if the replace box is visible.
"""Cycle focus on tab key press. This just alternates focus
between the two input boxes, if the replace box is visible.
"""
if self.searchBox.hasFocus():
self.replaceBox.setFocus()
@@ -2813,7 +2741,6 @@ class GuiDocEditSearch(QFrame):
self.setVisible(False)
self.docEditor.updateDocMargins()
self.docEditor.setFocus()
return
##
# Private Slots
@@ -2823,13 +2750,11 @@ class GuiDocEditSearch(QFrame):
def _doSearch(self) -> None:
"""Call the search action function for the document editor."""
self.docEditor.findNext(goBack=(QApplication.keyboardModifiers() == QtModShift))
return
@pyqtSlot()
def _doReplace(self) -> None:
"""Call the replace action function for the document editor."""
self.docEditor.replaceNext()
return
@pyqtSlot(bool)
def _doToggleReplace(self, state: bool) -> None:
@@ -2838,43 +2763,36 @@ class GuiDocEditSearch(QFrame):
self.replaceButton.setVisible(state)
self.adjustSize()
self.docEditor.updateDocMargins()
return
@pyqtSlot(bool)
def _doToggleCase(self, state: bool) -> None:
"""Enable/disable case sensitive mode."""
CONFIG.searchCase = state
return
@pyqtSlot(bool)
def _doToggleWord(self, state: bool) -> None:
"""Enable/disable whole word search mode."""
CONFIG.searchWord = state
return
@pyqtSlot(bool)
def _doToggleRegEx(self, state: bool) -> None:
"""Enable/disable regular expression search mode."""
CONFIG.searchRegEx = state
return
@pyqtSlot(bool)
def _doToggleLoop(self, state: bool) -> None:
"""Enable/disable looping the search."""
CONFIG.searchLoop = state
return
@pyqtSlot(bool)
def _doToggleProject(self, state: bool) -> None:
"""Enable/disable continuing search in next project file."""
CONFIG.searchNextFile = state
return
@pyqtSlot(bool)
def _doToggleMatchCap(self, state: bool) -> None:
"""Enable/disable preserving capitalisation when replacing."""
CONFIG.searchMatchCap = state
return
##
# Internal Functions
@@ -2890,11 +2808,10 @@ class GuiDocEditSearch(QFrame):
palette.text().color() if isValid else SHARED.theme.errorText
)
self.searchBox.setPalette(palette)
return
class GuiDocEditHeader(QWidget):
"""The Embedded Document Header
"""The Embedded Document Header.
Only used by DocEditor, and is at a fixed position in the
QTextEdit's viewport.
@@ -2985,8 +2902,6 @@ class GuiDocEditHeader(QWidget):
logger.debug("Ready: GuiDocEditHeader")
return
##
# Methods
##
@@ -3003,7 +2918,6 @@ class GuiDocEditHeader(QWidget):
self.searchButton.setVisible(False)
self.closeButton.setVisible(False)
self.minmaxButton.setVisible(False)
return
def setOutline(self, data: dict[int, str]) -> None:
"""Set the document outline dataset."""
@@ -3015,13 +2929,11 @@ class GuiDocEditHeader(QWidget):
action.triggered.connect(qtLambda(self._gotoBlock, number))
self._docOutline = data
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
return
def updateFont(self) -> None:
"""Update the font settings."""
self.setFont(SHARED.theme.guiFont)
self.itemTitle.setFont(SHARED.theme.guiFontSmall)
return
def updateTheme(self) -> None:
"""Update theme elements."""
@@ -3040,8 +2952,6 @@ class GuiDocEditHeader(QWidget):
self.matchColors()
return
def matchColors(self) -> None:
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
@@ -3055,12 +2965,10 @@ class GuiDocEditHeader(QWidget):
self.itemTitle.setTextColors(
color=palette.windowText().color(), faded=SHARED.theme.fadedText
)
return
def changeFocusState(self, state: bool) -> None:
"""Toggle focus state."""
self.itemTitle.setColorState(state)
return
def setHandle(self, tHandle: str) -> None:
"""Set the document title from the handle, or alternatively, set
@@ -3081,8 +2989,6 @@ class GuiDocEditHeader(QWidget):
self.closeButton.setVisible(True)
self.minmaxButton.setVisible(True)
return
##
# Private Slots
##
@@ -3092,19 +2998,16 @@ class GuiDocEditHeader(QWidget):
"""Trigger the close editor on the main window."""
self.clearHeader()
self.closeDocumentRequest.emit()
return
@pyqtSlot(int)
def _gotoBlock(self, blockNumber: int) -> None:
"""Move cursor to a specific heading."""
self.docEditor.setCursorLine(blockNumber + 1)
return
@pyqtSlot(bool)
def _focusModeChanged(self, focusMode: bool) -> None:
"""Update minimise/maximise icon of the Focus Mode button."""
self.minmaxButton.setThemeIcon("minimise" if focusMode else "maximise", "blue")
return
##
# Events
@@ -3116,11 +3019,10 @@ class GuiDocEditHeader(QWidget):
"""
if event.button() == QtMouseLeft:
self.docEditor.requestProjectItemSelected.emit(self._docHandle or "", True)
return
class GuiDocEditFooter(QWidget):
"""The Embedded Document Footer
"""The Embedded Document Footer.
Only used by DocEditor, and is at a fixed position in the
QTextEdit's viewport.
@@ -3205,8 +3107,6 @@ class GuiDocEditFooter(QWidget):
logger.debug("Ready: GuiDocEditFooter")
return
##
# Methods
##
@@ -3216,7 +3116,6 @@ class GuiDocEditFooter(QWidget):
self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
])
return
def updateFont(self) -> None:
"""Update the font settings."""
@@ -3224,7 +3123,6 @@ class GuiDocEditFooter(QWidget):
self.statusText.setFont(SHARED.theme.guiFontSmall)
self.linesText.setFont(SHARED.theme.guiFontSmall)
self.wordsText.setFont(SHARED.theme.guiFontSmall)
return
def updateTheme(self) -> None:
"""Update theme elements."""
@@ -3232,7 +3130,6 @@ class GuiDocEditFooter(QWidget):
self.linesIcon.setPixmap(SHARED.theme.getPixmap("lines", (iPx, iPx)))
self.wordsIcon.setPixmap(SHARED.theme.getPixmap("stats", (iPx, iPx)))
self.matchColors()
return
def matchColors(self) -> None:
"""Update the colours of the widget to match those of the syntax
@@ -3250,8 +3147,6 @@ class GuiDocEditFooter(QWidget):
self.linesText.setPalette(palette)
self.wordsText.setPalette(palette)
return
def setHandle(self, tHandle: str | None) -> None:
"""Set the handle that will populate the footer's data."""
self._docHandle = tHandle
@@ -3264,8 +3159,6 @@ class GuiDocEditFooter(QWidget):
self.updateInfo()
self.updateMainCount(0, False)
return
def updateInfo(self) -> None:
"""Update the content of text labels."""
if self._tItem is None:
@@ -3280,8 +3173,6 @@ class GuiDocEditFooter(QWidget):
self.statusIcon.setPixmap(sIcon)
self.statusText.setText(sText)
return
def updateLineCount(self, cursor: QTextCursor) -> None:
"""Update the line and document position counter."""
if document := cursor.document():
@@ -3291,7 +3182,6 @@ class GuiDocEditFooter(QWidget):
self.linesText.setText(
self._trLineCount.format(f"{cLine:n}", f"{100*cPos//cCount:d} %")
)
return
def updateMainCount(self, count: int, selection: bool) -> None:
"""Update main counter information."""
@@ -3304,4 +3194,3 @@ class GuiDocEditFooter(QWidget):
else:
text = self._trMainCount.format("0", "+0")
self.wordsText.setText(text)
return
+7 -13
View File
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -57,6 +57,7 @@ BLOCK_TITLE = 4
class GuiDocHighlighter(QSyntaxHighlighter):
"""GUI: Editor Syntax Highlighter."""
__slots__ = (
"_cmnRules", "_dialogParser", "_hStyles", "_isInactive", "_isNovel",
@@ -85,8 +86,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
logger.debug("Ready: GuiDocHighlighter")
return
def initHighlighter(self) -> None:
"""Initialise the syntax highlighter, setting all the colour
rules and building the RegExes.
@@ -255,8 +254,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._txtRules.append((rxRule, hlRule))
self._cmnRules.append((rxRule, hlRule))
return
##
# Setters
##
@@ -264,7 +261,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
def setSpellCheck(self, state: bool) -> None:
"""Enable/disable the real time spell checker."""
self._spellCheck = state
return
def setHandle(self, tHandle: str) -> None:
"""Set the handle of the currently highlighted document."""
@@ -275,7 +271,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._isNovel = item.isDocumentLayout()
self._isInactive = item.isInactiveClass()
logger.debug("Syntax highlighter enabled for item '%s'", tHandle)
return
##
# Methods
@@ -293,7 +288,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if block.userState() & cType > 0:
self.rehighlightBlock(block)
logger.debug("Document highlighted in %.3f ms" % (1000*(time() - tStart)))
return
##
# Highlight Block
@@ -506,10 +500,13 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self._hStyles[name] = charFormat
return
class TextBlockData(QTextBlockUserData):
"""Custom QTextBlock Data.
Custom data stored in a single text block. The spell check state is
cached here and used when correcting misspelled text.
"""
__slots__ = ("_metaData", "_offset", "_spellErrors", "_text")
@@ -519,7 +516,6 @@ class TextBlockData(QTextBlockUserData):
self._offset = 0
self._metaData: list[tuple[int, int, str, str]] = []
self._spellErrors: list[tuple[int, int, str]] = []
return
@property
def metaData(self) -> list[tuple[int, int, str, str]]:
@@ -553,8 +549,6 @@ class TextBlockData(QTextBlockUserData):
self._text = text.replace("\u02bc", "'").replace("_", " ")
self._offset = offset
return
def spellCheck(self, utf16Map: list[int] | None) -> list[tuple[int, int, str]]:
"""Run the spell checker and cache the result, and return the
list of spell check errors.
+10 -63
View File
@@ -23,7 +23,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -60,6 +60,7 @@ logger = logging.getLogger(__name__)
class GuiDocViewer(QTextBrowser):
"""GUI: Document Viewer."""
closeDocumentRequest = pyqtSignal()
documentLoaded = pyqtSignal(str)
@@ -110,8 +111,6 @@ class GuiDocViewer(QTextBrowser):
logger.debug("Ready: GuiDocViewer")
return
##
# Properties
##
@@ -138,13 +137,11 @@ class GuiDocViewer(QTextBrowser):
self.setSearchPaths([""])
self._docHandle = None
self.docHeader.clearHeader()
return
def updateTheme(self) -> None:
"""Update theme elements."""
self.docHeader.updateTheme()
self.docFooter.updateTheme()
return
def initViewer(self) -> None:
"""Set editor settings from main config."""
@@ -206,8 +203,6 @@ class GuiDocViewer(QTextBrowser):
# If we have a document open, we should reload it in case the font changed
self.reloadText()
return
def loadText(self, tHandle: str, updateHistory: bool = True) -> bool:
"""Load text into the viewer from an item handle."""
if not SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
@@ -280,7 +275,6 @@ class GuiDocViewer(QTextBrowser):
"""Reload the text in the current document."""
if self._docHandle:
self.loadText(self._docHandle, updateHistory=False)
return
def docAction(self, action: nwDocAction) -> bool:
"""Process document actions on the current document."""
@@ -308,7 +302,6 @@ class GuiDocViewer(QTextBrowser):
def clearNavHistory(self) -> None:
"""Clear the navigation history."""
self.docHistory.clear()
return
def updateDocMargins(self) -> None:
"""Automatically adjust the margins so the text is centred."""
@@ -337,8 +330,6 @@ class GuiDocViewer(QTextBrowser):
self.docFooter.setGeometry(tB, fY, tW, fH)
self.setViewportMargins(tM, max(cM, tH), tM, max(cM, fH))
return
##
# Setters
##
@@ -347,7 +338,6 @@ class GuiDocViewer(QTextBrowser):
"""Set the scrollbar position."""
if (vBar := self.verticalScrollBar()) and vBar.isVisible():
vBar.setValue(pos)
return
##
# Public Slots
@@ -359,7 +349,6 @@ class GuiDocViewer(QTextBrowser):
if tHandle == self._docHandle and change == nwChange.UPDATE:
self.docHeader.setHandle(tHandle)
self.updateDocMargins()
return
@pyqtSlot(str)
def navigateTo(self, anchor: str) -> None:
@@ -367,7 +356,6 @@ class GuiDocViewer(QTextBrowser):
if isinstance(anchor, str) and anchor.startswith("#"):
logger.debug("Moving to anchor '%s'", anchor)
self.setSource(QUrl(anchor))
return
##
# Private Slots
@@ -377,13 +365,11 @@ class GuiDocViewer(QTextBrowser):
def navBackward(self) -> None:
"""Navigate backwards in the document view history."""
self.docHistory.backward()
return
@pyqtSlot()
def navForward(self) -> None:
"""Navigate forwards in the document view history."""
self.docHistory.forward()
return
@pyqtSlot("QUrl")
def _linkClicked(self, url: QUrl) -> None:
@@ -396,7 +382,6 @@ class GuiDocViewer(QTextBrowser):
self.navigateTo(link)
elif link.startswith("http"):
QDesktopServices.openUrl(QUrl(url))
return
@pyqtSlot("QPoint")
def _openContextMenu(self, point: QPoint) -> None:
@@ -430,8 +415,6 @@ class GuiDocViewer(QTextBrowser):
ctxMenu.setParent(None)
return
##
# Events
##
@@ -440,7 +423,6 @@ class GuiDocViewer(QTextBrowser):
"""Update document margins when widget is resized."""
self.updateDocMargins()
super().resizeEvent(event)
return
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
"""Capture mouse click events on the document."""
@@ -450,7 +432,6 @@ class GuiDocViewer(QTextBrowser):
self.navForward()
else:
super().mouseReleaseEvent(event)
return
def dragEnterEvent(self, event: QDragEnterEvent) -> None:
"""Overload drag enter event to handle dragged items."""
@@ -458,7 +439,6 @@ class GuiDocViewer(QTextBrowser):
event.acceptProposedAction()
else:
super().dragEnterEvent(event)
return
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
"""Overload drag move event to handle dragged items."""
@@ -466,7 +446,6 @@ class GuiDocViewer(QTextBrowser):
event.acceptProposedAction()
else:
super().dragMoveEvent(event)
return
def dropEvent(self, event: QDropEvent) -> None:
"""Overload drop event to handle dragged items."""
@@ -476,7 +455,6 @@ class GuiDocViewer(QTextBrowser):
self.openDocumentRequest.emit(handles[0], nwDocMode.VIEW, "", True)
else:
super().dropEvent(event)
return
##
# Internal Functions
@@ -500,16 +478,18 @@ class GuiDocViewer(QTextBrowser):
self.setTextCursor(cursor)
return
def _makePosSelection(self, selType: QTextCursor.SelectionType, pos: QPoint) -> None:
"""Handle text selection at a given location."""
self.setTextCursor(self.cursorForPosition(pos))
self._makeSelection(selType)
return
class GuiDocViewHistory:
"""GUI: Document Viewer History.
This class holds the navigation history for the viewer panel, which
is used for backward/forward navigation.
"""
def __init__(self, docViewer: GuiDocViewer) -> None:
self.docViewer = docViewer
@@ -517,7 +497,6 @@ class GuiDocViewHistory:
self._posHistory = []
self._currPos = -1
self._prevPos = -1
return
def clear(self) -> None:
"""Clear the view history."""
@@ -526,7 +505,6 @@ class GuiDocViewHistory:
self._posHistory = []
self._currPos = -1
self._prevPos = -1
return
def append(self, tHandle: str) -> bool:
"""Append a document handle and its scroll bar position to the
@@ -566,7 +544,6 @@ class GuiDocViewHistory:
self._currPos = newPos
self._updateNavButtons()
self._dumpHistory()
return
def backward(self) -> None:
"""Navigate to the previous entry in the view history."""
@@ -580,7 +557,6 @@ class GuiDocViewHistory:
self._currPos = newPos
self._updateNavButtons()
self._dumpHistory()
return
##
# Internal Functions
@@ -590,12 +566,10 @@ class GuiDocViewHistory:
"""Update the scrollbar position of the previous entry."""
if self._prevPos >= 0 and self._prevPos < len(self._posHistory):
self._posHistory[self._prevPos] = self.docViewer.scrollPosition
return
def _updateNavButtons(self) -> None:
"""Update the navigation buttons in the document header."""
self.docViewer.docHeader.updateNavButtons(0, len(self._navHistory) - 1, self._currPos)
return
def _truncateHistory(self, atPos: int) -> None:
"""Truncate the navigation history to the given position. Also
@@ -606,7 +580,6 @@ class GuiDocViewHistory:
self._posHistory = self._posHistory[nSkip:atPos + 1]
self._currPos -= nSkip
self._prevPos -= nSkip
return
def _dumpHistory(self) -> None:
"""Debug function to dump history to the logger. Since it is a
@@ -616,11 +589,10 @@ class GuiDocViewHistory:
for i, (h, p) in enumerate(zip(self._navHistory, self._posHistory, strict=False)):
a = ">" if i == self._currPos else " "
logger.debug(f"History {i + 1:02d}: {a} {h:13s} [x:{p}]")
return
class GuiDocViewHeader(QWidget):
"""The Embedded Document Header
"""The Embedded Document Header.
Only used by DocViewer, and is at a fixed position in the
QTextBrowser's viewport.
@@ -711,8 +683,6 @@ class GuiDocViewHeader(QWidget):
logger.debug("Ready: GuiDocViewHeader")
return
##
# Methods
##
@@ -730,7 +700,6 @@ class GuiDocViewHeader(QWidget):
self.editButton.setVisible(False)
self.refreshButton.setVisible(False)
self.closeButton.setVisible(False)
return
def setOutline(self, data: dict[str, tuple[str, int]]) -> None:
"""Set the document outline dataset."""
@@ -750,13 +719,11 @@ class GuiDocViewHeader(QWidget):
lambda _, title=title: self.docViewer.navigateTo(f"#{tHandle}:{title}")
)
self._docOutline = data
return
def updateFont(self) -> None:
"""Update the font settings."""
self.setFont(SHARED.theme.guiFont)
self.itemTitle.setFont(SHARED.theme.guiFontSmall)
return
def updateTheme(self) -> None:
"""Update theme elements."""
@@ -777,8 +744,6 @@ class GuiDocViewHeader(QWidget):
self.matchColors()
return
def matchColors(self) -> None:
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
@@ -792,15 +757,13 @@ class GuiDocViewHeader(QWidget):
self.itemTitle.setTextColors(
color=palette.windowText().color(), faded=SHARED.theme.fadedText
)
return
def changeFocusState(self, state: bool) -> None:
"""Toggle focus state."""
self.itemTitle.setColorState(state)
return
def setHandle(self, tHandle: str) -> None:
"""Sets the document title from the handle, or alternatively,
"""Set the document title from the handle, or alternatively,
set the whole document path.
"""
self._docHandle = tHandle
@@ -819,13 +782,10 @@ class GuiDocViewHeader(QWidget):
self.refreshButton.setVisible(True)
self.closeButton.setVisible(True)
return
def updateNavButtons(self, firstIdx: int, lastIdx: int, currIdx: int) -> None:
"""Enable and disable nav buttons based on index in history."""
self.backButton.setEnabled(currIdx > firstIdx)
self.forwardButton.setEnabled(currIdx < lastIdx)
return
##
# Private Slots
@@ -836,20 +796,17 @@ class GuiDocViewHeader(QWidget):
"""Trigger the close editor/viewer on the main window."""
self.clearHeader()
self.docViewer.closeDocumentRequest.emit()
return
@pyqtSlot()
def _refreshDocument(self) -> None:
"""Reload the content of the document."""
self.docViewer.reloadDocumentRequest.emit()
return
@pyqtSlot()
def _editDocument(self) -> None:
"""Open the document in the editor."""
if tHandle := self._docHandle:
self.docViewer.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "", True)
return
##
# Events
@@ -861,11 +818,10 @@ class GuiDocViewHeader(QWidget):
"""
if event.button() == QtMouseLeft:
self.docViewer.requestProjectItemSelected.emit(self._docHandle, True)
return
class GuiDocViewFooter(QWidget):
"""The Embedded Document Footer
"""The Embedded Document Footer.
Only used by DocViewer, and is at a fixed position in the
QTextBrowser's viewport.
@@ -944,8 +900,6 @@ class GuiDocViewFooter(QWidget):
logger.debug("Ready: GuiDocViewFooter")
return
##
# Methods
##
@@ -956,7 +910,6 @@ class GuiDocViewFooter(QWidget):
self.showComments.setFont(SHARED.theme.guiFontSmall)
self.showSynopsis.setFont(SHARED.theme.guiFontSmall)
self.showNotes.setFont(SHARED.theme.guiFontSmall)
return
def updateTheme(self) -> None:
"""Update theme elements."""
@@ -977,8 +930,6 @@ class GuiDocViewFooter(QWidget):
self.matchColors()
return
def matchColors(self) -> None:
"""Update the colours of the widget to match those of the syntax
theme rather than the main GUI.
@@ -989,7 +940,6 @@ class GuiDocViewFooter(QWidget):
palette.setColor(QPalette.ColorRole.WindowText, syntax.text)
palette.setColor(QPalette.ColorRole.Text, syntax.text)
self.setPalette(palette)
return
##
# Private Slots
@@ -1000,18 +950,15 @@ class GuiDocViewFooter(QWidget):
"""Toggle the view comment button and reload the document."""
CONFIG.viewComments = state
self.docViewer.reloadText()
return
@pyqtSlot(bool)
def _doToggleSynopsis(self, state: bool) -> None:
"""Toggle the view synopsis button and reload the document."""
CONFIG.viewSynopsis = state
self.docViewer.reloadText()
return
@pyqtSlot(bool)
def _doToggleNotes(self, state: bool) -> None:
"""Toggle the view notes button and reload the document."""
CONFIG.viewNotes = state
self.docViewer.reloadText()
return
+5 -34
View File
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -49,6 +49,10 @@ logger = logging.getLogger(__name__)
class GuiDocViewerPanel(QWidget):
"""GUI: Document Viewer Panel.
The panel of project meta data below the viewer.
"""
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
loadDocumentTagRequest = pyqtSignal(str, Enum)
@@ -96,8 +100,6 @@ class GuiDocViewerPanel(QWidget):
logger.debug("Ready: GuiDocViewerPanel")
return
##
# Methods
##
@@ -113,7 +115,6 @@ class GuiDocViewerPanel(QWidget):
for tab in self.kwTabs.values():
tab.updateTheme()
self._loadAllTags()
return
def openProjectTasks(self) -> None:
"""Run open project tasks."""
@@ -124,7 +125,6 @@ class GuiDocViewerPanel(QWidget):
for key, value in colWidths.items():
if key in self.kwTabs and isinstance(value, list):
self.kwTabs[key].setColumnWidths(value)
return
def closeProjectTasks(self) -> None:
"""Run close project tasks."""
@@ -133,7 +133,6 @@ class GuiDocViewerPanel(QWidget):
hideInactive = self.aInactive.isChecked()
SHARED.project.options.setValue("GuiDocViewerPanel", "colWidths", colWidths)
SHARED.project.options.setValue("GuiDocViewerPanel", "hideInactive", hideInactive)
return
##
# Public Slots
@@ -145,7 +144,6 @@ class GuiDocViewerPanel(QWidget):
self.tabBackRefs.clearContent()
for cTab in self.kwTabs.values():
cTab.clearContent()
return
@pyqtSlot()
def indexHasAppeared(self) -> None:
@@ -153,7 +151,6 @@ class GuiDocViewerPanel(QWidget):
self._loadAllTags()
self._updateTabVisibility()
self.updateHandle(self._lastHandle)
return
@pyqtSlot(str, Enum)
def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
@@ -168,14 +165,12 @@ class GuiDocViewerPanel(QWidget):
else:
self.kwTabs[tClass].removeEntry(key)
self._updateTabVisibility()
return
@pyqtSlot(str)
def updateHandle(self, tHandle: str | None) -> None:
"""Update the document handle."""
self._lastHandle = tHandle
self.tabBackRefs.refreshContent(tHandle or None)
return
@pyqtSlot(list, list)
def updateChangedTags(self, updated: list[str], deleted: list[str]) -> None:
@@ -191,14 +186,12 @@ class GuiDocViewerPanel(QWidget):
else:
logger.warning("Could not remove tag '%s' from view panel", key)
self._updateTabVisibility()
return
@pyqtSlot(str)
def updateStatusLabels(self, kind: str) -> None:
"""Update the importance labels."""
if kind == "i":
self._loadAllTags()
return
##
# Private Slots
@@ -212,7 +205,6 @@ class GuiDocViewerPanel(QWidget):
cTab.clearContent()
self._loadAllTags()
self._updateTabVisibility()
return
##
# Internal Functions
@@ -222,7 +214,6 @@ class GuiDocViewerPanel(QWidget):
"""Hide class tabs with no content."""
for tClass, cTab in self.kwTabs.items():
self.mainTabs.setTabVisible(self.idTabs[tClass], cTab.countEntries() > 0)
return
def _loadAllTags(self) -> None:
"""Load all tags into the tabs."""
@@ -230,7 +221,6 @@ class GuiDocViewerPanel(QWidget):
for key, name, tClass, iItem, hItem in data:
if tClass in self.kwTabs and iItem and hItem:
self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
return
class _ViewPanelBackRefs(QTreeWidget):
@@ -278,8 +268,6 @@ class _ViewPanelBackRefs(QTreeWidget):
self.clicked.connect(self._treeItemClicked)
self.doubleClicked.connect(self._treeItemDoubleClicked)
return
def updateTheme(self) -> None:
"""Update theme elements."""
self._editIcon = SHARED.theme.getIcon("edit", "green")
@@ -288,13 +276,11 @@ class _ViewPanelBackRefs(QTreeWidget):
if item := self.topLevelItem(i):
item.setIcon(self.C_EDIT, self._editIcon)
item.setIcon(self.C_VIEW, self._viewIcon)
return
def clearContent(self) -> None:
"""Clear the widget."""
self.clear()
self._treeMap = {}
return
def refreshContent(self, dHandle: str | None) -> None:
"""Update the content."""
@@ -303,7 +289,6 @@ class _ViewPanelBackRefs(QTreeWidget):
refs = SHARED.project.index.getBackReferenceList(dHandle)
for tHandle, (sTitle, hItem) in refs.items():
self._setTreeItemValues(tHandle, sTitle, hItem)
return
def refreshDocument(self, tHandle: str) -> None:
"""Refresh document meta data."""
@@ -311,7 +296,6 @@ class _ViewPanelBackRefs(QTreeWidget):
for sTitle, hItem in iItem.items():
if f"{tHandle}:{sTitle}" in self._treeMap:
self._setTreeItemValues(tHandle, sTitle, hItem)
return
##
# Private Slots
@@ -325,7 +309,6 @@ class _ViewPanelBackRefs(QTreeWidget):
self._parent.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, "", True)
elif index.column() == self.C_VIEW:
self._parent.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", True)
return
@pyqtSlot("QModelIndex")
def _treeItemDoubleClicked(self, index: QModelIndex) -> None:
@@ -333,7 +316,6 @@ class _ViewPanelBackRefs(QTreeWidget):
tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
if index.column() not in (self.C_EDIT, self.C_VIEW):
self._parent.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, "", True)
return
##
# Internal Functions
@@ -362,8 +344,6 @@ class _ViewPanelBackRefs(QTreeWidget):
self.addTopLevelItem(trItem)
self._treeMap[tKey] = trItem
return
class _ViewPanelKeyWords(QTreeWidget):
@@ -418,14 +398,11 @@ class _ViewPanelKeyWords(QTreeWidget):
self.clicked.connect(self._treeItemClicked)
self.doubleClicked.connect(self._treeItemDoubleClicked)
return
def updateTheme(self) -> None:
"""Update theme elements."""
self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[self._class], "root")
self._editIcon = SHARED.theme.getIcon("edit", "green")
self._viewIcon = SHARED.theme.getIcon("view", "blue")
return
def countEntries(self) -> int:
"""Return the number of items in the list."""
@@ -435,7 +412,6 @@ class _ViewPanelKeyWords(QTreeWidget):
"""Clear the list."""
self._treeMap = {}
self.clear()
return
def addUpdateEntry(self, tag: str, name: str, iItem: IndexNode, hItem: IndexHeading) -> None:
"""Add a new entry, or update an existing one."""
@@ -470,8 +446,6 @@ class _ViewPanelKeyWords(QTreeWidget):
self.addTopLevelItem(trItem)
self._treeMap[tag] = trItem
return
def removeEntry(self, tag: str) -> bool:
"""Remove a tag from the list."""
if tag in self._treeMap:
@@ -487,7 +461,6 @@ class _ViewPanelKeyWords(QTreeWidget):
self.setColumnWidth(self.C_IMPORT, checkInt(widths[1], 100))
self.setColumnWidth(self.C_DOC, checkInt(widths[2], 100))
self.setColumnWidth(self.C_TITLE, checkInt(widths[3], 100))
return
def getColumnWidths(self) -> list[int]:
"""Get the widths of the user-adjustable columns."""
@@ -510,7 +483,6 @@ class _ViewPanelKeyWords(QTreeWidget):
self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.EDIT)
elif index.column() == self.C_VIEW:
self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
return
@pyqtSlot("QModelIndex")
def _treeItemDoubleClicked(self, index: QModelIndex) -> None:
@@ -518,4 +490,3 @@ class _ViewPanelKeyWords(QTreeWidget):
tag = index.siblingAtColumn(self.C_DATA).data(self.D_TAG)
if index.column() not in (self.C_EDIT, self.C_VIEW):
self._parent.loadDocumentTagRequest.emit(tag, nwDocMode.VIEW)
return
+6 -7
View File
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -42,6 +42,11 @@ logger = logging.getLogger(__name__)
class GuiTextDocument(QTextDocument):
"""Custom: Modified QTextDocument.
A special text document format that incorporates a few additional
features including spell checking.
"""
def __init__(self, parent: QObject) -> None:
super().__init__(parent=parent)
@@ -52,11 +57,8 @@ class GuiTextDocument(QTextDocument):
logger.debug("Ready: GuiTextDocument")
return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: GuiTextDocument")
return
##
# Properties
@@ -96,8 +98,6 @@ class GuiTextDocument(QTextDocument):
logger.debug("Loaded %d text blocks in %.3f ms", count, 1000*(tMid - tStart))
logger.debug("Highlighted document in %.3f ms", 1000*(tEnd - tMid))
return
def metaDataAtPos(self, pos: int) -> tuple[str, str]:
"""Check if there is meta data available at a given position in
the document, and if so, return it.
@@ -146,4 +146,3 @@ class GuiTextDocument(QTextDocument):
def setSpellCheckState(self, state: bool) -> None:
"""Set the spell check state of the syntax highlighter."""
self._syntax.setSpellCheck(state)
return
+2 -6
View File
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -43,6 +43,7 @@ logger = logging.getLogger(__name__)
class GuiItemDetails(QWidget):
"""GUI: Project Item Details Panel."""
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -190,8 +191,6 @@ class GuiItemDetails(QWidget):
logger.debug("Ready: GuiItemDetails")
return
###
# Class Methods
##
@@ -210,7 +209,6 @@ class GuiItemDetails(QWidget):
self.cCountData.clear()
self.wCountData.clear()
self.pCountData.clear()
return
def refreshDetails(self) -> None:
"""Reload the content of the details panel."""
@@ -219,7 +217,6 @@ class GuiItemDetails(QWidget):
def updateTheme(self) -> None:
"""Update theme elements."""
self.updateViewBox(self._handle)
return
def updateViewBox(self, tHandle: str | None) -> None:
"""Populate the details box from a given handle."""
@@ -283,4 +280,3 @@ class GuiItemDetails(QWidget):
self.updateViewBox(tHandle)
elif change == nwChange.DELETE:
self.updateViewBox(None)
return
+1 -25
View File
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -82,8 +82,6 @@ class GuiMainMenu(QMenuBar):
logger.debug("Ready: GuiMainMenu")
return
##
# Public Slots
##
@@ -92,7 +90,6 @@ class GuiMainMenu(QMenuBar):
def setSpellCheckState(self, state: bool) -> None:
"""Forward spell check check state to its action."""
self.aSpellCheck.setChecked(state)
return
##
# Private Slots
@@ -105,21 +102,18 @@ class GuiMainMenu(QMenuBar):
decision, just pass a None to the function and let it decide.
"""
self.mainGui.docEditor.toggleSpellCheck(None)
return
@pyqtSlot()
def _openUserManualFile(self) -> None:
"""Open the documentation in PDF format."""
if isinstance(CONFIG.pdfDocs, Path):
openExternalPath(CONFIG.pdfDocs)
return
@pyqtSlot(str)
def _changeSpelling(self, language: str) -> None:
"""Change the spell check language."""
SHARED.project.data.setSpellLang(language)
SHARED.updateSpellCheckLanguage()
return
##
# Internal Functions
@@ -188,8 +182,6 @@ class GuiMainMenu(QMenuBar):
self.aExitNW.triggered.connect(qtLambda(self.mainGui.closeMain))
self.mainGui.addAction(self.aExitNW)
return
def _buildDocumentMenu(self) -> None:
"""Assemble the Document menu."""
# Document
@@ -236,8 +228,6 @@ class GuiMainMenu(QMenuBar):
self.aImportFile = qtAddAction(self.docuMenu, self.tr("Import Text from File"))
self.aImportFile.triggered.connect(qtLambda(self.mainGui.importDocument))
return
def _buildEditMenu(self) -> None:
"""Assemble the Edit menu."""
# Edit
@@ -305,8 +295,6 @@ class GuiMainMenu(QMenuBar):
)
self.mainGui.addAction(self.aSelectPar)
return
def _buildViewMenu(self) -> None:
"""Assemble the View menu."""
# View
@@ -367,8 +355,6 @@ class GuiMainMenu(QMenuBar):
self.aFullScreen.triggered.connect(self.mainGui.toggleFullScreenMode)
self.mainGui.addAction(self.aFullScreen)
return
def _buildInsertMenu(self) -> None:
"""Assemble the Insert menu."""
# Insert
@@ -646,8 +632,6 @@ class GuiMainMenu(QMenuBar):
lambda: self.requestDocInsert.emit(nwDocInsert.FOOTNOTE)
)
return
def _buildFormatMenu(self) -> None:
"""Assemble the Format menu."""
# Format
@@ -901,8 +885,6 @@ class GuiMainMenu(QMenuBar):
lambda: self.requestDocAction.emit(nwDocAction.RM_BREAKS)
)
return
def _buildSearchMenu(self) -> None:
"""Assemble the Search menu."""
# Search
@@ -948,8 +930,6 @@ class GuiMainMenu(QMenuBar):
self.aFindProj.setShortcut("Ctrl+Shift+F")
self.aFindProj.triggered.connect(qtLambda(self.requestViewChange.emit, nwView.SEARCH))
return
def _buildToolsMenu(self) -> None:
"""Assemble the Tools menu."""
# Tools
@@ -1019,8 +999,6 @@ class GuiMainMenu(QMenuBar):
self.aPreferences.triggered.connect(self.mainGui.showPreferencesDialog)
self.mainGui.addAction(self.aPreferences)
return
def _buildHelpMenu(self) -> None:
"""Assemble the Help menu."""
# Help
@@ -1066,5 +1044,3 @@ class GuiMainMenu(QMenuBar):
# Document > Main Website
self.aWebsite = qtAddAction(self.helpMenu, self.tr("The novelWriter Website"))
self.aWebsite.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_WEB))
return
+7 -46
View File
@@ -24,7 +24,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -55,6 +55,7 @@ logger = logging.getLogger(__name__)
class GuiNovelView(QWidget):
"""GUI: Novel View Panel."""
# Signals for user interaction with the novel tree
selectedItemChanged = pyqtSignal(str)
@@ -82,8 +83,6 @@ class GuiNovelView(QWidget):
self.getSelectedHandle = self.novelTree.getSelectedHandle
self.refreshCurrentTree = self.novelBar.forceRefreshNovelTree
return
##
# Methods
##
@@ -91,19 +90,16 @@ class GuiNovelView(QWidget):
def updateTheme(self) -> None:
"""Update theme elements."""
self.novelBar.updateTheme()
return
def initSettings(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
self.novelTree.initSettings()
return
def clearNovelView(self) -> None:
"""Clear project-related GUI content."""
self.novelBar.clearContent()
self.novelBar.setEnabled(False)
self.novelTree.clearContent()
return
def openProjectTasks(self) -> None:
"""Run open project tasks."""
@@ -125,8 +121,6 @@ class GuiNovelView(QWidget):
self.novelTree.setLastColSize(lastColSize)
return
def closeProjectTasks(self) -> None:
"""Run closing project tasks."""
logger.debug("Saving State: GuiNovelView")
@@ -140,12 +134,9 @@ class GuiNovelView(QWidget):
self.clearNovelView()
return
def setTreeFocus(self) -> None:
"""Set the focus to the tree widget."""
self.novelTree.setFocus()
return
def treeHasFocus(self) -> bool:
"""Check if the novel tree has focus."""
@@ -159,22 +150,20 @@ class GuiNovelView(QWidget):
def setCurrentNovel(self, rootHandle: str | None) -> None:
"""Set the current novel to display."""
self.novelTree.setNovelModel(rootHandle)
return
@pyqtSlot(str)
def setActiveHandle(self, tHandle: str) -> None:
"""Highlight the rows associated with a given handle."""
self.novelTree.setActiveHandle(tHandle)
return
@pyqtSlot(str, Enum)
def updateRootItem(self, tHandle: str, change: nwChange) -> None:
"""If any root item changes, rebuild the novel root menu."""
self.novelBar.buildNovelRootMenu()
return
class GuiNovelToolBar(QWidget):
"""GUI: Novel View Panel ToolBar."""
def __init__(self, novelView: GuiNovelView) -> None:
super().__init__(parent=novelView)
@@ -249,8 +238,6 @@ class GuiNovelToolBar(QWidget):
logger.debug("Ready: GuiNovelToolBar")
return
##
# Methods
##
@@ -277,20 +264,16 @@ class GuiNovelToolBar(QWidget):
self.forceRefreshNovelTree()
return
def clearContent(self) -> None:
"""Run clearing project tasks."""
self.novelValue.clear()
self.novelValue.setToolTip("")
return
def buildNovelRootMenu(self) -> None:
"""Build the novel root menu."""
self.novelValue.refreshNovelList()
self.novelView.setCurrentNovel(self.novelValue.handle)
self.tbNovel.setVisible(self.novelValue.count() > 1)
return
def setCurrentRoot(self, rootHandle: str | None) -> None:
"""Set the current active root handle."""
@@ -300,7 +283,6 @@ class GuiNovelToolBar(QWidget):
SHARED.project.data.setLastHandle(rootHandle, "novel")
self.novelView.setCurrentNovel(rootHandle)
self.novelView.novelTree.setAccessibleName(self.novelValue.currentText())
return
def setLastColType(self, colType: nwNovelExtra, doRefresh: bool = True) -> None:
"""Set the last column type."""
@@ -309,7 +291,6 @@ class GuiNovelToolBar(QWidget):
if doRefresh:
self.forceRefreshNovelTree()
self.novelView.novelTree.resizeColumns()
return
def setActive(self, state: bool) -> None:
"""Set the widget active state, which enables automatic tree
@@ -322,7 +303,6 @@ class GuiNovelToolBar(QWidget):
and self._refresh.get(handle, False)
):
self._refreshNovelTree(self.novelValue.handle)
return
##
# Public Slots
@@ -335,7 +315,6 @@ class GuiNovelToolBar(QWidget):
self.novelView.setCurrentNovel(tHandle)
SHARED.project.index.refreshNovelModel(tHandle)
self._refresh[tHandle] = False
return
##
# Private Slots
@@ -349,7 +328,6 @@ class GuiNovelToolBar(QWidget):
self._refresh[tHandle] = False
else:
self._refresh[tHandle] = True
return
@pyqtSlot()
def _selectLastColumnSize(self) -> None:
@@ -361,7 +339,6 @@ class GuiNovelToolBar(QWidget):
if isOk:
self.novelView.novelTree.setLastColSize(newSize)
self.novelView.novelTree.resizeColumns()
return
##
# Internal Functions
@@ -374,10 +351,10 @@ class GuiNovelToolBar(QWidget):
aLast.setActionGroup(self.gLastCol)
aLast.triggered.connect(qtLambda(self.setLastColType, colType))
self.aLastCol[colType] = aLast
return
class GuiNovelTree(NTreeView):
"""GUI: Novel View Panel Tree."""
def __init__(self, novelView: GuiNovelView) -> None:
super().__init__(parent=novelView)
@@ -414,8 +391,6 @@ class GuiNovelTree(NTreeView):
logger.debug("Ready: GuiNovelTree")
return
def initSettings(self) -> None:
"""Set or update tree widget settings."""
if CONFIG.hideVScroll:
@@ -426,7 +401,6 @@ class GuiNovelTree(NTreeView):
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
else:
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
return
##
# Properties
@@ -466,25 +440,21 @@ class GuiNovelTree(NTreeView):
self.resizeColumns()
else:
self.clearContent()
return
def setActiveHandle(self, tHandle: str | None) -> None:
"""Set the handle to be highlighted."""
self._actHandle = tHandle
if viewport := self.viewport():
viewport.repaint()
return
def setLastColType(self, colType: nwNovelExtra) -> None:
"""Set the extra column type."""
self._lastColType = colType
SHARED.project.index.setNovelModelExtraColumn(colType)
return
def setLastColSize(self, colSize: int) -> None:
"""Set the extra column size between 15% and 75%."""
self._lastColSize = minmax(colSize, 15, 75)/100.0
return
##
# Class Methods
@@ -493,7 +463,6 @@ class GuiNovelTree(NTreeView):
def clearContent(self) -> None:
"""Clear the tree view."""
self.setModel(None)
return
def resizeColumns(self) -> None:
"""Set the correct column sizes."""
@@ -506,7 +475,6 @@ class GuiNovelTree(NTreeView):
if model.columns == 4:
header.setSectionResizeMode(3, QtHeaderToContents)
header.setMaximumSectionSize(int(self._lastColSize * vp.width()))
return
##
# Overloads
@@ -517,7 +485,6 @@ class GuiNovelTree(NTreeView):
if (model := self._getModel()) and model.handle(index) == self._actHandle:
painter.fillRect(opt.rect, self.palette().alternateBase())
super().drawRow(painter, opt, index)
return
##
# Events
@@ -527,7 +494,6 @@ class GuiNovelTree(NTreeView):
"""Process size changed."""
super().resizeEvent(event)
self.resizeColumns()
return
##
# Private Slots
@@ -535,36 +501,33 @@ class GuiNovelTree(NTreeView):
@pyqtSlot(QModelIndex)
def _onSingleClick(self, index: QModelIndex) -> None:
"""The user single-clicked an index."""
"""Process user single-click on an index."""
if index.isValid() and (model := self._getModel()):
if (tHandle := model.handle(index)) and (sTitle := model.key(index)):
self.novelView.selectedItemChanged.emit(tHandle)
if index.column() == model.columnCount(index) - 1:
pos = self.mapToGlobal(self.visualRect(index).topRight())
self._popMetaBox(pos, tHandle, sTitle)
return
@pyqtSlot(QModelIndex)
def _onDoubleClick(self, index: QModelIndex) -> None:
"""The user double-clicked an index."""
"""Process user double-click on an index."""
if (
(model := self._getModel())
and (tHandle := model.handle(index))
and (sTitle := model.key(index))
):
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle, False)
return
@pyqtSlot(QModelIndex)
def _onMiddleClick(self, index: QModelIndex) -> None:
"""The user middle-clicked an index."""
"""Process user middle-click on an index."""
if (
(model := self._getModel())
and (tHandle := model.handle(index))
and (sTitle := model.key(index))
):
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle, False)
return
##
# Internal Functions
@@ -582,7 +545,6 @@ class GuiNovelTree(NTreeView):
"""Generate a reference list for a given reference key."""
if tags := ", ".join(refs.get(key, [])):
lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}:</b> {tags}")
return
if head := SHARED.project.index.getItemHeading(tHandle, sTitle):
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
@@ -610,4 +572,3 @@ class GuiNovelTree(NTreeView):
text = f"<p>{refs}</p>"
if tooltip := (text + synopsis or self.tr("No meta data")):
QToolTip.showText(qPos, tooltip)
return
+10 -57
View File
@@ -24,7 +24,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import csv
@@ -58,6 +58,7 @@ logger = logging.getLogger(__name__)
class GuiOutlineView(QWidget):
"""GUI: Project Outline Panel."""
loadDocumentTagRequest = pyqtSignal(str, Enum)
openDocumentRequest = pyqtSignal(str, Enum, str, bool)
@@ -96,8 +97,6 @@ class GuiOutlineView(QWidget):
# Function Mappings
self.getSelectedHandle = self.outlineTree.getSelectedHandle
return
##
# Methods
##
@@ -109,24 +108,20 @@ class GuiOutlineView(QWidget):
self.outlineTree.refreshTree(
rootHandle=SHARED.project.data.getLastHandle("outline"), overRide=True
)
return
def initSettings(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
self.outlineTree.initSettings()
self.outlineData.initSettings()
return
def refreshTree(self) -> None:
"""Refresh the current tree."""
self.outlineTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("outline"))
return
def clearOutline(self) -> None:
"""Clear project-related GUI content."""
self.outlineData.clearDetails()
self.outlineBar.setEnabled(False)
return
def openProjectTasks(self) -> None:
"""Run open project tasks."""
@@ -142,8 +137,6 @@ class GuiOutlineView(QWidget):
self.outlineBar.setEnabled(True)
self.outlineData.loadGuiSettings()
return
def closeProjectTasks(self) -> None:
"""Run closing project tasks."""
if self.outlineTree.wasRendered:
@@ -152,7 +145,6 @@ class GuiOutlineView(QWidget):
self.outlineTree.closeProjectTasks()
self.outlineData.updateClasses()
self.clearOutline()
return
def splitSizes(self) -> list[int]:
"""Get the sizes of the splitter widget."""
@@ -175,7 +167,6 @@ class GuiOutlineView(QWidget):
"""Handle tasks whenever a root folders changes."""
self.outlineBar.populateNovelList()
self.outlineData.updateClasses()
return
##
# Private Slots
@@ -188,23 +179,21 @@ class GuiOutlineView(QWidget):
of columns has changed.
"""
self.outlineBar.setColumnHiddenState(self.outlineTree.hiddenColumns)
return
@pyqtSlot(str)
def _tagClicked(self, link: str) -> None:
"""Capture the click of a tag in the details panel."""
if link:
self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW)
return
@pyqtSlot(str)
def _rootItemChanged(self, tHandle: str) -> None:
"""Handle root novel changed or needs to be refreshed."""
self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True)
return
class GuiOutlineToolBar(QToolBar):
"""GUI: Project Outline Panel ToolBar."""
loadNovelRootRequest = pyqtSignal(str)
outlineExportRequest = pyqtSignal()
@@ -263,8 +252,6 @@ class GuiOutlineToolBar(QToolBar):
logger.debug("Ready: GuiOutlineToolBar")
return
##
# Methods
##
@@ -278,22 +265,18 @@ class GuiOutlineToolBar(QToolBar):
self.tbColumns.setIcon(SHARED.theme.getIcon("more_vertical"))
self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}")
self.novelLabel.setTextColors(color=self.palette().windowText().color())
return
def populateNovelList(self) -> None:
"""Reload the content of the novel list."""
self.novelValue.refreshNovelList()
return
def setCurrentRoot(self, rootHandle: str | None) -> None:
"""Set the current active root handle."""
self.novelValue.setHandle(rootHandle)
return
def setColumnHiddenState(self, hiddenState: dict[nwOutline, bool]) -> None:
"""Forward the change of column hidden states to the menu."""
self.mColumns.setHiddenState(hiddenState)
return
##
# Private Slots
@@ -303,22 +286,20 @@ class GuiOutlineToolBar(QToolBar):
def _novelValueChanged(self, tHandle: str) -> None:
"""Emit a signal containing the handle of the selected item."""
self.loadNovelRootRequest.emit(tHandle)
return
@pyqtSlot()
def _refreshRequested(self) -> None:
"""Emit a signal containing the handle of the selected item."""
self.loadNovelRootRequest.emit(self.novelValue.handle)
return
@pyqtSlot()
def _exportRequested(self) -> None:
"""Emit a signal that an export of the outline was requested."""
self.outlineExportRequest.emit()
return
class GuiOutlineTree(QTreeWidget):
"""GUI: Project Outline Panel Tree."""
DEF_WIDTH: Final[dict[nwOutline, int]] = {
nwOutline.TITLE: 200,
@@ -422,8 +403,6 @@ class GuiOutlineTree(QTreeWidget):
logger.debug("Ready: GuiOutlineTree")
return
##
# Properties
##
@@ -451,7 +430,6 @@ class GuiOutlineTree(QTreeWidget):
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
else:
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
return
def clearContent(self) -> None:
"""Clear the tree and header and set the default values for the
@@ -474,8 +452,6 @@ class GuiOutlineTree(QTreeWidget):
self._treeNCols = len(self._treeOrder)
return
def updateTheme(self) -> None:
"""Update theme elements."""
iType = nwItemType.FILE
@@ -488,15 +464,14 @@ class GuiOutlineTree(QTreeWidget):
"H3": SHARED.theme.getItemIcon(iType, iClass, iLayout, "H3"),
"H4": SHARED.theme.getItemIcon(iType, iClass, iLayout, "H4"),
}
return
def refreshTree(
self, rootHandle: str | None = None,
overRide: bool = False, novelChanged: bool = False
) -> None:
"""Called whenever the Outline tab is activated and controls
what data to load, and if necessary, force a rebuild of the
tree.
"""Refresh the outline tree. Called whenever the Outline tab is
activated and controls what data to load, and if necessary,
force a rebuild of the tree.
"""
# If it's the first time, we always build
if self._firstView or (self._firstView and overRide):
@@ -518,11 +493,10 @@ class GuiOutlineTree(QTreeWidget):
return
def closeProjectTasks(self) -> None:
"""Called before a project is closed."""
"""Call before a project is closed."""
self._saveHeaderState()
self.clearContent()
self._firstView = True
return
def getSelectedHandle(self) -> tuple[str | None, str | None]:
"""Get the currently selected handle. If multiple items are
@@ -546,7 +520,6 @@ class GuiOutlineTree(QTreeWidget):
if hItem in self._colIdx:
self.setColumnHidden(self._colIdx[hItem], not isChecked)
self._saveHeaderState()
return
@pyqtSlot()
def exportOutline(self) -> None:
@@ -562,7 +535,6 @@ class GuiOutlineTree(QTreeWidget):
writer.writerows(
self._dumpNovelData(self.outlineView.outlineBar.novelValue.handle)
)
return
##
# Private Slots
@@ -577,7 +549,6 @@ class GuiOutlineTree(QTreeWidget):
tHandle, sTitle = self.getSelectedHandle()
if tHandle:
self.outlineView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "", True)
return
@pyqtSlot()
def _onItemSelectionChanged(self) -> None:
@@ -588,7 +559,6 @@ class GuiOutlineTree(QTreeWidget):
tHandle = items[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
sTitle = items[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
self.activeItemChanged.emit(tHandle, sTitle)
return
@pyqtSlot(int, int, int)
def _columnMoved(self, logIdx: int, oldVisualIdx: int, newVisualIdx: int) -> None:
@@ -597,7 +567,6 @@ class GuiOutlineTree(QTreeWidget):
"""
self._treeOrder.insert(newVisualIdx, self._treeOrder.pop(oldVisualIdx))
self._saveHeaderState()
return
##
# Internal Functions
@@ -637,8 +606,6 @@ class GuiOutlineTree(QTreeWidget):
self.hiddenStateChanged.emit()
return
def _saveHeaderState(self) -> None:
"""Save the state of the main tree header, that is, column
order, column width and column hidden state. We don't want to
@@ -661,7 +628,6 @@ class GuiOutlineTree(QTreeWidget):
pOptions = SHARED.project.options
pOptions.setValue("GuiOutline", "columnState", colState)
pOptions.saveSettings()
return
def _populateTree(self, rootHandle: str | None) -> None:
"""Build the tree based on the project index, and the header
@@ -746,8 +712,6 @@ class GuiOutlineTree(QTreeWidget):
self._lastBuild = time()
logger.debug("Project outline built in %.3f ms", 1000.0*(time() - tStart))
return
def _dumpNovelData(self, rootHandle: str | None) -> list[list[str | int]]:
"""Dump all novel data into a table."""
sLabel = SHARED.project.localLookup("Story Structure")
@@ -821,6 +785,7 @@ class GuiOutlineTree(QTreeWidget):
class GuiOutlineHeaderMenu(QMenu):
"""GUI: Project Outline Panel Header Selection Menu."""
columnToggled = pyqtSignal(bool, Enum)
@@ -844,8 +809,6 @@ class GuiOutlineHeaderMenu(QMenu):
)
self.addAction(self.actionMap[hItem])
return
def setHiddenState(self, hiddenState: dict[nwOutline, bool]) -> None:
"""Overwrite the checked state of the columns as the inverse of
the hidden state. Skip the TITLE column as it cannot be hidden.
@@ -859,10 +822,9 @@ class GuiOutlineHeaderMenu(QMenu):
self.acceptToggle = True
return
class GuiOutlineDetails(QScrollArea):
"""GUI: Project Outline Panel Details View."""
LVL_MAP: Final[dict[str, str]] = {
"H1": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"),
@@ -1007,8 +969,6 @@ class GuiOutlineDetails(QScrollArea):
logger.debug("Ready: GuiOutlineDetails")
return
def initSettings(self) -> None:
"""Set or update outline settings."""
if CONFIG.hideVScroll:
@@ -1020,7 +980,6 @@ class GuiOutlineDetails(QScrollArea):
else:
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
self.updateClasses()
return
def loadGuiSettings(self) -> None:
"""Run open project tasks."""
@@ -1031,7 +990,6 @@ class GuiOutlineDetails(QScrollArea):
pOptions.getInt("GuiOutlineDetails", "detailsWidth", width//3),
pOptions.getInt("GuiOutlineDetails", "tagsWidth", 2*width//3),
])
return
def saveGuiSettings(self) -> None:
"""Run close project tasks."""
@@ -1040,7 +998,6 @@ class GuiOutlineDetails(QScrollArea):
pOptions = SHARED.project.options
pOptions.setValue("GuiOutlineDetails", "detailsWidth", mainSplit[0])
pOptions.setValue("GuiOutlineDetails", "tagsWidth", mainSplit[1])
return
def clearDetails(self) -> None:
"""Clear all the data labels."""
@@ -1057,7 +1014,6 @@ class GuiOutlineDetails(QScrollArea):
value.clear()
self.updateClasses()
return
##
# Slots
@@ -1090,8 +1046,6 @@ class GuiOutlineDetails(QScrollArea):
for key, (_, value) in self.tagValues.items():
value.setText(self._formatTags(novRefs, key))
return
@pyqtSlot()
def updateClasses(self) -> None:
"""Update the visibility status of class details."""
@@ -1102,7 +1056,6 @@ class GuiOutlineDetails(QScrollArea):
label, value = self.tagValues[key]
label.setVisible(visible)
value.setVisible(visible)
return
@staticmethod
def _formatTags(refs: dict[str, list[str]], key: str) -> str:
+9 -88
View File
@@ -25,7 +25,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -61,7 +61,9 @@ logger = logging.getLogger(__name__)
class GuiProjectView(QWidget):
"""This is a wrapper class holding all the elements of the project
"""GUI: Project View.
This is a wrapper class holding all the elements of the project
tree. The core object is the project tree itself. Most methods
available are mapped through to the project tree class.
"""
@@ -130,8 +132,6 @@ class GuiProjectView(QWidget):
# Function Mappings
self.getSelectedHandle = self.projTree.getSelectedHandle
return
##
# Methods
##
@@ -139,19 +139,16 @@ class GuiProjectView(QWidget):
def updateTheme(self) -> None:
"""Update theme elements."""
self.projBar.updateTheme()
return
def initSettings(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
self.projTree.initSettings()
return
def closeProjectTasks(self) -> None:
"""Clear project-related GUI content."""
self.projBar.clearContent()
self.projBar.setEnabled(False)
self.projTree.clearTree()
return
def openProjectTasks(self) -> None:
"""Run open project tasks."""
@@ -159,26 +156,23 @@ class GuiProjectView(QWidget):
self.projBar.buildTemplatesMenu()
self.projBar.buildQuickLinksMenu()
self.projBar.setEnabled(True)
return
def setTreeFocus(self) -> None:
"""Forward the set focus call to the tree widget."""
self.projTree.setFocus()
return
def treeHasFocus(self) -> bool:
"""Check if the project tree has focus."""
return self.projTree.hasFocus()
def connectMenuActions(self, rename: QAction, delete: QAction, trash: QAction) -> None:
"""Main menu actions passed to the project tree."""
"""Connect main menu actions passed to the project tree."""
self.projTree.addAction(rename)
self.projTree.addAction(delete)
self.projTree.addAction(trash)
rename.triggered.connect(self.renameTreeItem)
delete.triggered.connect(self.projTree.processDeleteRequest)
trash.triggered.connect(self.projTree.emptyTrash)
return
##
# Public Slots
@@ -197,41 +191,36 @@ class GuiProjectView(QWidget):
if dlgOk:
nwItem.setName(newLabel)
nwItem.notifyToRefresh()
return
@pyqtSlot(str, bool)
def setSelectedHandle(self, tHandle: str, doScroll: bool = False) -> None:
"""Select an item and optionally scroll it into view."""
self.projTree.setSelectedHandle(tHandle, doScroll=doScroll)
return
@pyqtSlot(str)
def setActiveHandle(self, tHandle: str | None) -> None:
"""Highlight the active handle."""
self.projTree.setActiveHandle(tHandle)
return
@pyqtSlot(str, Enum)
def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
"""Refresh other content when project item changed."""
self.projBar.processTemplateDocuments(tHandle)
return
@pyqtSlot(str)
def createFileFromTemplate(self, tHandle: str) -> None:
"""Create a new document from a template."""
logger.debug("Template selected: '%s'", tHandle)
self.projTree.newTreeItem(nwItemType.FILE, copyDoc=tHandle)
return
@pyqtSlot(str, Enum)
def updateRootItem(self, tHandle: str, change: nwChange) -> None:
"""Process root item changes."""
self.projBar.buildQuickLinksMenu()
return
class GuiProjectToolBar(QWidget):
"""GUI> Project View ToolBar."""
newDocumentFromTemplate = pyqtSignal(str)
@@ -351,8 +340,6 @@ class GuiProjectToolBar(QWidget):
logger.debug("Ready: GuiProjectToolBar")
return
##
# Methods
##
@@ -383,13 +370,10 @@ class GuiProjectToolBar(QWidget):
self.buildQuickLinksMenu()
self._buildRootMenu()
return
def clearContent(self) -> None:
"""Clear dynamic content on the tool bar."""
self.mQuick.clear()
self.mTemplates.clearMenu()
return
def buildQuickLinksMenu(self) -> None:
"""Build the quick link menu."""
@@ -402,14 +386,12 @@ class GuiProjectToolBar(QWidget):
action.triggered.connect(
qtLambda(self.projView.setSelectedHandle, tHandle, doScroll=True)
)
return
def buildTemplatesMenu(self) -> None:
"""Build the templates menu."""
for tHandle, _ in SHARED.project.tree.iterRoots(nwItemClass.TEMPLATE):
for dHandle in SHARED.project.tree.subTree(tHandle):
self.processTemplateDocuments(dHandle)
return
def processTemplateDocuments(self, tHandle: str) -> None:
"""Process change in tree items to update menu content."""
@@ -418,7 +400,6 @@ class GuiProjectToolBar(QWidget):
self.mTemplates.addUpdate(tHandle, item.itemName, item.getMainIcon())
elif tHandle in self.mTemplates:
self.mTemplates.remove(tHandle)
return
##
# Public Slots
@@ -436,7 +417,6 @@ class GuiProjectToolBar(QWidget):
self.aAddChap.setVisible(allowDoc)
self.aAddPart.setVisible(allowDoc)
self.aAddEmpty.setVisible(allowDoc)
return
##
# Internal Functions
@@ -451,7 +431,6 @@ class GuiProjectToolBar(QWidget):
qtLambda(self.projTree.newTreeItem, nwItemType.ROOT, itemClass)
)
self.mAddRoot.addAction(aNew)
return
self.mAddRoot.clear()
addClass(nwItemClass.NOVEL)
@@ -467,10 +446,9 @@ class GuiProjectToolBar(QWidget):
addClass(nwItemClass.ARCHIVE)
addClass(nwItemClass.TEMPLATE)
return
class GuiProjectTree(QTreeView):
"""GUI: Project View Tree."""
def __init__(self, projView: GuiProjectView) -> None:
super().__init__(parent=projView)
@@ -520,8 +498,6 @@ class GuiProjectTree(QTreeView):
logger.debug("Ready: GuiProjectTree")
return
def initSettings(self) -> None:
"""Set or update tree widget settings."""
if CONFIG.hideVScroll:
@@ -532,7 +508,6 @@ class GuiProjectTree(QTreeView):
self.setHorizontalScrollBarPolicy(QtScrollAlwaysOff)
else:
self.setHorizontalScrollBarPolicy(QtScrollAsNeeded)
return
##
# External Methods
@@ -541,7 +516,6 @@ class GuiProjectTree(QTreeView):
def setActiveHandle(self, tHandle: str | None) -> None:
"""Set the handle to be highlighted."""
self._actHandle = tHandle
return
def getSelectedHandle(self) -> str | None:
"""Get the currently selected handle."""
@@ -556,7 +530,6 @@ class GuiProjectTree(QTreeView):
def clearTree(self) -> None:
"""Clear the tree view."""
self.setModel(None)
return
def loadModel(self) -> None:
"""Load and prepare a new project model."""
@@ -583,8 +556,6 @@ class GuiProjectTree(QTreeView):
self.restoreExpandedState()
return
def restoreExpandedState(self) -> None:
"""Expand all nodes that were previously expanded."""
if model := self._getModel():
@@ -592,7 +563,6 @@ class GuiProjectTree(QTreeView):
for index in model.allExpanded():
self.setExpanded(index, True)
self.blockSignals(False)
return
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
"""Set a specific handle as the selected item."""
@@ -601,7 +571,6 @@ class GuiProjectTree(QTreeView):
if doScroll:
self.scrollTo(index, QAbstractItemView.ScrollHint.PositionAtCenter)
self.projView.selectedItemChanged.emit(tHandle)
return
def newTreeItem(
self, itemType: nwItemType, itemClass: nwItemClass | None = None,
@@ -805,7 +774,6 @@ class GuiProjectTree(QTreeView):
SHARED.warn(self.tr("Could not duplicate all items."))
self.setEnabled(True)
self.restoreExpandedState()
return
##
# Events and Overloads
@@ -825,14 +793,12 @@ class GuiProjectTree(QTreeView):
self.projView.openDocumentRequest.emit(
node.item.itemHandle, nwDocMode.VIEW, "", False
)
return
def drawRow(self, painter: QPainter, opt: QStyleOptionViewItem, index: QModelIndex) -> None:
"""Draw a box on the active row."""
if (node := self._getNode(index)) and node.item.itemHandle == self._actHandle:
painter.fillRect(opt.rect, self.palette().alternateBase())
super().drawRow(painter, opt, index)
return
##
# Public Slots
@@ -843,14 +809,12 @@ class GuiProjectTree(QTreeView):
"""Move an item up in the tree."""
if model := self._getModel():
model.internalMove(self.currentIndex(), -1)
return
@pyqtSlot()
def moveItemDown(self) -> None:
"""Move an item down in the tree."""
if model := self._getModel():
model.internalMove(self.currentIndex(), 1)
return
@pyqtSlot()
def goToSiblingUp(self) -> None:
@@ -858,7 +822,6 @@ class GuiProjectTree(QTreeView):
if (node := self._getNode(self.currentIndex())) and (parent := node.parent()):
if (move := parent.child(node.row() - 1)) and (model := self._getModel()):
self.setCurrentIndex(model.indexFromNode(move))
return
@pyqtSlot()
def goToSiblingDown(self) -> None:
@@ -866,7 +829,6 @@ class GuiProjectTree(QTreeView):
if (node := self._getNode(self.currentIndex())) and (parent := node.parent()):
if (move := parent.child(node.row() + 1)) and (model := self._getModel()):
self.setCurrentIndex(model.indexFromNode(move))
return
@pyqtSlot()
def goToParent(self) -> None:
@@ -877,7 +839,6 @@ class GuiProjectTree(QTreeView):
and (parent := node.parent())
):
self.setCurrentIndex(model.indexFromNode(parent))
return
@pyqtSlot()
def goToFirstChild(self) -> None:
@@ -888,13 +849,11 @@ class GuiProjectTree(QTreeView):
and (child := node.child(0))
):
self.setCurrentIndex(model.indexFromNode(child))
return
@pyqtSlot(QModelIndex)
def expandFromIndex(self, index: QModelIndex) -> None:
"""Expand all nodes from index."""
self.expandRecursively(index)
return
@pyqtSlot(QModelIndex)
def collapseFromIndex(self, index: QModelIndex) -> None:
@@ -902,7 +861,6 @@ class GuiProjectTree(QTreeView):
if (model := self._getModel()) and (node := model.node(index)):
for child in node.allChildren():
self.setExpanded(model.indexFromNode(child), False)
return
@pyqtSlot()
def processDeleteRequest(
@@ -968,9 +926,7 @@ class GuiProjectTree(QTreeView):
@pyqtSlot()
@pyqtSlot("QPoint")
def openContextMenu(self, point: QPoint | None = None) -> None:
"""The user right clicked an element in the project tree, so we
open a context menu in-place.
"""
"""Open a context menu in-place where the user clicked."""
if model := self._getModel():
if point is None:
point = self.visualRect(self.currentIndex()).center()
@@ -987,7 +943,6 @@ class GuiProjectTree(QTreeView):
if viewport := self.viewport():
ctxMenu.exec(viewport.mapToGlobal(point))
ctxMenu.setParent(None)
return
##
# Private Slots
@@ -995,10 +950,9 @@ class GuiProjectTree(QTreeView):
@pyqtSlot(QModelIndex, QModelIndex)
def _onSelectionChange(self, current: QModelIndex, previous: QModelIndex) -> None:
"""The user changed which item is selected."""
"""Process user changing which item is selected."""
if node := self._getNode(current):
self.projView.selectedItemChanged.emit(node.item.itemHandle)
return
@pyqtSlot(QModelIndex)
def _onDoubleClick(self, index: QModelIndex) -> None:
@@ -1012,21 +966,18 @@ class GuiProjectTree(QTreeView):
)
else:
self.setExpanded(index, not self.isExpanded(index))
return
@pyqtSlot(QModelIndex)
def _onNodeCollapsed(self, index: QModelIndex) -> None:
"""Capture a node collapse, and pass it to the model."""
if node := self._getNode(index):
node.setExpanded(False)
return
@pyqtSlot(QModelIndex)
def _onNodeExpanded(self, index: QModelIndex) -> None:
"""Capture a node expand, and pass it to the model."""
if node := self._getNode(index):
node.setExpanded(True)
return
##
# Internal Functions
@@ -1038,7 +989,6 @@ class GuiProjectTree(QTreeView):
if model := self.selectionModel():
# Selection model can be None (#2173)
model.clearCurrentIndex()
return
def _selectedRows(self) -> list[QModelIndex]:
"""Return all column 0 indexes."""
@@ -1066,7 +1016,6 @@ class _UpdatableMenu(QMenu):
self._map: dict[str, QAction] = {}
self.setTitle(self.tr("From Template"))
self.triggered.connect(self._actionTriggered)
return
def __contains__(self, tHandle: str) -> bool:
"""Look up a handle in the menu."""
@@ -1088,7 +1037,6 @@ class _UpdatableMenu(QMenu):
self.addAction(action)
self._map[tHandle] = action
self.setActionsVisible(True)
return
def remove(self, tHandle: str) -> None:
"""Remove a template item."""
@@ -1096,19 +1044,16 @@ class _UpdatableMenu(QMenu):
self.removeAction(action)
if not self._map:
self.setActionsVisible(False)
return
def clearMenu(self) -> None:
"""Clear all menu content."""
self._map.clear()
self.clear()
return
def setActionsVisible(self, value: bool) -> None:
"""Set the visibility of root action."""
if action := self.menuAction():
action.setVisible(value)
return
##
# Private Slots
@@ -1118,7 +1063,6 @@ class _UpdatableMenu(QMenu):
def _actionTriggered(self, action: QAction) -> None:
"""Translate the menu trigger into an item trigger."""
self.menuItemTriggered.emit(str(action.data()))
return
class _TreeContextMenu(QMenu):
@@ -1139,11 +1083,9 @@ class _TreeContextMenu(QMenu):
self._indices = indices
self._children = node.childCount() > 0
logger.debug("Ready: _TreeContextMenu")
return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: _TreeContextMenu")
return
##
# Methods
@@ -1155,7 +1097,6 @@ class _TreeContextMenu(QMenu):
action.triggered.connect(self._tree.emptyTrash)
if self._children:
self._expandCollapse()
return
def buildSingleSelectMenu(self) -> None:
"""Build the single-select menu."""
@@ -1191,15 +1132,12 @@ class _TreeContextMenu(QMenu):
action.triggered.connect(qtLambda(self._tree.duplicateFromHandle, self._handle))
self._deleteOrTrash()
return
def buildMultiSelectMenu(self) -> None:
"""Build the multi-select menu."""
self._itemActive()
self._itemStatusImport(True)
self.addSeparator()
self._deleteOrTrash()
return
##
# Menu Builders
@@ -1217,7 +1155,6 @@ class _TreeContextMenu(QMenu):
self._view.openDocumentRequest.emit,
self._handle, nwDocMode.VIEW, "", False
))
return
def _itemCreation(self) -> None:
"""Add create item actions."""
@@ -1228,7 +1165,6 @@ class _TreeContextMenu(QMenu):
menu.addAction(self._view.projBar.aAddEmpty)
menu.addAction(self._view.projBar.aAddNote)
menu.addAction(self._view.projBar.aAddFolder)
return
def _itemHeader(self) -> None:
"""Check if there is a header that can be used for rename."""
@@ -1238,7 +1174,6 @@ class _TreeContextMenu(QMenu):
action.triggered.connect(
qtLambda(self._view.renameTreeItem, self._handle, hItem.title)
)
return
def _itemActive(self) -> None:
"""Add Active/Inactive actions."""
@@ -1253,7 +1188,6 @@ class _TreeContextMenu(QMenu):
else:
action = qtAddAction(self, self.tr("Toggle Active"))
action.triggered.connect(self._toggleItemActive)
return
def _itemStatusImport(self, multi: bool) -> None:
"""Add actions for changing status or importance."""
@@ -1295,7 +1229,6 @@ class _TreeContextMenu(QMenu):
self._view.projectSettingsRequest.emit,
GuiProjectSettings.PAGE_IMPORT
))
return
def _itemTransform(self, isFile: bool, isFolder: bool) -> None:
"""Add actions for the Transform menu."""
@@ -1338,15 +1271,12 @@ class _TreeContextMenu(QMenu):
action = qtAddAction(menu, self.tr("Split Document by Headings"))
action.triggered.connect(qtLambda(self._tree.splitDocument, self._handle))
return
def _expandCollapse(self) -> None:
"""Add actions for expand and collapse."""
action = qtAddAction(self, self.tr("Expand All"))
action.triggered.connect(qtLambda(self._tree.expandFromIndex, self._indices[0]))
action = qtAddAction(self, self.tr("Collapse All"))
action.triggered.connect(qtLambda(self._tree.collapseFromIndex, self._indices[0]))
return
def _deleteOrTrash(self) -> None:
"""Add move to Trash action."""
@@ -1359,7 +1289,6 @@ class _TreeContextMenu(QMenu):
text = self.tr("Move to Trash")
action = qtAddAction(self, text)
action.triggered.connect(self._tree.processDeleteRequest)
return
##
# Private Slots
@@ -1371,7 +1300,6 @@ class _TreeContextMenu(QMenu):
if self._item.isFileType():
self._item.setActive(not self._item.isActive)
self._item.notifyToRefresh()
return
##
# Internal Functions
@@ -1385,13 +1313,11 @@ class _TreeContextMenu(QMenu):
node.item.setActive(state)
refresh.append(node.item.itemHandle)
SHARED.project.tree.refreshItems(refresh)
return
def _changeItemStatus(self, key: str) -> None:
"""Set a new status value of an item."""
self._item.setStatus(key)
self._item.notifyToRefresh()
return
def _iterSetItemStatus(self, key: str) -> None:
"""Change the status value for multiple items."""
@@ -1401,13 +1327,11 @@ class _TreeContextMenu(QMenu):
node.item.setStatus(key)
refresh.append(node.item.itemHandle)
SHARED.project.tree.refreshItems(refresh)
return
def _changeItemImport(self, key: str) -> None:
"""Set a new importance value of an item."""
self._item.setImport(key)
self._item.notifyToRefresh()
return
def _iterSetItemImport(self, key: str) -> None:
"""Change the status value for multiple items."""
@@ -1417,7 +1341,6 @@ class _TreeContextMenu(QMenu):
node.item.setImport(key)
refresh.append(node.item.itemHandle)
SHARED.project.tree.refreshItems(refresh)
return
def _changeItemLayout(self, itemLayout: nwItemLayout) -> None:
"""Set a new item layout value of an item."""
@@ -1428,7 +1351,6 @@ class _TreeContextMenu(QMenu):
elif itemLayout == nwItemLayout.NOTE:
self._item.setLayout(nwItemLayout.NOTE)
self._item.notifyToRefresh()
return
def _convertFolderToFile(self, itemLayout: nwItemLayout) -> None:
"""Convert a folder to a note or document."""
@@ -1448,4 +1370,3 @@ class _TreeContextMenu(QMenu):
self._item.notifyToRefresh()
else:
logger.info("Folder conversion cancelled")
return
+2 -19
View File
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -50,6 +50,7 @@ logger = logging.getLogger(__name__)
class GuiProjectSearch(QWidget):
"""GUI: Project Search Panel."""
C_NAME = 0
C_RESULT = 0
@@ -151,8 +152,6 @@ class GuiProjectSearch(QWidget):
logger.debug("Ready: GuiProjectSearch")
return
##
# Methods
##
@@ -174,8 +173,6 @@ class GuiProjectSearch(QWidget):
self.toggleWord.setIcon(SHARED.theme.getIcon("search_word"))
self.toggleRegEx.setIcon(SHARED.theme.getIcon("search_regex"))
return
def processReturn(self) -> None:
"""Process a return keypress forwarded from the main GUI."""
if self.searchText.hasFocus():
@@ -189,7 +186,6 @@ class GuiProjectSearch(QWidget):
self.openDocumentSelectRequest.emit(
str(data[0]), checkInt(data[1], -1), checkInt(data[2], -1), False
)
return
def beginSearch(self, text: str = "") -> None:
"""Focus the search box and select its text, if any."""
@@ -198,20 +194,17 @@ class GuiProjectSearch(QWidget):
if text:
self.searchText.setText(text.partition("\n")[0])
self.searchText.selectAll()
return
def closeProjectTasks(self) -> None:
"""Run close project tasks."""
self._map = {}
self.searchText.clear()
self.searchResult.clear()
return
def refreshCurrentSearch(self) -> None:
"""Refresh the search if there is one."""
if self.searchResult.topLevelItemCount() > 0:
self._processSearch()
return
##
# Events
@@ -238,7 +231,6 @@ class GuiProjectSearch(QWidget):
self.searchText.setFocus()
else:
super().keyPressEvent(event)
return
##
# Public Slots
@@ -252,7 +244,6 @@ class GuiProjectSearch(QWidget):
results, capped = self._search.searchText(SHARED.mainGui.docEditor.getText())
self._displayResultSet(SHARED.project.tree[tHandle], results, capped)
logger.debug("Updated search for '%s' in %.3f ms", tHandle, 1000*(time() - start))
return
##
# Private Slots
@@ -278,7 +269,6 @@ class GuiProjectSearch(QWidget):
self._time = time()
QApplication.restoreOverrideCursor()
self._blocked = False
return
@pyqtSlot()
def _searchResultSelected(self) -> None:
@@ -288,7 +278,6 @@ class GuiProjectSearch(QWidget):
self.selectedItemChanged.emit(str(data[0]))
elif data := items[0].data(0, self.D_HANDLE):
self.selectedItemChanged.emit(str(data))
return
@pyqtSlot("QTreeWidgetItem*", int)
def _searchResultDoubleClicked(self, item: QTreeWidgetItem, column: int) -> None:
@@ -297,28 +286,24 @@ class GuiProjectSearch(QWidget):
self.openDocumentSelectRequest.emit(
str(data[0]), checkInt(data[1], -1), checkInt(data[2], -1), True
)
return
@pyqtSlot(bool)
def _toggleCase(self, state: bool) -> None:
"""Enable/disable case sensitive mode."""
CONFIG.searchProjCase = state
self.refreshCurrentSearch()
return
@pyqtSlot(bool)
def _toggleWord(self, state: bool) -> None:
"""Enable/disable whole word search mode."""
CONFIG.searchProjWord = state
self.refreshCurrentSearch()
return
@pyqtSlot(bool)
def _toggleRegEx(self, state: bool) -> None:
"""Enable/disable regular expression search mode."""
CONFIG.searchProjRegEx = state
self.refreshCurrentSearch()
return
##
# Internal Functions
@@ -360,5 +345,3 @@ class GuiProjectSearch(QWidget):
self.searchResult.setFirstColumnSpanned(i, parent, True)
QApplication.processEvents()
return
+2 -7
View File
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -45,6 +45,7 @@ logger = logging.getLogger(__name__)
class GuiSideBar(QWidget):
"""GUI: Main Window SideBar."""
requestViewChange = pyqtSignal(nwView)
@@ -126,8 +127,6 @@ class GuiSideBar(QWidget):
logger.debug("Ready: GuiSideBar")
return
def updateTheme(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
buttonStyle = SHARED.theme.getStyleSheet(STYLES_BIG_TOOLBUTTON)
@@ -153,8 +152,6 @@ class GuiSideBar(QWidget):
self._setThemeModeIcon()
return
##
# Private Slots
##
@@ -171,7 +168,6 @@ class GuiSideBar(QWidget):
CONFIG.themeMode = nwTheme.AUTO
self.mainGui.checkThemeUpdate()
self._setThemeModeIcon()
return
##
# Internal Functions
@@ -181,7 +177,6 @@ class GuiSideBar(QWidget):
"""Set the theme button icon."""
self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode])
self.tbTheme.setToolTip(trConst(nwLabels.THEME_MODE_LABEL[CONFIG.themeMode]))
return
class _PopRightMenu(QMenu):
+2 -19
View File
@@ -20,7 +20,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -41,6 +41,7 @@ logger = logging.getLogger(__name__)
class GuiMainStatus(QStatusBar):
"""GUI: Main Window Status Bar."""
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
@@ -112,8 +113,6 @@ class GuiMainStatus(QStatusBar):
self.updateTheme()
self.clearStatus()
return
def initSettings(self) -> None:
"""Apply user settings."""
if CONFIG.useCharCount:
@@ -122,7 +121,6 @@ class GuiMainStatus(QStatusBar):
else:
self._trStatsCount = trStats(nwLabels.STATS_DISPLAY[nwStats.WORDS])
self._trStatsTip = self.tr("Total word count (session change)")
return
def clearStatus(self) -> None:
"""Reset all widgets on the status bar to default values."""
@@ -132,7 +130,6 @@ class GuiMainStatus(QStatusBar):
self.setProjectStatus(None)
self.setDocumentStatus(None)
self.updateTime()
return
def updateTheme(self) -> None:
"""Update theme elements."""
@@ -149,8 +146,6 @@ class GuiMainStatus(QStatusBar):
self.docIcon.setColors(colNone, colSaved, colUnsaved)
self.projIcon.setColors(colNone, colSaved, colUnsaved)
return
##
# Setters
##
@@ -158,17 +153,14 @@ class GuiMainStatus(QStatusBar):
def setRefTime(self, refTime: float) -> None:
"""Set the reference time for the status bar clock."""
self._refTime = refTime
return
def setProjectStatus(self, state: bool | None) -> None:
"""Set the project status colour icon."""
self.projIcon.setState(state)
return
def setDocumentStatus(self, state: bool | None) -> None:
"""Set the document status colour icon."""
self.docIcon.setState(state)
return
def setUserIdle(self, idle: bool) -> None:
"""Change the idle status icon."""
@@ -180,13 +172,11 @@ class GuiMainStatus(QStatusBar):
else:
self.timeIcon.setPixmap(self.timePixmap)
self._userIdle = idle
return
def setProjectStats(self, pWC: int, sWC: int) -> None:
"""Update the current project statistics."""
self.statsText.setText(self._trStatsCount.format(f"{pWC:n}", f"{sWC:+n}"))
self.statsText.setToolTip(self._trStatsTip)
return
def updateTime(self, idleTime: float = 0.0) -> None:
"""Update the session clock."""
@@ -198,7 +188,6 @@ class GuiMainStatus(QStatusBar):
else:
sessTime = round(time() - self._refTime)
self.timeText.setText(formatTime(sessTime))
return
##
# Public Slots
@@ -209,7 +198,6 @@ class GuiMainStatus(QStatusBar):
"""Set the status bar message to display."""
self.showMessage(message, nwConst.STATUS_MSG_TIMEOUT)
QApplication.processEvents()
return
@pyqtSlot(str, str)
def setLanguage(self, language: str, provider: str) -> None:
@@ -220,19 +208,16 @@ class GuiMainStatus(QStatusBar):
else:
self.langText.setText(QLocale(language).nativeLanguageName().title())
self.langText.setToolTip(f"{language} ({provider})" if provider else language)
return
@pyqtSlot(bool)
def updateProjectStatus(self, status: bool) -> None:
"""Update the project status."""
self.setProjectStatus(not status)
return
@pyqtSlot(bool)
def updateDocumentStatus(self, status: bool) -> None:
"""Update the document status."""
self.setDocumentStatus(not status)
return
##
# Private Slots
@@ -244,7 +229,6 @@ class GuiMainStatus(QStatusBar):
state = not CONFIG.showSessionTime
self.timeText.setVisible(state)
CONFIG.showSessionTime = state
return
##
# Debug
@@ -279,4 +263,3 @@ class GuiMainStatus(QStatusBar):
)
self.showMessage(f"Debug [{stamp}] {message}", 6000)
logger.debug("[MEMINFO] %s", message)
return
+8 -23
View File
@@ -21,7 +21,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
""" # noqa
from __future__ import annotations
import logging
@@ -58,6 +58,7 @@ STYLES_BIG_TOOLBUTTON = "bigToolButton"
@dataclass
class ThemeEntry:
"""Theme data."""
name: str
dark: bool
@@ -65,6 +66,7 @@ class ThemeEntry:
class ThemeMeta:
"""Theme meta data."""
name: str = ""
mode: str = ""
@@ -74,6 +76,7 @@ class ThemeMeta:
class IconsMeta:
"""Icon theme meta data."""
name: str = ""
author: str = ""
@@ -81,6 +84,7 @@ class IconsMeta:
class SyntaxColors:
"""Colours for the syntax highlighter."""
back: QColor = QColor(255, 255, 255)
text: QColor = QColor(0, 0, 0)
@@ -106,7 +110,7 @@ class SyntaxColors:
class GuiTheme:
"""Gui Theme Class
"""Gui Theme Class.
Handles the look and feel of novelWriter.
"""
@@ -190,8 +194,6 @@ class GuiTheme:
logger.debug("Text 'N' Height: %d", self.textNHeight)
logger.debug("Text 'N' Width: %d", self.textNWidth)
return
##
# Properties
##
@@ -206,7 +208,7 @@ class GuiTheme:
##
def getTextWidth(self, text: str, font: QFont | None = None) -> int:
"""Returns the width needed to contain a given piece of text in
"""Return the width needed to contain a given piece of text in
pixels.
"""
if isinstance(font, QFont):
@@ -238,8 +240,6 @@ class GuiTheme:
self.iconCache.initIcons()
self.loadTheme()
return
def isDesktopDarkMode(self) -> bool:
"""Check if the desktop is in dark mode."""
if CONFIG.verQtValue >= 0x060500 and (hint := QGuiApplication.styleHints()):
@@ -507,7 +507,6 @@ class GuiTheme:
"""Set the colour for a named colour."""
self._qColors[key] = QColor(color)
self._svgColors[key] = color.name(QColor.NameFormat.HexRgb).encode("utf-8")
return
def _resetTheme(self) -> None:
"""Reset GUI colours to default values."""
@@ -559,8 +558,6 @@ class GuiTheme:
self._setBaseColor("inactive", red)
self._setBaseColor("disabled", faded)
return
def _readColor(self, parser: ConfigParser, section: str, name: str) -> QColor:
"""Parse a colour value from a config string."""
return self.parseColor(parser.get(section, name, fallback="default"))
@@ -570,7 +567,6 @@ class GuiTheme:
) -> None:
"""Set a palette colour value from a config string."""
self._guiPalette.setBrush(value, self._readColor(parser, section, name))
return
def _buildStyleSheets(self, palette: QPalette) -> None:
"""Build default style sheets."""
@@ -602,8 +598,6 @@ class GuiTheme:
"QToolButton::menu-indicator {image: none;} "
)
return
def _scanThemes(self, files: list[Path]) -> None:
"""Scan the GUI themes folder and list all themes."""
parser = ConfigParser()
@@ -631,8 +625,6 @@ class GuiTheme:
logger.debug("Checking theme config '%s'", key)
self._allThemes[key] = ThemeEntry(name, dark, item)
return
class GuiIcons:
"""The icon class manages the content of the assets/icons folder,
@@ -672,8 +664,6 @@ class GuiIcons:
# None Icon
self._noIcon = QIcon(str(CONFIG.assetPath("icons") / "none.svg"))
return
def clear(self) -> None:
"""Clear the icon cache."""
self._svgData = {}
@@ -681,7 +671,6 @@ class GuiIcons:
self._headerDec = []
self._headerDecNarrow = []
self._meta = ThemeMeta()
return
##
# Properties
@@ -703,7 +692,6 @@ class GuiIcons:
_listContent(icons, CONFIG.assetPath("icons"), ".icons")
_listContent(icons, CONFIG.dataPath("icons"), ".icons")
self._scanThemes(icons)
return
def loadTheme(self, theme: str) -> None:
"""Update the theme map. This is more of an init, since many of
@@ -781,7 +769,7 @@ class GuiIcons:
self, tType: nwItemType, tClass: nwItemClass, tLayout: nwItemLayout, hLevel: str = "H0"
) -> QIcon:
"""Get the correct icon for a project item based on type, class
and heading level
and heading level.
"""
name = None
color = "default"
@@ -942,8 +930,6 @@ class GuiIcons:
logger.debug("Checking icon theme '%s'", key)
self._allThemes[key] = ThemeEntry(name, False, item)
return
# Module Functions
# ================
@@ -952,4 +938,3 @@ def _listContent(data: list[Path], path: Path, extension: str) -> None:
"""List files of a specific type and extend the list."""
if path.is_dir():
data.extend(n for n in path.iterdir() if n.is_file() and n.suffix == extension)
return