From 8c3c85f2590c203d1f44368e72b22d8939e5028c Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 23 Nov 2023 18:36:08 +0100
Subject: [PATCH 1/6] Clean up Qt enums in doc editor
---
novelwriter/gui/doceditor.py | 201 +++++++++++++++++++----------------
1 file changed, 111 insertions(+), 90 deletions(-)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 91e4b57f9..39dcdc070 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -83,8 +83,8 @@ class GuiDocEditor(QPlainTextEdit):
"""Gui Widget: Main Document Editor"""
MOVE_KEYS = (
- Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down,
- Qt.Key_PageUp, Qt.Key_PageDown
+ Qt.Key.Key_Left, Qt.Key.Key_Right, Qt.Key.Key_Up, Qt.Key.Key_Down,
+ Qt.Key.Key_PageUp, Qt.Key.Key_PageDown
)
# Custom Signals
@@ -157,28 +157,28 @@ def __init__(self, mainGui: GuiMain) -> None:
self.docToolBar.requestDocAction.connect(self.docAction)
# Context Menu
- self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self._openContextMenu)
# Editor Settings
self.setMinimumWidth(CONFIG.pxInt(300))
self.setAutoFillBackground(True)
- self.setFrameStyle(QFrame.NoFrame)
+ self.setFrameStyle(QFrame.Shape.NoFrame)
# Custom Shortcuts
self.keyContext = QShortcut(self)
self.keyContext.setKey("Ctrl+.")
- self.keyContext.setContext(Qt.WidgetShortcut)
+ self.keyContext.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyContext.activated.connect(self._openContextFromCursor)
self.followTag1 = QShortcut(self)
- self.followTag1.setKey(Qt.Key_Return | Qt.ControlModifier)
- self.followTag1.setContext(Qt.WidgetShortcut)
+ self.followTag1.setKey(Qt.Key.Key_Return | Qt.KeyboardModifier.ControlModifier)
+ self.followTag1.setContext(Qt.ShortcutContext.WidgetShortcut)
self.followTag1.activated.connect(self._processTag)
self.followTag2 = QShortcut(self)
- self.followTag2.setKey(Qt.Key_Enter | Qt.ControlModifier)
- self.followTag2.setContext(Qt.WidgetShortcut)
+ self.followTag2.setKey(Qt.Key.Key_Enter | Qt.KeyboardModifier.ControlModifier)
+ self.followTag2.setContext(Qt.ShortcutContext.WidgetShortcut)
self.followTag2.activated.connect(self._processTag)
# Set Up Document Word Counter
@@ -274,14 +274,14 @@ def updateTheme(self) -> None:
def updateSyntaxColours(self) -> None:
"""Update the syntax highlighting theme."""
mainPalette = self.palette()
- mainPalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
- mainPalette.setColor(QPalette.Base, QColor(*SHARED.theme.colBack))
- mainPalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ mainPalette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
+ mainPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
+ mainPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.setPalette(mainPalette)
docPalette = self.viewport().palette()
- docPalette.setColor(QPalette.Base, QColor(*SHARED.theme.colBack))
- docPalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ docPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
+ docPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.viewport().setPalette(docPalette)
self.docHeader.matchColours()
@@ -333,25 +333,25 @@ def initEditor(self) -> None:
options = QTextOption()
if CONFIG.doJustify:
- options.setAlignment(Qt.AlignJustify)
+ options.setAlignment(Qt.AlignmentFlag.AlignJustify)
if CONFIG.showTabsNSpaces:
- options.setFlags(options.flags() | QTextOption.ShowTabsAndSpaces)
+ options.setFlags(options.flags() | QTextOption.Flag.ShowTabsAndSpaces)
if CONFIG.showLineEndings:
- options.setFlags(options.flags() | QTextOption.ShowLineAndParagraphSeparators)
+ options.setFlags(options.flags() | QTextOption.Flag.ShowLineAndParagraphSeparators)
self._qDocument.setDefaultTextOption(options)
# Scrolling
self.setCenterOnScroll(CONFIG.scrollPastEnd)
if CONFIG.hideVScroll:
- self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
else:
- self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
if CONFIG.hideHScroll:
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
else:
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
# Refresh the tab stops
self.setTabStopDistance(CONFIG.getTabWidth())
@@ -388,7 +388,7 @@ def loadText(self, tHandle, tLine=None) -> bool:
self.clearEditor()
return False
- qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
+ qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
self._docHandle = tHandle
self._allowAutoReplace(False)
@@ -441,7 +441,7 @@ def replaceText(self, text: str) -> None:
"""Replace the text of the current document with the provided
text. This also clears undo history.
"""
- qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
+ qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
self.setPlainText(text)
self.updateDocMargins()
self.setDocumentChanged(True)
@@ -676,7 +676,7 @@ def spellCheckDocument(self) -> None:
"""
logger.debug("Running spell checker")
start = time()
- qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
+ qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
self._qDocument.syntaxHighlighter.rehighlight()
qApp.restoreOverrideCursor()
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
@@ -726,9 +726,9 @@ def docAction(self, action: nwDocAction) -> bool:
elif action == nwDocAction.D_QUOTE:
self._wrapSelection(self._typDQuoteO, self._typDQuoteC)
elif action == nwDocAction.SEL_ALL:
- self._makeSelection(QTextCursor.Document)
+ self._makeSelection(QTextCursor.SelectionType.Document)
elif action == nwDocAction.SEL_PARA:
- self._makeSelection(QTextCursor.BlockUnderCursor)
+ self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor)
elif action == nwDocAction.BLOCK_H1:
self._formatBlock(nwDocAction.BLOCK_H1)
elif action == nwDocAction.BLOCK_H2:
@@ -909,17 +909,17 @@ def keyPressEvent(self, event: QKeyEvent) -> None:
* We also handle automatic scrolling here.
"""
self._lastActive = time()
- isReturn = event.key() == Qt.Key_Return
- isReturn |= event.key() == Qt.Key_Enter
+ isReturn = event.key() == Qt.Key.Key_Return
+ isReturn |= event.key() == Qt.Key.Key_Enter
if isReturn and self.docSearch.anyFocus():
return
- elif event == QKeySequence.Redo:
+ elif event == QKeySequence.StandardKey.Redo:
self.docAction(nwDocAction.REDO)
return
- elif event == QKeySequence.Undo:
+ elif event == QKeySequence.StandardKey.Undo:
self.docAction(nwDocAction.UNDO)
return
- elif event == QKeySequence.SelectAll:
+ elif event == QKeySequence.StandardKey.SelectAll:
self.docAction(nwDocAction.SEL_ALL)
return
@@ -928,7 +928,7 @@ def keyPressEvent(self, event: QKeyEvent) -> None:
super().keyPressEvent(event)
nPos = self.cursorRect().topLeft().y()
kMod = event.modifiers()
- okMod = kMod == Qt.NoModifier or kMod == Qt.ShiftModifier
+ okMod = kMod in (Qt.KeyboardModifier.NoModifier, Qt.KeyboardModifier.ShiftModifier)
okKey = event.key() not in self.MOVE_KEYS
if nPos != cPos and okMod and okKey:
mPos = CONFIG.autoScrollPos*0.01 * self.viewport().height()
@@ -959,7 +959,7 @@ def mouseReleaseEvent(self, event: QMouseEvent) -> None:
pressed, check if we're clicking on a tag, and trigger the
follow tag function.
"""
- if qApp.keyboardModifiers() == Qt.ControlModifier:
+ if qApp.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier:
self._processTag(self.cursorForPosition(event.pos()))
super().mouseReleaseEvent(event)
self.docFooter.updateLineCount()
@@ -1099,9 +1099,13 @@ def _openContextMenu(self, pos: QPoint) -> None:
aSAll = ctxMenu.addAction(self.tr("Select All"))
aSAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
aSWrd = ctxMenu.addAction(self.tr("Select Word"))
- aSWrd.triggered.connect(lambda: self._makePosSelection(QTextCursor.WordUnderCursor, pos))
+ aSWrd.triggered.connect(
+ lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, pos)
+ )
aSPar = ctxMenu.addAction(self.tr("Select Paragraph"))
- aSPar.triggered.connect(lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, pos))
+ aSPar.triggered.connect(lambda: self._makePosSelection(
+ QTextCursor.SelectionType.BlockUnderCursor, pos)
+ )
# Spell Checking
if SHARED.project.data.spellCheck:
@@ -1111,7 +1115,9 @@ def _openContextMenu(self, pos: QPoint) -> None:
block = pCursor.block()
sCursor = self.textCursor()
sCursor.setPosition(block.position() + cPos)
- sCursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, cLen)
+ sCursor.movePosition(
+ QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, cLen
+ )
if suggest:
ctxMenu.addSeparator()
ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
@@ -1316,8 +1322,8 @@ def findNext(self, goBack: bool = False) -> None:
else:
resIdx = 0 if doLoop else maxIdx
- cursor.setPosition(resS[resIdx], QTextCursor.MoveAnchor)
- cursor.setPosition(resE[resIdx], QTextCursor.KeepAnchor)
+ cursor.setPosition(resS[resIdx], QTextCursor.MoveMode.MoveAnchor)
+ cursor.setPosition(resE[resIdx], QTextCursor.MoveMode.KeepAnchor)
self.setTextCursor(cursor)
self.docFooter.updateLineCount()
@@ -1343,9 +1349,9 @@ def findAllOccurences(self) -> tuple[list[int], list[int]]:
findOpt = QTextDocument.FindFlag(0)
if self.docSearch.isCaseSense:
- findOpt |= QTextDocument.FindCaseSensitively
+ findOpt |= QTextDocument.FindFlag.FindCaseSensitively
if self.docSearch.isWholeWord:
- findOpt |= QTextDocument.FindWholeWords
+ findOpt |= QTextDocument.FindFlag.FindWholeWords
searchFor = self.docSearch.getSearchObject()
cursor.setPosition(0)
@@ -1363,8 +1369,8 @@ def findAllOccurences(self) -> tuple[list[int], list[int]]:
break
if hasSelection:
- cursor.setPosition(origA, QTextCursor.MoveAnchor)
- cursor.setPosition(origB, QTextCursor.KeepAnchor)
+ cursor.setPosition(origA, QTextCursor.MoveMode.MoveAnchor)
+ cursor.setPosition(origB, QTextCursor.MoveMode.KeepAnchor)
else:
cursor.setPosition(origA)
@@ -1475,8 +1481,8 @@ def _toggleFormat(self, fLen: int, fChar: str) -> bool:
if blockS != blockE:
posE = blockS.position() + blockS.length() - 1
cursor.clearSelection()
- cursor.setPosition(posS, QTextCursor.MoveAnchor)
- cursor.setPosition(posE, QTextCursor.KeepAnchor)
+ cursor.setPosition(posS, QTextCursor.MoveMode.MoveAnchor)
+ cursor.setPosition(posE, QTextCursor.MoveMode.KeepAnchor)
self.setTextCursor(cursor)
numB = 0
@@ -1578,7 +1584,9 @@ def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> bool:
self._allowAutoReplace(False)
for posC in range(posS, posE+1):
cursor.setPosition(posC)
- cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 2)
+ cursor.movePosition(
+ QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 2
+ )
selText = cursor.selectedText()
nS = len(selText)
@@ -1598,12 +1606,16 @@ def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> bool:
cursor.setPosition(posC)
if pC in closeCheck:
cursor.beginEditBlock()
- cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
+ cursor.movePosition(
+ QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 1
+ )
cursor.insertText(oQuote)
cursor.endEditBlock()
else:
cursor.beginEditBlock()
- cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, 1)
+ cursor.movePosition(
+ QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, 1
+ )
cursor.insertText(cQuote)
cursor.endEditBlock()
@@ -1723,7 +1735,7 @@ def _formatBlock(self, action: nwDocAction) -> bool:
# Replace the block text
cursor.beginEditBlock()
posO = cursor.position()
- cursor.select(QTextCursor.BlockUnderCursor)
+ cursor.select(QTextCursor.SelectionType.BlockUnderCursor)
posS = cursor.selectionStart()
cursor.removeSelectedText()
cursor.setPosition(posS)
@@ -1783,7 +1795,9 @@ def _removeInParLineBreaks(self) -> None:
cursor.beginEditBlock()
cursor.clearSelection()
cursor.setPosition(rS)
- cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, rE-rS)
+ cursor.movePosition(
+ QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, rE-rS
+ )
cursor.insertText(cleanText.rstrip() + "\n")
cursor.endEditBlock()
@@ -1943,7 +1957,9 @@ def _docAutoReplace(self, text: str) -> None:
tInsert = tInsert + self._typPadChar
if nDelete > 0:
- cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, nDelete)
+ cursor.movePosition(
+ QTextCursor.MoveOperation.Left, QTextCursor.MoveMode.KeepAnchor, nDelete
+ )
cursor.insertText(tInsert)
return
@@ -1994,8 +2010,8 @@ def _autoSelect(self) -> QTextCursor:
return cursor
cursor.clearSelection()
- cursor.setPosition(sPos, QTextCursor.MoveAnchor)
- cursor.setPosition(ePos, QTextCursor.KeepAnchor)
+ cursor.setPosition(sPos, QTextCursor.MoveMode.MoveAnchor)
+ cursor.setPosition(ePos, QTextCursor.MoveMode.KeepAnchor)
self.setTextCursor(cursor)
@@ -2007,18 +2023,18 @@ def _makeSelection(self, mode: QTextCursor.SelectionType) -> None:
cursor.clearSelection()
cursor.select(mode)
- if mode == QTextCursor.WordUnderCursor:
+ if mode == QTextCursor.SelectionType.WordUnderCursor:
cursor = self._autoSelect()
- elif mode == QTextCursor.BlockUnderCursor:
+ elif mode == QTextCursor.SelectionType.BlockUnderCursor:
# This selection mode also selects the preceding paragraph
# separator, which we want to avoid.
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
selTxt = cursor.selectedText()
if selTxt.startswith(nwUnicode.U_PSEP):
- cursor.setPosition(posS+1, QTextCursor.MoveAnchor)
- cursor.setPosition(posE, QTextCursor.KeepAnchor)
+ cursor.setPosition(posS+1, QTextCursor.MoveMode.MoveAnchor)
+ cursor.setPosition(posE, QTextCursor.MoveMode.KeepAnchor)
self.setTextCursor(cursor)
@@ -2100,7 +2116,10 @@ def updateText(self, text: str, pos: int) -> bool:
def keyPressEvent(self, event: QKeyEvent) -> None:
"""Capture keypresses and forward most of them to the editor."""
parent = self.parent()
- if event.key() in (Qt.Key_Up, Qt.Key_Down, Qt.Key_Return, Qt.Key_Enter, Qt.Key_Escape):
+ if event.key() in (
+ Qt.Key.Key_Up, Qt.Key.Key_Down, Qt.Key.Key_Return,
+ Qt.Key.Key_Enter, Qt.Key.Key_Escape
+ ):
super().keyPressEvent(event)
elif isinstance(parent, GuiDocEditor):
parent.keyPressEvent(event)
@@ -2254,9 +2273,9 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
def updateTheme(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
palette = QPalette()
- palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
- palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
- palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
+ palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.setPalette(palette)
tPx = int(0.8*SHARED.theme.fontPixelSize)
@@ -2336,7 +2355,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True)
- self.setFrameStyle(QFrame.StyledPanel | QFrame.Plain)
+ self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Plain)
self.mainBox = QGridLayout(self)
self.setLayout(self.mainBox)
@@ -2355,7 +2374,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.replaceBox.returnPressed.connect(self._doReplace)
self.searchOpt = QToolBar(self)
- self.searchOpt.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.searchOpt.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.searchOpt.setIconSize(QSize(tPx, tPx))
self.searchOpt.setContentsMargins(0, 0, 0, 0)
@@ -2417,7 +2436,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
bPx = self.searchBox.sizeHint().height()
self.showReplace = QToolButton(self)
- self.showReplace.setArrowType(Qt.RightArrow)
+ self.showReplace.setArrowType(Qt.ArrowType.RightArrow)
self.showReplace.setCheckable(True)
self.showReplace.toggled.connect(self._doToggleReplace)
@@ -2431,8 +2450,8 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.replaceButton.setToolTip(self.tr("Find and replace in current document"))
self.replaceButton.clicked.connect(self._doReplace)
- self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2, Qt.AlignLeft)
- self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 3, Qt.AlignRight)
+ self.mainBox.addWidget(self.searchLabel, 0, 0, 1, 2, Qt.AlignmentFlag.AlignLeft)
+ self.mainBox.addWidget(self.searchOpt, 0, 2, 1, 3, Qt.AlignmentFlag.AlignRight)
self.mainBox.addWidget(self.showReplace, 1, 0, 1, 1)
self.mainBox.addWidget(self.searchBox, 1, 1, 1, 2)
self.mainBox.addWidget(self.searchButton, 1, 3, 1, 1)
@@ -2490,18 +2509,18 @@ def getSearchObject(self) -> str | QRegularExpression | QRegExp:
# only added in Qt 5.13. Otherwise, 5.3 and up supports
# only the QRegExp class.
if CONFIG.verQtValue >= 0x050d00:
- rxOpt = QRegularExpression.UseUnicodePropertiesOption
+ rxOpt = QRegularExpression.PatternOption.UseUnicodePropertiesOption
if not self.isCaseSense:
- rxOpt |= QRegularExpression.CaseInsensitiveOption
+ rxOpt |= QRegularExpression.PatternOption.CaseInsensitiveOption
regEx = QRegularExpression(text, rxOpt)
self._alertSearchValid(regEx.isValid())
return regEx
else: # pragma: no cover
# >= 50300 to < 51300
if self.isCaseSense:
- rxOpt = Qt.CaseSensitive
+ rxOpt = Qt.CaseSensitivity.CaseSensitive
else:
- rxOpt = Qt.CaseInsensitive
+ rxOpt = Qt.CaseSensitivity.CaseInsensitive
regEx = QRegExp(text, rxOpt)
self._alertSearchValid(regEx.isValid())
return regEx
@@ -2637,7 +2656,7 @@ def _doClose(self) -> None:
def _doSearch(self) -> None:
"""Call the search action function for the document editor."""
modKey = qApp.keyboardModifiers()
- if modKey == Qt.ShiftModifier:
+ if modKey == Qt.KeyboardModifier.ShiftModifier:
self.docEditor.findNext(goBack=True)
else:
self.docEditor.findNext()
@@ -2653,9 +2672,9 @@ def _doReplace(self) -> None:
def _doToggleReplace(self, state: bool) -> None:
"""Toggle the show/hide of the replace box."""
if state:
- self.showReplace.setArrowType(Qt.DownArrow)
+ self.showReplace.setArrowType(Qt.ArrowType.DownArrow)
else:
- self.showReplace.setArrowType(Qt.RightArrow)
+ self.showReplace.setArrowType(Qt.ArrowType.RightArrow)
self.replaceBox.setVisible(state)
self.replaceButton.setVisible(state)
self.repVisible = state
@@ -2708,7 +2727,7 @@ def _alertSearchValid(self, isValid: bool) -> None:
isn't valid. Take the colour from the replace box.
"""
qPalette = self.replaceBox.palette()
- qPalette.setColor(QPalette.Base, self.rxCol[isValid])
+ qPalette.setColor(QPalette.ColorRole.Base, self.rxCol[isValid])
self.searchBox.setPalette(qPalette)
return
@@ -2749,7 +2768,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.itemTitle.setMargin(0)
self.itemTitle.setContentsMargins(0, 0, 0, 0)
self.itemTitle.setAutoFillBackground(True)
- self.itemTitle.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
+ self.itemTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
self.itemTitle.setFixedHeight(fPx)
lblFont = self.itemTitle.font()
@@ -2761,7 +2780,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.tbButton.setContentsMargins(0, 0, 0, 0)
self.tbButton.setIconSize(iconSize)
self.tbButton.setFixedSize(fPx, fPx)
- self.tbButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.tbButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.tbButton.setVisible(False)
self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit())
@@ -2770,7 +2789,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.searchButton.setContentsMargins(0, 0, 0, 0)
self.searchButton.setIconSize(iconSize)
self.searchButton.setFixedSize(fPx, fPx)
- self.searchButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.searchButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.searchButton.setVisible(False)
self.searchButton.setToolTip(self.tr("Search"))
self.searchButton.clicked.connect(self.docEditor.toggleSearch)
@@ -2779,7 +2798,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.minmaxButton.setContentsMargins(0, 0, 0, 0)
self.minmaxButton.setIconSize(iconSize)
self.minmaxButton.setFixedSize(fPx, fPx)
- self.minmaxButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.minmaxButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.minmaxButton.setVisible(False)
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit())
@@ -2788,7 +2807,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.closeButton.setContentsMargins(0, 0, 0, 0)
self.closeButton.setIconSize(iconSize)
self.closeButton.setFixedSize(fPx, fPx)
- self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.closeButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.closeButton.setVisible(False)
self.closeButton.setToolTip(self.tr("Close"))
self.closeButton.clicked.connect(self._closeDocument)
@@ -2846,9 +2865,9 @@ def matchColours(self) -> None:
theme rather than the main GUI.
"""
palette = QPalette()
- palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
- palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
- palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
+ palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.setPalette(palette)
self.itemTitle.setPalette(palette)
@@ -2961,11 +2980,13 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True)
+ alLeftTop = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
+
# Status
self.statusIcon = QLabel("")
self.statusIcon.setContentsMargins(0, 0, 0, 0)
self.statusIcon.setFixedHeight(self.sPx)
- self.statusIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
+ self.statusIcon.setAlignment(alLeftTop)
self.statusText = QLabel(self.tr("Status"))
self.statusText.setIndent(0)
@@ -2973,14 +2994,14 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.statusText.setContentsMargins(0, 0, 0, 0)
self.statusText.setAutoFillBackground(True)
self.statusText.setFixedHeight(fPx)
- self.statusText.setAlignment(Qt.AlignLeft | Qt.AlignTop)
+ self.statusText.setAlignment(alLeftTop)
self.statusText.setFont(lblFont)
# Lines
self.linesIcon = QLabel("")
self.linesIcon.setContentsMargins(0, 0, 0, 0)
self.linesIcon.setFixedHeight(self.sPx)
- self.linesIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
+ self.linesIcon.setAlignment(alLeftTop)
self.linesText = QLabel("")
self.linesText.setIndent(0)
@@ -2988,14 +3009,14 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.linesText.setContentsMargins(0, 0, 0, 0)
self.linesText.setAutoFillBackground(True)
self.linesText.setFixedHeight(fPx)
- self.linesText.setAlignment(Qt.AlignLeft | Qt.AlignTop)
+ self.linesText.setAlignment(alLeftTop)
self.linesText.setFont(lblFont)
# Words
self.wordsIcon = QLabel("")
self.wordsIcon.setContentsMargins(0, 0, 0, 0)
self.wordsIcon.setFixedHeight(self.sPx)
- self.wordsIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
+ self.wordsIcon.setAlignment(alLeftTop)
self.wordsText = QLabel("")
self.wordsText.setIndent(0)
@@ -3003,7 +3024,7 @@ def __init__(self, docEditor: GuiDocEditor) -> None:
self.wordsText.setContentsMargins(0, 0, 0, 0)
self.wordsText.setAutoFillBackground(True)
self.wordsText.setFixedHeight(fPx)
- self.wordsText.setAlignment(Qt.AlignLeft | Qt.AlignTop)
+ self.wordsText.setAlignment(alLeftTop)
self.wordsText.setFont(lblFont)
# Assemble Layout
@@ -3051,9 +3072,9 @@ def matchColours(self) -> None:
theme rather than the main GUI.
"""
palette = QPalette()
- palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
- palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
- palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
+ palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.setPalette(palette)
self.statusText.setPalette(palette)
From 84eff433fbed001215b76e70ca64d9306edffdb1 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 23 Nov 2023 18:52:42 +0100
Subject: [PATCH 2/6] Clean up Qt enums in doc viewer
---
novelwriter/gui/docviewer.py | 82 ++++++++++++++++++------------------
1 file changed, 41 insertions(+), 41 deletions(-)
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index a0e2c3b90..a3bdcae81 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -37,8 +37,8 @@
QTextOption
)
from PyQt5.QtWidgets import (
- QAction, qApp, QFrame, QHBoxLayout, QLabel, QMenu, QTextBrowser,
- QToolButton, QWidget
+ QAction, QFrame, QHBoxLayout, QLabel, QMenu, QTextBrowser, QToolButton,
+ QWidget, qApp
)
from novelwriter import CONFIG, SHARED
@@ -75,8 +75,8 @@ def __init__(self, mainGui: GuiMain) -> None:
self.setMinimumWidth(CONFIG.pxInt(300))
self.setAutoFillBackground(True)
self.setOpenExternalLinks(False)
- self.setFocusPolicy(Qt.StrongFocus)
- self.setFrameStyle(QFrame.NoFrame)
+ self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+ self.setFrameStyle(QFrame.Shape.NoFrame)
# Document Header and Footer
self.docHeader = GuiDocViewHeader(self)
@@ -92,7 +92,7 @@ def __init__(self, mainGui: GuiMain) -> None:
self.installEventFilter(self.wheelEventFilter)
# Context Menu
- self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self._openContextMenu)
self.initViewer()
@@ -148,14 +148,14 @@ def initViewer(self) -> None:
# Set the widget colours to match syntax theme
mainPalette = self.palette()
- mainPalette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
- mainPalette.setColor(QPalette.Base, QColor(*SHARED.theme.colBack))
- mainPalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ mainPalette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
+ mainPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
+ mainPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.setPalette(mainPalette)
docPalette = self.viewport().palette()
- docPalette.setColor(QPalette.Base, QColor(*SHARED.theme.colBack))
- docPalette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ docPalette.setColor(QPalette.ColorRole.Base, QColor(*SHARED.theme.colBack))
+ docPalette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.viewport().setPalette(docPalette)
self.docHeader.matchColours()
@@ -165,19 +165,19 @@ def initViewer(self) -> None:
self.document().setDocumentMargin(0)
theOpt = QTextOption()
if CONFIG.doJustify:
- theOpt.setAlignment(Qt.AlignJustify)
+ theOpt.setAlignment(Qt.AlignmentFlag.AlignJustify)
self.document().setDefaultTextOption(theOpt)
# Scroll bars
if CONFIG.hideVScroll:
- self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
else:
- self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
if CONFIG.hideHScroll:
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
else:
- self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
# Refresh the tab stops
self.setTabStopDistance(CONFIG.getTabWidth())
@@ -196,7 +196,7 @@ def loadText(self, tHandle: str, updateHistory: bool = True) -> bool:
return False
logger.debug("Generating preview for item '%s'", tHandle)
- qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
+ qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
sPos = self.verticalScrollBar().value()
aDoc = ToHtml(SHARED.project)
@@ -272,9 +272,9 @@ def docAction(self, action: nwDocAction) -> bool:
elif action == nwDocAction.COPY:
self.copy()
elif action == nwDocAction.SEL_ALL:
- self._makeSelection(QTextCursor.Document)
+ self._makeSelection(QTextCursor.SelectionType.Document)
elif action == nwDocAction.SEL_PARA:
- self._makeSelection(QTextCursor.BlockUnderCursor)
+ self._makeSelection(QTextCursor.SelectionType.BlockUnderCursor)
else:
logger.debug("Unknown or unsupported document action '%s'", str(action))
return False
@@ -392,13 +392,13 @@ def _openContextMenu(self, point: QPoint) -> None:
mnuSelWord = QAction(self.tr("Select Word"), mnuContext)
mnuSelWord.triggered.connect(
- lambda: self._makePosSelection(QTextCursor.WordUnderCursor, point)
+ lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, point)
)
mnuContext.addAction(mnuSelWord)
mnuSelPara = QAction(self.tr("Select Paragraph"), mnuContext)
mnuSelPara.triggered.connect(
- lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, point)
+ lambda: self._makePosSelection(QTextCursor.SelectionType.BlockUnderCursor, point)
)
mnuContext.addAction(mnuSelPara)
@@ -419,9 +419,9 @@ def resizeEvent(self, event: QResizeEvent) -> None:
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
"""Capture mouse click events on the document."""
- if event.button() == Qt.BackButton:
+ if event.button() == Qt.MouseButton.BackButton:
self.navBackward()
- elif event.button() == Qt.ForwardButton:
+ elif event.button() == Qt.MouseButton.ForwardButton:
self.navForward()
else:
super().mouseReleaseEvent(event)
@@ -437,15 +437,15 @@ def _makeSelection(self, selType: QTextCursor.SelectionType) -> None:
cursor.clearSelection()
cursor.select(selType)
- if selType == QTextCursor.BlockUnderCursor:
+ if selType == QTextCursor.SelectionType.BlockUnderCursor:
# This selection mode also selects the preceding paragraph
# separator, which we want to avoid.
posS = cursor.selectionStart()
posE = cursor.selectionEnd()
selTxt = cursor.selectedText()
if selTxt.startswith(nwUnicode.U_PSEP):
- cursor.setPosition(posS+1, QTextCursor.MoveAnchor)
- cursor.setPosition(posE, QTextCursor.KeepAnchor)
+ cursor.setPosition(posS+1, QTextCursor.MoveMode.MoveAnchor)
+ cursor.setPosition(posE, QTextCursor.MoveMode.KeepAnchor)
self.setTextCursor(cursor)
@@ -663,7 +663,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.docTitle.setMargin(0)
self.docTitle.setContentsMargins(0, 0, 0, 0)
self.docTitle.setAutoFillBackground(True)
- self.docTitle.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
+ self.docTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
self.docTitle.setFixedHeight(fPx)
lblFont = self.docTitle.font()
@@ -675,7 +675,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.backButton.setContentsMargins(0, 0, 0, 0)
self.backButton.setIconSize(QSize(fPx, fPx))
self.backButton.setFixedSize(fPx, fPx)
- self.backButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.backButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.backButton.setVisible(False)
self.backButton.setToolTip(self.tr("Go Backward"))
self.backButton.clicked.connect(self.docViewer.navBackward)
@@ -684,7 +684,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.forwardButton.setContentsMargins(0, 0, 0, 0)
self.forwardButton.setIconSize(QSize(fPx, fPx))
self.forwardButton.setFixedSize(fPx, fPx)
- self.forwardButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.forwardButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.forwardButton.setVisible(False)
self.forwardButton.setToolTip(self.tr("Go Forward"))
self.forwardButton.clicked.connect(self.docViewer.navForward)
@@ -693,7 +693,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.refreshButton.setContentsMargins(0, 0, 0, 0)
self.refreshButton.setIconSize(QSize(fPx, fPx))
self.refreshButton.setFixedSize(fPx, fPx)
- self.refreshButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.refreshButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.refreshButton.setVisible(False)
self.refreshButton.setToolTip(self.tr("Reload"))
self.refreshButton.clicked.connect(self._refreshDocument)
@@ -702,7 +702,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.closeButton.setContentsMargins(0, 0, 0, 0)
self.closeButton.setIconSize(QSize(fPx, fPx))
self.closeButton.setFixedSize(fPx, fPx)
- self.closeButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.closeButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.closeButton.setVisible(False)
self.closeButton.setToolTip(self.tr("Close"))
self.closeButton.clicked.connect(self._closeDocument)
@@ -761,9 +761,9 @@ def matchColours(self) -> None:
theme rather than the main GUI.
"""
palette = QPalette()
- palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
- palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
- palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
+ palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.setPalette(palette)
self.docTitle.setPalette(palette)
return
@@ -868,7 +868,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
# Show/Hide Details
self.showHide = QToolButton(self)
- self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.showHide.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.showHide.setIconSize(QSize(fPx, fPx))
self.showHide.setFixedSize(QSize(fPx, fPx))
self.showHide.clicked.connect(lambda: self.docViewer.togglePanelVisibility.emit())
@@ -878,7 +878,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.showComments = QToolButton(self)
self.showComments.setCheckable(True)
self.showComments.setChecked(CONFIG.viewComments)
- self.showComments.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.showComments.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.showComments.setIconSize(QSize(fPx, fPx))
self.showComments.setFixedSize(QSize(fPx, fPx))
self.showComments.toggled.connect(self._doToggleComments)
@@ -888,7 +888,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.showSynopsis = QToolButton(self)
self.showSynopsis.setCheckable(True)
self.showSynopsis.setChecked(CONFIG.viewSynopsis)
- self.showSynopsis.setToolButtonStyle(Qt.ToolButtonIconOnly)
+ self.showSynopsis.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.showSynopsis.setIconSize(QSize(fPx, fPx))
self.showSynopsis.setFixedSize(QSize(fPx, fPx))
self.showSynopsis.toggled.connect(self._doToggleSynopsis)
@@ -902,7 +902,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.lblComments.setContentsMargins(0, 0, 0, 0)
self.lblComments.setAutoFillBackground(True)
self.lblComments.setFixedHeight(fPx)
- self.lblComments.setAlignment(Qt.AlignLeft | Qt.AlignTop)
+ self.lblComments.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
self.lblSynopsis = QLabel(self.tr("Synopsis"))
self.lblSynopsis.setBuddy(self.showSynopsis)
@@ -911,7 +911,7 @@ def __init__(self, docViewer: GuiDocViewer) -> None:
self.lblSynopsis.setContentsMargins(0, 0, 0, 0)
self.lblSynopsis.setAutoFillBackground(True)
self.lblSynopsis.setFixedHeight(fPx)
- self.lblSynopsis.setAlignment(Qt.AlignLeft | Qt.AlignTop)
+ self.lblSynopsis.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop)
lblFont = self.font()
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
@@ -977,9 +977,9 @@ def matchColours(self) -> None:
theme rather than the main GUI.
"""
palette = QPalette()
- palette.setColor(QPalette.Window, QColor(*SHARED.theme.colBack))
- palette.setColor(QPalette.WindowText, QColor(*SHARED.theme.colText))
- palette.setColor(QPalette.Text, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Window, QColor(*SHARED.theme.colBack))
+ palette.setColor(QPalette.ColorRole.WindowText, QColor(*SHARED.theme.colText))
+ palette.setColor(QPalette.ColorRole.Text, QColor(*SHARED.theme.colText))
self.setPalette(palette)
self.lblComments.setPalette(palette)
self.lblSynopsis.setPalette(palette)
From d35dfda92f464358fd3fc9738f2c382e8eeef6af Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 23 Nov 2023 19:04:35 +0100
Subject: [PATCH 3/6] Change set selection from doc header to use a signal/slot
set
---
novelwriter/gui/doceditor.py | 4 +++-
novelwriter/gui/docviewer.py | 4 +++-
novelwriter/gui/projtree.py | 7 ++++++-
novelwriter/guimain.py | 2 ++
tests/test_gui/test_gui_docviewer.py | 11 +++++++----
5 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 39dcdc070..f1ae127b8 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -97,6 +97,7 @@ class GuiDocEditor(QPlainTextEdit):
spellCheckStateChanged = pyqtSignal(bool)
closeDocumentRequest = pyqtSignal()
toggleFocusModeRequest = pyqtSignal()
+ requestProjectItemSelected = pyqtSignal(str, bool)
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -2943,7 +2944,8 @@ def mousePressEvent(self, event: QMouseEvent):
"""Capture a click on the title and ensure that the item is
selected in the project tree.
"""
- self.mainGui.projView.setSelectedHandle(self._docHandle, doScroll=True)
+ if event.button() == Qt.MouseButton.LeftButton:
+ self.docEditor.requestProjectItemSelected.emit(self._docHandle, True)
return
# END Class GuiDocEditHeader
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index a3bdcae81..5df058108 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -59,6 +59,7 @@ class GuiDocViewer(QTextBrowser):
documentLoaded = pyqtSignal(str)
loadDocumentTagRequest = pyqtSignal(str, Enum)
togglePanelVisibility = pyqtSignal()
+ requestProjectItemSelected = pyqtSignal(str, bool)
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -834,7 +835,8 @@ def mousePressEvent(self, event: QMouseEvent) -> None:
"""Capture a click on the title and ensure that the item is
selected in the project tree.
"""
- self.mainGui.projView.setSelectedHandle(self._docHandle, doScroll=True)
+ if event.button() == Qt.MouseButton.LeftButton:
+ self.docViewer.requestProjectItemSelected.emit(self._docHandle, True)
return
# END Class GuiDocViewHeader
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 4b261afdb..d55a06020 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -138,7 +138,6 @@ def __init__(self, mainGui: GuiMain) -> None:
self.emptyTrash = self.projTree.emptyTrash
self.requestDeleteItem = self.projTree.requestDeleteItem
self.getSelectedHandle = self.projTree.getSelectedHandle
- self.setSelectedHandle = self.projTree.setSelectedHandle
self.changedSince = self.projTree.changedSince
self.createNewNote = self.projTree.createNewNote
@@ -203,6 +202,12 @@ def renameTreeItem(self, tHandle: str | None = None) -> bool:
# Public Slots
##
+ @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 updateItemValues(self, tHandle: str) -> None:
"""Update tree item"""
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 635e9b8f0..4e885b247 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -284,10 +284,12 @@ def __init__(self) -> None:
self.docEditor.spellCheckStateChanged.connect(self.mainMenu.setSpellCheckState)
self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
+ self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
self.docViewer.togglePanelVisibility.connect(self._toggleViewerPanelVisibility)
+ self.docViewer.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
self.docViewerPanel.loadDocumentTagRequest.connect(self._followTag)
self.docViewerPanel.openDocumentRequest.connect(self._openDocument)
diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py
index b05e35b11..a5fdc77be 100644
--- a/tests/test_gui/test_gui_docviewer.py
+++ b/tests/test_gui/test_gui_docviewer.py
@@ -23,8 +23,8 @@
from mocked import causeException
-from PyQt5.QtGui import QTextCursor
-from PyQt5.QtCore import Qt, QUrl
+from PyQt5.QtGui import QMouseEvent, QTextCursor
+from PyQt5.QtCore import QEvent, QPoint, Qt, QUrl
from PyQt5.QtWidgets import QMenu, qApp, QAction
from novelwriter import CONFIG, SHARED
@@ -58,7 +58,10 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
assert nwGUI.projView.projTree.getSelectedHandle() is None
# Re-select via header click
- docViewer.docHeader.mousePressEvent(None) # type: ignore
+ button = Qt.MouseButton.LeftButton
+ modifier = Qt.KeyboardModifier.NoModifier
+ event = QMouseEvent(QEvent.MouseButtonPress, QPoint(), button, button, modifier)
+ docViewer.docHeader.mousePressEvent(event)
assert nwGUI.projView.projTree.getSelectedHandle() == "88243afbe5ed8"
# Reload the text
@@ -127,7 +130,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
assert docViewer.docAction(nwDocAction.COPY) is False
# Open again via menu
- assert nwGUI.projView.setSelectedHandle("88243afbe5ed8")
+ assert nwGUI.projView.projTree.setSelectedHandle("88243afbe5ed8")
nwGUI.mainMenu.aViewDoc.activate(QAction.Trigger)
# Open context menu
From 3f842c2fbee24e1ca212abd48faccb5f227a40e1 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 23 Nov 2023 19:40:17 +0100
Subject: [PATCH 4/6] Add a context menu option to rename a document from any
title within it
---
novelwriter/gui/doceditor.py | 14 +++++++++++-
novelwriter/gui/projtree.py | 34 ++++++++++++++---------------
novelwriter/guimain.py | 1 +
tests/test_gui/test_gui_projtree.py | 9 +++-----
4 files changed, 34 insertions(+), 24 deletions(-)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index f1ae127b8..28225d8b2 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -98,6 +98,7 @@ class GuiDocEditor(QPlainTextEdit):
closeDocumentRequest = pyqtSignal()
toggleFocusModeRequest = pyqtSignal()
requestProjectItemSelected = pyqtSignal(str, bool)
+ requestProjectItemRenamed = pyqtSignal(str, str)
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -371,7 +372,7 @@ def initEditor(self) -> None:
return
- def loadText(self, tHandle, tLine=None) -> bool:
+ def loadText(self, tHandle: str, tLine=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
risk overwriting the file if it exists. This can for instance
@@ -1071,8 +1072,12 @@ def _openContextMenu(self, pos: QPoint) -> None:
"""
uCursor = self.textCursor()
pCursor = self.cursorForPosition(pos)
+ pBlock = pCursor.block()
ctxMenu = QMenu(self)
+ if pBlock.userState() == GuiDocHighlighter.BLOCK_TITLE:
+ aLabel = ctxMenu.addAction(self.tr("Set as Document Name"))
+ aLabel.triggered.connect(lambda: self._emitRenameItem(pBlock))
# Follow
status = self._processTag(cursor=pCursor, follow=False)
@@ -1867,6 +1872,13 @@ def _processTag(self, cursor: QTextCursor | None = None,
return nwTrinary.NEUTRAL
+ def _emitRenameItem(self, block: QTextBlock) -> None:
+ """Emit a signal to request an item be renamed."""
+ if self._docHandle:
+ text = block.text().lstrip("#").lstrip("!").strip()
+ self.requestProjectItemRenamed.emit(self._docHandle, text)
+ return
+
def _openContextFromCursor(self) -> None:
"""Open the spell check context menu at the cursor."""
self._openContextMenu(self.cursorRect().center())
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index d55a06020..607e694a7 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -190,17 +190,20 @@ def treeHasFocus(self) -> bool:
"""Check if the project tree has focus."""
return self.projTree.hasFocus()
- def renameTreeItem(self, tHandle: str | None = None) -> bool:
+ ##
+ # Public Slots
+ ##
+
+ @pyqtSlot(str, str)
+ def renameTreeItem(self, tHandle: str | None = None, name: str = "") -> None:
"""External request to rename an item or the currently selected
item. This is triggered by the global menu or keyboard shortcut.
"""
if tHandle is None:
tHandle = self.projTree.getSelectedHandle()
- return self.projTree.renameTreeItem(tHandle) if tHandle else False
-
- ##
- # Public Slots
- ##
+ if tHandle:
+ self.projTree.renameTreeItem(tHandle, name=name)
+ return
@pyqtSlot(str, bool)
def setSelectedHandle(self, tHandle: str, doScroll: bool = False) -> None:
@@ -766,19 +769,16 @@ def moveToLevel(self, step: int) -> None:
self.setCurrentItem(tItem.child(0))
return
- def renameTreeItem(self, tHandle: str) -> bool:
+ def renameTreeItem(self, tHandle: str, name: str = "") -> None:
"""Open a dialog to edit the label of an item."""
tItem = SHARED.project.tree[tHandle]
- if tItem is None:
- return False
-
- newLabel, dlgOk = GuiEditLabel.getLabel(self, text=tItem.itemName)
- if dlgOk:
- tItem.setName(newLabel)
- self.setTreeItemValues(tHandle)
- self._alertTreeChange(tHandle, flush=False)
-
- return True
+ if tItem:
+ newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or tItem.itemName)
+ if dlgOk:
+ tItem.setName(newLabel)
+ self.setTreeItemValues(tHandle)
+ self._alertTreeChange(tHandle, flush=False)
+ return
def saveTreeOrder(self) -> None:
"""Build a list of the items in the project tree and send them
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index 4e885b247..defb0fe57 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -285,6 +285,7 @@ def __init__(self) -> None:
self.docEditor.closeDocumentRequest.connect(self.closeDocEditor)
self.docEditor.toggleFocusModeRequest.connect(self.toggleFocusMode)
self.docEditor.requestProjectItemSelected.connect(self.projView.setSelectedHandle)
+ self.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
self.docViewer.documentLoaded.connect(self.docViewerPanel.updateHandle)
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py
index 76495311e..679f48263 100644
--- a/tests/test_gui/test_gui_projtree.py
+++ b/tests/test_gui/test_gui_projtree.py
@@ -156,12 +156,9 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
# Rename plot folder
with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "getLabel", lambda *a, **k: ("Stuff", True))
- projTree.renameTreeItem(C.hPlotRoot) is True
+ projTree.renameTreeItem(C.hPlotRoot)
assert project.tree[C.hPlotRoot].itemName == "Stuff" # type: ignore
- # Rename invalid folder
- projTree.renameTreeItem("0000000000000") is False
-
# Other Checks
# ============
@@ -1095,12 +1092,12 @@ def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mockRnd)
mp.setattr(GuiEditLabel, "getLabel", lambda *a, **k: ("FooBar", True))
projTree.clearSelection()
assert SHARED.project.tree[C.hChapterDoc].itemName == "New Chapter" # type: ignore
- assert projView.renameTreeItem(C.hChapterDoc) is True
+ projView.renameTreeItem(C.hChapterDoc)
assert SHARED.project.tree[C.hChapterDoc].itemName == "FooBar" # type: ignore
projTree.setSelectedHandle(C.hSceneDoc)
assert SHARED.project.tree[C.hSceneDoc].itemName == "New Scene" # type: ignore
- assert projView.renameTreeItem() is True
+ projView.renameTreeItem()
assert SHARED.project.tree[C.hSceneDoc].itemName == "FooBar" # type: ignore
# Check Crash Resistance
From de11ea389ac9760232de063d38aa0b6bf7b4c027 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 23 Nov 2023 20:25:18 +0100
Subject: [PATCH 5/6] Add test coverage of editor context menu
---
novelwriter/gui/doceditor.py | 49 +++++----
tests/test_gui/test_gui_doceditor.py | 147 ++++++++++++++++++++++++++-
2 files changed, 169 insertions(+), 27 deletions(-)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 28225d8b2..fc513485c 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -1067,49 +1067,48 @@ def _insertCompletion(self, pos: int, length: int, text: str) -> None:
@pyqtSlot("QPoint")
def _openContextMenu(self, pos: QPoint) -> None:
- """Triggered by right click to open the context menu. Also
- triggered by the Ctrl+. shortcut.
- """
+ """Open the editor context menu at a given coordinate."""
uCursor = self.textCursor()
pCursor = self.cursorForPosition(pos)
pBlock = pCursor.block()
ctxMenu = QMenu(self)
+ ctxMenu.setObjectName("ContextMenu")
if pBlock.userState() == GuiDocHighlighter.BLOCK_TITLE:
- aLabel = ctxMenu.addAction(self.tr("Set as Document Name"))
- aLabel.triggered.connect(lambda: self._emitRenameItem(pBlock))
+ action = ctxMenu.addAction(self.tr("Set as Document Name"))
+ action.triggered.connect(lambda: self._emitRenameItem(pBlock))
# Follow
status = self._processTag(cursor=pCursor, follow=False)
if status == nwTrinary.POSITIVE:
- aTag = ctxMenu.addAction(self.tr("Follow Tag"))
- aTag.triggered.connect(lambda: self._processTag(cursor=pCursor, follow=True))
+ action = ctxMenu.addAction(self.tr("Follow Tag"))
+ action.triggered.connect(lambda: self._processTag(cursor=pCursor, follow=True))
ctxMenu.addSeparator()
elif status == nwTrinary.NEGATIVE:
- aTag = ctxMenu.addAction(self.tr("Create Note for Tag"))
- aTag.triggered.connect(lambda: self._processTag(cursor=pCursor, create=True))
+ action = ctxMenu.addAction(self.tr("Create Note for Tag"))
+ action.triggered.connect(lambda: self._processTag(cursor=pCursor, create=True))
ctxMenu.addSeparator()
# Cut, Copy and Paste
if uCursor.hasSelection():
- aCut = ctxMenu.addAction(self.tr("Cut"))
- aCut.triggered.connect(lambda: self.docAction(nwDocAction.CUT))
- aCopy = ctxMenu.addAction(self.tr("Copy"))
- aCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
+ action = ctxMenu.addAction(self.tr("Cut"))
+ action.triggered.connect(lambda: self.docAction(nwDocAction.CUT))
+ action = ctxMenu.addAction(self.tr("Copy"))
+ action.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
- aPaste = ctxMenu.addAction(self.tr("Paste"))
- aPaste.triggered.connect(lambda: self.docAction(nwDocAction.PASTE))
+ action = ctxMenu.addAction(self.tr("Paste"))
+ action.triggered.connect(lambda: self.docAction(nwDocAction.PASTE))
ctxMenu.addSeparator()
# Selections
- aSAll = ctxMenu.addAction(self.tr("Select All"))
- aSAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
- aSWrd = ctxMenu.addAction(self.tr("Select Word"))
- aSWrd.triggered.connect(
+ action = ctxMenu.addAction(self.tr("Select All"))
+ action.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
+ action = ctxMenu.addAction(self.tr("Select Word"))
+ action.triggered.connect(
lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, pos)
)
- aSPar = ctxMenu.addAction(self.tr("Select Paragraph"))
- aSPar.triggered.connect(lambda: self._makePosSelection(
+ action = ctxMenu.addAction(self.tr("Select Paragraph"))
+ action.triggered.connect(lambda: self._makePosSelection(
QTextCursor.SelectionType.BlockUnderCursor, pos)
)
@@ -1128,16 +1127,16 @@ def _openContextMenu(self, pos: QPoint) -> None:
ctxMenu.addSeparator()
ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
for option in suggest[:15]:
- aFix = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}")
- aFix.triggered.connect(
+ action = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}")
+ action.triggered.connect(
lambda _, option=option: self._correctWord(sCursor, option)
)
else:
ctxMenu.addAction("%s %s" % (nwUnicode.U_ENDASH, self.tr("No Suggestions")))
ctxMenu.addSeparator()
- aAdd = ctxMenu.addAction(self.tr("Add Word to Dictionary"))
- aAdd.triggered.connect(lambda: self._addWord(word, block))
+ action = ctxMenu.addAction(self.tr("Add Word to Dictionary"))
+ action.triggered.connect(lambda: self._addWord(word, block))
# Execute the context menu
ctxMenu.exec_(self.viewport().mapToGlobal(pos))
diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py
index c64649873..e9eca659f 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -20,13 +20,14 @@
"""
import pytest
+from novelwriter.dialogs.editlabel import GuiEditLabel
from tools import C, buildTestProject
from mocked import causeOSError
-from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
+from PyQt5.QtGui import QClipboard, QTextBlock, QTextCursor, QTextOption
from PyQt5.QtCore import QThreadPool, Qt
-from PyQt5.QtWidgets import QAction, qApp
+from PyQt5.QtWidgets import QAction, QMenu, qApp
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout, nwTrinary, nwWidget
@@ -209,6 +210,148 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
# END Test testGuiEditor_MetaData
+@pytest.mark.gui
+def testGuiEditor_ContextMenu(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
+ """Test the editor context menu."""
+ monkeypatch.setattr(QMenu, "exec_", lambda *a: None)
+
+ buildTestProject(nwGUI, projPath)
+ assert nwGUI.openDocument(C.hSceneDoc) is True
+ docEditor = nwGUI.docEditor
+ sceneItem = SHARED.project.tree[C.hSceneDoc]
+ assert sceneItem is not None
+
+ def getMenuForPos(pos: int, select: bool = False) -> QMenu | None:
+ nonlocal docEditor
+ cursor = docEditor.textCursor()
+ cursor.setPosition(pos)
+ if select:
+ cursor.select(QTextCursor.SelectionType.WordUnderCursor)
+ docEditor.setTextCursor(cursor)
+ docEditor._openContextMenu(docEditor.cursorRect().center())
+ for obj in docEditor.children():
+ if isinstance(obj, QMenu) and obj.objectName() == "ContextMenu":
+ return obj
+ return None
+
+ docText = (
+ "### A Scene\n\n"
+ "@pov: Jane\n"
+ "Some text ..."
+ )
+ docEditor.setPlainText(docText)
+ assert docEditor.getText() == docText
+
+ # Rename Item from Heading
+ ctxMenu = getMenuForPos(1)
+ assert ctxMenu is not None
+ actions = [x.text() for x in ctxMenu.actions() if x.text()]
+ assert actions == [
+ "Set as Document Name", "Paste",
+ "Select All", "Select Word", "Select Paragraph"
+ ]
+ with monkeypatch.context() as mp:
+ mp.setattr(GuiEditLabel, "getLabel", lambda a, text: (text, True))
+ assert sceneItem.itemName == "New Scene"
+ ctxMenu.actions()[0].trigger()
+ assert sceneItem.itemName == "A Scene"
+ ctxMenu.setObjectName("")
+ ctxMenu.deleteLater()
+
+ # Create Character
+ ctxMenu = getMenuForPos(21)
+ assert ctxMenu is not None
+ actions = [x.text() for x in ctxMenu.actions() if x.text()]
+ assert actions == [
+ "Create Note for Tag", "Paste",
+ "Select All", "Select Word", "Select Paragraph"
+ ]
+ ctxMenu.actions()[0].trigger()
+ janeItem = SHARED.project.tree["0000000000010"]
+ assert janeItem is not None
+ assert janeItem.itemName == "Jane"
+ ctxMenu.setObjectName("")
+ ctxMenu.deleteLater()
+
+ # Follow Character Tag
+ ctxMenu = getMenuForPos(21)
+ assert ctxMenu is not None
+ actions = [x.text() for x in ctxMenu.actions() if x.text()]
+ assert actions == [
+ "Follow Tag", "Paste",
+ "Select All", "Select Word", "Select Paragraph"
+ ]
+ ctxMenu.actions()[0].trigger()
+ assert nwGUI.docViewer.docHandle == "0000000000010"
+ ctxMenu.setObjectName("")
+ ctxMenu.deleteLater()
+
+ # Select Word
+ ctxMenu = getMenuForPos(31)
+ assert ctxMenu is not None
+ actions = [x.text() for x in ctxMenu.actions() if x.text()]
+ assert actions == [
+ "Paste", "Select All", "Select Word", "Select Paragraph"
+ ]
+ ctxMenu.actions()[3].trigger()
+ assert docEditor.textCursor().selectedText() == "text"
+ ctxMenu.setObjectName("")
+ ctxMenu.deleteLater()
+
+ # Select Paragraph
+ ctxMenu = getMenuForPos(31)
+ assert ctxMenu is not None
+ actions = [x.text() for x in ctxMenu.actions() if x.text()]
+ assert actions == [
+ "Paste", "Select All", "Select Word", "Select Paragraph"
+ ]
+ ctxMenu.actions()[4].trigger()
+ assert docEditor.textCursor().selectedText() == "Some text ..."
+ ctxMenu.setObjectName("")
+ ctxMenu.deleteLater()
+
+ # Select All
+ ctxMenu = getMenuForPos(31)
+ assert ctxMenu is not None
+ actions = [x.text() for x in ctxMenu.actions() if x.text()]
+ assert actions == [
+ "Paste", "Select All", "Select Word", "Select Paragraph"
+ ]
+ ctxMenu.actions()[2].trigger()
+ assert docEditor.textCursor().selectedText() == docEditor.document().toRawText()
+ ctxMenu.setObjectName("")
+ ctxMenu.deleteLater()
+
+ # Copy Text
+ ctxMenu = getMenuForPos(31, True)
+ assert ctxMenu is not None
+ assert docEditor.textCursor().selectedText() == "text"
+ actions = [x.text() for x in ctxMenu.actions() if x.text()]
+ assert actions == [
+ "Cut", "Copy", "Paste", "Select All", "Select Word", "Select Paragraph"
+ ]
+ qApp.clipboard().clear()
+ ctxMenu.actions()[1].trigger()
+ assert qApp.clipboard().text(QClipboard.Mode.Clipboard) == "text"
+
+ # Cut Text
+ qApp.clipboard().clear()
+ ctxMenu.actions()[0].trigger()
+ assert qApp.clipboard().text(QClipboard.Mode.Clipboard) == "text"
+ assert "text" not in docEditor.getText()
+
+ # Paste Text
+ ctxMenu.actions()[2].trigger()
+ assert docEditor.getText() == docText
+
+ ctxMenu.setObjectName("")
+ ctxMenu.deleteLater()
+
+ # qtbot.stop()
+
+# END Test testGuiEditor_ContextMenu
+
+
@pytest.mark.gui
def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
"""Test the document actions. This is not an extensive test of the
From e02d2b9298df0a34e77641d7ff40a53ebe28c131 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Thu, 23 Nov 2023 20:35:17 +0100
Subject: [PATCH 6/6] Add annotations import to all test source files
---
tests/conftest.py | 1 +
tests/mocked.py | 1 +
tests/test_base/test_base_common.py | 1 +
tests/test_base/test_base_config.py | 1 +
tests/test_base/test_base_error.py | 1 +
tests/test_base/test_base_init.py | 1 +
tests/test_base/test_base_shared.py | 1 +
tests/test_core/test_core_buildsettings.py | 1 +
tests/test_core/test_core_coretools.py | 1 +
tests/test_core/test_core_docbuild.py | 1 +
tests/test_core/test_core_document.py | 1 +
tests/test_core/test_core_index.py | 1 +
tests/test_core/test_core_item.py | 1 +
tests/test_core/test_core_options.py | 1 +
tests/test_core/test_core_project.py | 4 +++-
tests/test_core/test_core_projectxml.py | 1 +
tests/test_core/test_core_sessions.py | 1 +
tests/test_core/test_core_spellcheck.py | 1 +
tests/test_core/test_core_status.py | 1 +
tests/test_core/test_core_storage.py | 1 +
tests/test_core/test_core_tohtml.py | 1 +
tests/test_core/test_core_tokenizer.py | 1 +
tests/test_core/test_core_tomd.py | 1 +
tests/test_core/test_core_toodt.py | 1 +
tests/test_dialogs/test_dlg_about.py | 1 +
tests/test_dialogs/test_dlg_dialogs.py | 1 +
tests/test_dialogs/test_dlg_docmerge.py | 1 +
tests/test_dialogs/test_dlg_docsplit.py | 1 +
tests/test_dialogs/test_dlg_preferences.py | 1 +
tests/test_dialogs/test_dlg_projdetails.py | 1 +
tests/test_dialogs/test_dlg_projload.py | 1 +
tests/test_dialogs/test_dlg_projsettings.py | 1 +
tests/test_dialogs/test_dlg_wordlist.py | 1 +
tests/test_ext/test_ext_wheeleventfilter.py | 3 ++-
tests/test_gui/test_gui_doceditor.py | 3 ++-
tests/test_gui/test_gui_docviewer.py | 1 +
tests/test_gui/test_gui_docviewerpanel.py | 1 +
tests/test_gui/test_gui_guimain.py | 1 +
tests/test_gui/test_gui_i18n.py | 1 +
tests/test_gui/test_gui_mainmenu.py | 5 +++--
tests/test_gui/test_gui_noveltree.py | 1 +
tests/test_gui/test_gui_outline.py | 1 +
tests/test_gui/test_gui_statusbar.py | 1 +
tests/test_gui/test_gui_theme.py | 1 +
tests/test_tools/test_tools_dictionaries.py | 1 +
tests/test_tools/test_tools_lipsum.py | 1 +
tests/test_tools/test_tools_manusbuild.py | 5 +++--
tests/test_tools/test_tools_manuscript.py | 1 +
tests/test_tools/test_tools_manussettings.py | 1 +
tests/test_tools/test_tools_projwizard.py | 1 +
tests/test_tools/test_tools_writingstats.py | 1 +
51 files changed, 59 insertions(+), 7 deletions(-)
diff --git a/tests/conftest.py b/tests/conftest.py
index 1886bfd40..be3c243f1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/mocked.py b/tests/mocked.py
index a22258f58..0c1d215c6 100644
--- a/tests/mocked.py
+++ b/tests/mocked.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QWidget
diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py
index 08e8b3e9e..2ca1a35fa 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import time
from PyQt5.QtCore import QUrl
diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py
index d97f57a4c..d8938d45a 100644
--- a/tests/test_base/test_base_config.py
+++ b/tests/test_base/test_base_config.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/test_base/test_base_error.py b/tests/test_base/test_base_error.py
index 886a8e806..b8afa2a0e 100644
--- a/tests/test_base/test_base_error.py
+++ b/tests/test_base/test_base_error.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_base/test_base_init.py b/tests/test_base/test_base_init.py
index cbc20c517..01645e04f 100644
--- a/tests/test_base/test_base_init.py
+++ b/tests/test_base/test_base_init.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/test_base/test_base_shared.py b/tests/test_base/test_base_shared.py
index d699da6f7..4e3de75a0 100644
--- a/tests/test_base/test_base_shared.py
+++ b/tests/test_base/test_base_shared.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_core/test_core_buildsettings.py b/tests/test_core/test_core_buildsettings.py
index eb4affdfa..3486fb85f 100644
--- a/tests/test_core/test_core_buildsettings.py
+++ b/tests/test_core/test_core_buildsettings.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import uuid
diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py
index 0f127e059..c52b9ad53 100644
--- a/tests/test_core/test_core_coretools.py
+++ b/tests/test_core/test_core_coretools.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import uuid
import pytest
diff --git a/tests/test_core/test_core_docbuild.py b/tests/test_core/test_core_docbuild.py
index 4c5e924a0..854a0eddb 100644
--- a/tests/test_core/test_core_docbuild.py
+++ b/tests/test_core/test_core_docbuild.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import pytest
diff --git a/tests/test_core/test_core_document.py b/tests/test_core/test_core_document.py
index 32f0f2589..1cf887326 100644
--- a/tests/test_core/test_core_document.py
+++ b/tests/test_core/test_core_document.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_core/test_core_index.py b/tests/test_core/test_core_index.py
index 6354b39c6..9e7856e84 100644
--- a/tests/test_core/test_core_index.py
+++ b/tests/test_core/test_core_index.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import pytest
diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py
index 2878137f1..5f4b8512a 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import copy
import pytest
diff --git a/tests/test_core/test_core_options.py b/tests/test_core/test_core_options.py
index 013456302..44ac84dc4 100644
--- a/tests/test_core/test_core_options.py
+++ b/tests/test_core/test_core_options.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import pytest
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index 1cc583428..475c9ce18 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -18,8 +18,8 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
-from PyQt5.QtWidgets import QMessageBox
import pytest
from shutil import copyfile
@@ -28,6 +28,8 @@
from mocked import causeOSError
from tools import C, cmpFiles, buildTestProject, XML_IGNORE
+from PyQt5.QtWidgets import QMessageBox
+
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemClass
from novelwriter.constants import nwFiles
diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py
index a6c6e5e17..6b43ca06a 100644
--- a/tests/test_core/test_core_projectxml.py
+++ b/tests/test_core/test_core_projectxml.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import pytest
diff --git a/tests/test_core/test_core_sessions.py b/tests/test_core/test_core_sessions.py
index 159e7469f..efbbad10d 100644
--- a/tests/test_core/test_core_sessions.py
+++ b/tests/test_core/test_core_sessions.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_core/test_core_spellcheck.py b/tests/test_core/test_core_spellcheck.py
index d20269b58..6645f6c11 100644
--- a/tests/test_core/test_core_spellcheck.py
+++ b/tests/test_core/test_core_spellcheck.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py
index a42701b9f..318203890 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_core/test_core_storage.py b/tests/test_core/test_core_storage.py
index 19d8a5b57..507705396 100644
--- a/tests/test_core/test_core_storage.py
+++ b/tests/test_core/test_core_storage.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import pytest
diff --git a/tests/test_core/test_core_tohtml.py b/tests/test_core/test_core_tohtml.py
index 771040ab9..b0776ef6e 100644
--- a/tests/test_core/test_core_tohtml.py
+++ b/tests/test_core/test_core_tohtml.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py
index d5cbee872..c218b7d75 100644
--- a/tests/test_core/test_core_tokenizer.py
+++ b/tests/test_core/test_core_tokenizer.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import pytest
diff --git a/tests/test_core/test_core_tomd.py b/tests/test_core/test_core_tomd.py
index 204f1ac13..c44447a41 100644
--- a/tests/test_core/test_core_tomd.py
+++ b/tests/test_core/test_core_tomd.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_core/test_core_toodt.py b/tests/test_core/test_core_toodt.py
index 24be48837..ab6bf41c6 100644
--- a/tests/test_core/test_core_toodt.py
+++ b/tests/test_core/test_core_toodt.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
import zipfile
diff --git a/tests/test_dialogs/test_dlg_about.py b/tests/test_dialogs/test_dlg_about.py
index d2ab97a65..36ba5d42a 100644
--- a/tests/test_dialogs/test_dlg_about.py
+++ b/tests/test_dialogs/test_dlg_about.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_dialogs/test_dlg_dialogs.py b/tests/test_dialogs/test_dlg_dialogs.py
index 39c8c770d..ad0c338a9 100644
--- a/tests/test_dialogs/test_dlg_dialogs.py
+++ b/tests/test_dialogs/test_dlg_dialogs.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_dialogs/test_dlg_docmerge.py b/tests/test_dialogs/test_dlg_docmerge.py
index 8071b555a..928253565 100644
--- a/tests/test_dialogs/test_dlg_docmerge.py
+++ b/tests/test_dialogs/test_dlg_docmerge.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_dialogs/test_dlg_docsplit.py b/tests/test_dialogs/test_dlg_docsplit.py
index 0de88a72a..adf186da4 100644
--- a/tests/test_dialogs/test_dlg_docsplit.py
+++ b/tests/test_dialogs/test_dlg_docsplit.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py
index dc51fd323..ae4d34aee 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_dialogs/test_dlg_projdetails.py b/tests/test_dialogs/test_dlg_projdetails.py
index 88fd9a928..8ea284435 100644
--- a/tests/test_dialogs/test_dlg_projdetails.py
+++ b/tests/test_dialogs/test_dlg_projdetails.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_dialogs/test_dlg_projload.py b/tests/test_dialogs/test_dlg_projload.py
index e5afe6aad..ad1bd49a9 100644
--- a/tests/test_dialogs/test_dlg_projload.py
+++ b/tests/test_dialogs/test_dlg_projload.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_dialogs/test_dlg_projsettings.py b/tests/test_dialogs/test_dlg_projsettings.py
index 824d04033..1413bb4a4 100644
--- a/tests/test_dialogs/test_dlg_projsettings.py
+++ b/tests/test_dialogs/test_dlg_projsettings.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_dialogs/test_dlg_wordlist.py b/tests/test_dialogs/test_dlg_wordlist.py
index 6012cf8e4..e775baa00 100644
--- a/tests/test_dialogs/test_dlg_wordlist.py
+++ b/tests/test_dialogs/test_dlg_wordlist.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_ext/test_ext_wheeleventfilter.py b/tests/test_ext/test_ext_wheeleventfilter.py
index 1120a237b..2d348358e 100644
--- a/tests/test_ext/test_ext_wheeleventfilter.py
+++ b/tests/test_ext/test_ext_wheeleventfilter.py
@@ -18,11 +18,12 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
-from PyQt5.QtCore import QEvent, QObject, QPoint, Qt
import pytest
from PyQt5.QtGui import QKeyEvent, QWheelEvent
+from PyQt5.QtCore import QEvent, QObject, QPoint, Qt
from PyQt5.QtWidgets import QWidget
from novelwriter.extensions.wheeleventfilter import WheelEventFilter
diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py
index e9eca659f..512171828 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -18,9 +18,9 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
-from novelwriter.dialogs.editlabel import GuiEditLabel
from tools import C, buildTestProject
from mocked import causeOSError
@@ -34,6 +34,7 @@
from novelwriter.constants import nwKeyWords, nwUnicode
from novelwriter.core.index import countWords
from novelwriter.gui.doceditor import GuiDocEditor, GuiDocToolBar
+from novelwriter.dialogs.editlabel import GuiEditLabel
KEY_DELAY = 1
diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py
index a5fdc77be..79c8dedbb 100644
--- a/tests/test_gui/test_gui_docviewer.py
+++ b/tests/test_gui/test_gui_docviewer.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py
index cff00ccff..1ff773ae3 100644
--- a/tests/test_gui/test_gui_docviewerpanel.py
+++ b/tests/test_gui/test_gui_docviewerpanel.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py
index 52e2bc0fd..4ed784bec 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/test_gui/test_gui_i18n.py b/tests/test_gui/test_gui_i18n.py
index a53ef8378..210dc710e 100644
--- a/tests/test_gui/test_gui_i18n.py
+++ b/tests/test_gui/test_gui_i18n.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index c00fe9da5..5bf25674d 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -18,15 +18,16 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
+from tools import C, writeFile, buildTestProject
+
from PyQt5.QtGui import QTextCursor, QTextBlock
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
-from tools import C, writeFile, buildTestProject
-
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert
from novelwriter.constants import nwKeyWords, nwUnicode
diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py
index e01c0dd9c..7cd0eb185 100644
--- a/tests/test_gui/test_gui_noveltree.py
+++ b/tests/test_gui/test_gui_noveltree.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_gui/test_gui_outline.py b/tests/test_gui/test_gui_outline.py
index 85ea46408..8e0f72ce0 100644
--- a/tests/test_gui/test_gui_outline.py
+++ b/tests/test_gui/test_gui_outline.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import time
import pytest
diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py
index 2a024f8a3..e500dfd9e 100644
--- a/tests/test_gui/test_gui_statusbar.py
+++ b/tests/test_gui/test_gui_statusbar.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import time
import pytest
diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py
index cfb16ab17..9bc452d79 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_tools/test_tools_dictionaries.py b/tests/test_tools/test_tools_dictionaries.py
index 5c3b471e0..eee0f0f7b 100644
--- a/tests/test_tools/test_tools_dictionaries.py
+++ b/tests/test_tools/test_tools_dictionaries.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
import enchant
diff --git a/tests/test_tools/test_tools_lipsum.py b/tests/test_tools/test_tools_lipsum.py
index 31944091c..191608515 100644
--- a/tests/test_tools/test_tools_lipsum.py
+++ b/tests/test_tools/test_tools_lipsum.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py
index a23f383e3..d29f31456 100644
--- a/tests/test_tools/test_tools_manusbuild.py
+++ b/tests/test_tools/test_tools_manusbuild.py
@@ -18,9 +18,8 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
-from PyQt5.QtCore import QUrl
-from PyQt5.QtGui import QDesktopServices
import pytest
from pathlib import Path
@@ -28,6 +27,8 @@
from tools import buildTestProject
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QDialogButtonBox, QFileDialog, QListWidgetItem, QMessageBox
from novelwriter.enum import nwBuildFmt
diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py
index af8a81464..d734a117a 100644
--- a/tests/test_tools/test_tools_manuscript.py
+++ b/tests/test_tools/test_tools_manuscript.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py
index 0a5f9c5a2..a752c9666 100644
--- a/tests/test_tools/test_tools_manussettings.py
+++ b/tests/test_tools/test_tools_manussettings.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
diff --git a/tests/test_tools/test_tools_projwizard.py b/tests/test_tools/test_tools_projwizard.py
index 9fe8865b5..65a80687c 100644
--- a/tests/test_tools/test_tools_projwizard.py
+++ b/tests/test_tools/test_tools_projwizard.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/test_tools/test_tools_writingstats.py b/tests/test_tools/test_tools_writingstats.py
index 64de87753..b5868f6e1 100644
--- a/tests/test_tools/test_tools_writingstats.py
+++ b/tests/test_tools/test_tools_writingstats.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import pytest