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