diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 91e4b57f..fc513485 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
@@ -97,6 +97,8 @@ class GuiDocEditor(QPlainTextEdit):
spellCheckStateChanged = pyqtSignal(bool)
closeDocumentRequest = pyqtSignal()
toggleFocusModeRequest = pyqtSignal()
+ requestProjectItemSelected = pyqtSignal(str, bool)
+ requestProjectItemRenamed = pyqtSignal(str, str)
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
@@ -157,28 +159,28 @@ class GuiDocEditor(QPlainTextEdit):
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 +276,14 @@ class GuiDocEditor(QPlainTextEdit):
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 +335,25 @@ class GuiDocEditor(QPlainTextEdit):
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())
@@ -370,7 +372,7 @@ class GuiDocEditor(QPlainTextEdit):
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
@@ -388,7 +390,7 @@ class GuiDocEditor(QPlainTextEdit):
self.clearEditor()
return False
- qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
+ qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
self._docHandle = tHandle
self._allowAutoReplace(False)
@@ -441,7 +443,7 @@ class GuiDocEditor(QPlainTextEdit):
"""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 +678,7 @@ class GuiDocEditor(QPlainTextEdit):
"""
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 +728,9 @@ class GuiDocEditor(QPlainTextEdit):
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 +911,17 @@ class GuiDocEditor(QPlainTextEdit):
* 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 +930,7 @@ class GuiDocEditor(QPlainTextEdit):
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 +961,7 @@ class GuiDocEditor(QPlainTextEdit):
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()
@@ -1065,43 +1067,50 @@ class GuiDocEditor(QPlainTextEdit):
@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:
+ 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(lambda: self._makePosSelection(QTextCursor.WordUnderCursor, pos))
- aSPar = ctxMenu.addAction(self.tr("Select Paragraph"))
- aSPar.triggered.connect(lambda: self._makePosSelection(QTextCursor.BlockUnderCursor, pos))
+ 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)
+ )
+ action = ctxMenu.addAction(self.tr("Select Paragraph"))
+ action.triggered.connect(lambda: self._makePosSelection(
+ QTextCursor.SelectionType.BlockUnderCursor, pos)
+ )
# Spell Checking
if SHARED.project.data.spellCheck:
@@ -1111,21 +1120,23 @@ class GuiDocEditor(QPlainTextEdit):
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)"))
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))
@@ -1316,8 +1327,8 @@ class GuiDocEditor(QPlainTextEdit):
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 +1354,9 @@ class GuiDocEditor(QPlainTextEdit):
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 +1374,8 @@ class GuiDocEditor(QPlainTextEdit):
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 +1486,8 @@ class GuiDocEditor(QPlainTextEdit):
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 +1589,9 @@ class GuiDocEditor(QPlainTextEdit):
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 +1611,16 @@ class GuiDocEditor(QPlainTextEdit):
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 +1740,7 @@ class GuiDocEditor(QPlainTextEdit):
# 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 +1800,9 @@ class GuiDocEditor(QPlainTextEdit):
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()
@@ -1852,6 +1871,13 @@ class GuiDocEditor(QPlainTextEdit):
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())
@@ -1943,7 +1969,9 @@ class GuiDocEditor(QPlainTextEdit):
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 +2022,8 @@ class GuiDocEditor(QPlainTextEdit):
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 +2035,18 @@ class GuiDocEditor(QPlainTextEdit):
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 +2128,10 @@ class MetaCompleter(QMenu):
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 +2285,9 @@ class GuiDocToolBar(QWidget):
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 +2367,7 @@ class GuiDocEditSearch(QFrame):
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 +2386,7 @@ class GuiDocEditSearch(QFrame):
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 +2448,7 @@ class GuiDocEditSearch(QFrame):
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 +2462,8 @@ class GuiDocEditSearch(QFrame):
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 +2521,18 @@ class GuiDocEditSearch(QFrame):
# 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 +2668,7 @@ class GuiDocEditSearch(QFrame):
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 +2684,9 @@ class GuiDocEditSearch(QFrame):
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 +2739,7 @@ class GuiDocEditSearch(QFrame):
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 +2780,7 @@ class GuiDocEditHeader(QWidget):
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 +2792,7 @@ class GuiDocEditHeader(QWidget):
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 +2801,7 @@ class GuiDocEditHeader(QWidget):
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 +2810,7 @@ class GuiDocEditHeader(QWidget):
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 +2819,7 @@ class GuiDocEditHeader(QWidget):
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 +2877,9 @@ class GuiDocEditHeader(QWidget):
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)
@@ -2924,7 +2955,8 @@ class GuiDocEditHeader(QWidget):
"""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
@@ -2961,11 +2993,13 @@ class GuiDocEditFooter(QWidget):
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 +3007,14 @@ class GuiDocEditFooter(QWidget):
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 +3022,14 @@ class GuiDocEditFooter(QWidget):
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 +3037,7 @@ class GuiDocEditFooter(QWidget):
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 +3085,9 @@ class GuiDocEditFooter(QWidget):
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)
diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py
index a0e2c3b9..5df05810 100644
--- a/novelwriter/gui/docviewer.py
+++ b/novelwriter/gui/docviewer.py
@@ -37,8 +37,8 @@ from PyQt5.QtGui import (
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
@@ -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)
@@ -75,8 +76,8 @@ class GuiDocViewer(QTextBrowser):
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 +93,7 @@ class GuiDocViewer(QTextBrowser):
self.installEventFilter(self.wheelEventFilter)
# Context Menu
- self.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self._openContextMenu)
self.initViewer()
@@ -148,14 +149,14 @@ class GuiDocViewer(QTextBrowser):
# 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 +166,19 @@ class GuiDocViewer(QTextBrowser):
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 +197,7 @@ class GuiDocViewer(QTextBrowser):
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 +273,9 @@ class GuiDocViewer(QTextBrowser):
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 +393,13 @@ class GuiDocViewer(QTextBrowser):
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 +420,9 @@ class GuiDocViewer(QTextBrowser):
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 +438,15 @@ class GuiDocViewer(QTextBrowser):
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 +664,7 @@ class GuiDocViewHeader(QWidget):
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 +676,7 @@ class GuiDocViewHeader(QWidget):
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 +685,7 @@ class GuiDocViewHeader(QWidget):
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 +694,7 @@ class GuiDocViewHeader(QWidget):
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 +703,7 @@ class GuiDocViewHeader(QWidget):
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 +762,9 @@ class GuiDocViewHeader(QWidget):
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
@@ -834,7 +835,8 @@ class GuiDocViewHeader(QWidget):
"""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
@@ -868,7 +870,7 @@ class GuiDocViewFooter(QWidget):
# 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 +880,7 @@ class GuiDocViewFooter(QWidget):
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 +890,7 @@ class GuiDocViewFooter(QWidget):
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 +904,7 @@ class GuiDocViewFooter(QWidget):
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 +913,7 @@ class GuiDocViewFooter(QWidget):
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 +979,9 @@ class GuiDocViewFooter(QWidget):
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)
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 4b261afd..607e694a 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -138,7 +138,6 @@ class GuiProjectView(QWidget):
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
@@ -191,17 +190,26 @@ class GuiProjectView(QWidget):
"""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
+ if tHandle:
+ self.projTree.renameTreeItem(tHandle, name=name)
+ return
- ##
- # 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:
@@ -761,19 +769,16 @@ class GuiProjectTree(QTreeWidget):
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 635e9b8f..defb0fe5 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -284,10 +284,13 @@ class GuiMain(QMainWindow):
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.docEditor.requestProjectItemRenamed.connect(self.projView.renameTreeItem)
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/conftest.py b/tests/conftest.py
index 1886bfd4..be3c243f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import sys
import pytest
diff --git a/tests/mocked.py b/tests/mocked.py
index a22258f5..0c1d215c 100644
--- a/tests/mocked.py
+++ b/tests/mocked.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 08e8b3e9..2ca1a35f 100644
--- a/tests/test_base/test_base_common.py
+++ b/tests/test_base/test_base_common.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 d97f57a4..d8938d45 100644
--- a/tests/test_base/test_base_config.py
+++ b/tests/test_base/test_base_config.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 886a8e80..b8afa2a0 100644
--- a/tests/test_base/test_base_error.py
+++ b/tests/test_base/test_base_error.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 cbc20c51..01645e04 100644
--- a/tests/test_base/test_base_init.py
+++ b/tests/test_base/test_base_init.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 d699da6f..4e3de75a 100644
--- a/tests/test_base/test_base_shared.py
+++ b/tests/test_base/test_base_shared.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 eb4affdf..3486fb85 100644
--- a/tests/test_core/test_core_buildsettings.py
+++ b/tests/test_core/test_core_buildsettings.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 0f127e05..c52b9ad5 100644
--- a/tests/test_core/test_core_coretools.py
+++ b/tests/test_core/test_core_coretools.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 4c5e924a..854a0edd 100644
--- a/tests/test_core/test_core_docbuild.py
+++ b/tests/test_core/test_core_docbuild.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 32f0f258..1cf88732 100644
--- a/tests/test_core/test_core_document.py
+++ b/tests/test_core/test_core_document.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 6354b39c..9e7856e8 100644
--- a/tests/test_core/test_core_index.py
+++ b/tests/test_core/test_core_index.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 2878137f..5f4b8512 100644
--- a/tests/test_core/test_core_item.py
+++ b/tests/test_core/test_core_item.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 01345630..44ac84dc 100644
--- a/tests/test_core/test_core_options.py
+++ b/tests/test_core/test_core_options.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 1cc58342..475c9ce1 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -18,8 +18,8 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
-from PyQt5.QtWidgets import QMessageBox
import pytest
from shutil import copyfile
@@ -28,6 +28,8 @@ from zipfile import ZipFile
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 a6c6e5e1..6b43ca06 100644
--- a/tests/test_core/test_core_projectxml.py
+++ b/tests/test_core/test_core_projectxml.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 159e7469..efbbad10 100644
--- a/tests/test_core/test_core_sessions.py
+++ b/tests/test_core/test_core_sessions.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 d20269b5..6645f6c1 100644
--- a/tests/test_core/test_core_spellcheck.py
+++ b/tests/test_core/test_core_spellcheck.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 a42701b9..31820389 100644
--- a/tests/test_core/test_core_status.py
+++ b/tests/test_core/test_core_status.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 19d8a5b5..50770539 100644
--- a/tests/test_core/test_core_storage.py
+++ b/tests/test_core/test_core_storage.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 771040ab..b0776ef6 100644
--- a/tests/test_core/test_core_tohtml.py
+++ b/tests/test_core/test_core_tohtml.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 d5cbee87..c218b7d7 100644
--- a/tests/test_core/test_core_tokenizer.py
+++ b/tests/test_core/test_core_tokenizer.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 204f1ac1..c44447a4 100644
--- a/tests/test_core/test_core_tomd.py
+++ b/tests/test_core/test_core_tomd.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 24be4883..ab6bf41c 100644
--- a/tests/test_core/test_core_toodt.py
+++ b/tests/test_core/test_core_toodt.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 d2ab97a6..36ba5d42 100644
--- a/tests/test_dialogs/test_dlg_about.py
+++ b/tests/test_dialogs/test_dlg_about.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 39c8c770..ad0c338a 100644
--- a/tests/test_dialogs/test_dlg_dialogs.py
+++ b/tests/test_dialogs/test_dlg_dialogs.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 8071b555..92825356 100644
--- a/tests/test_dialogs/test_dlg_docmerge.py
+++ b/tests/test_dialogs/test_dlg_docmerge.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 0de88a72..adf186da 100644
--- a/tests/test_dialogs/test_dlg_docsplit.py
+++ b/tests/test_dialogs/test_dlg_docsplit.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 dc51fd32..ae4d34ae 100644
--- a/tests/test_dialogs/test_dlg_preferences.py
+++ b/tests/test_dialogs/test_dlg_preferences.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 88fd9a92..8ea28443 100644
--- a/tests/test_dialogs/test_dlg_projdetails.py
+++ b/tests/test_dialogs/test_dlg_projdetails.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 e5afe6aa..ad1bd49a 100644
--- a/tests/test_dialogs/test_dlg_projload.py
+++ b/tests/test_dialogs/test_dlg_projload.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 824d0403..1413bb4a 100644
--- a/tests/test_dialogs/test_dlg_projsettings.py
+++ b/tests/test_dialogs/test_dlg_projsettings.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 6012cf8e..e775baa0 100644
--- a/tests/test_dialogs/test_dlg_wordlist.py
+++ b/tests/test_dialogs/test_dlg_wordlist.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 1120a237..2d348358 100644
--- a/tests/test_ext/test_ext_wheeleventfilter.py
+++ b/tests/test_ext/test_ext_wheeleventfilter.py
@@ -18,11 +18,12 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 c6464987..51217182 100644
--- a/tests/test_gui/test_gui_doceditor.py
+++ b/tests/test_gui/test_gui_doceditor.py
@@ -18,21 +18,23 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
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
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
@@ -209,6 +211,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
diff --git a/tests/test_gui/test_gui_docviewer.py b/tests/test_gui/test_gui_docviewer.py
index b05e35b1..79c8dedb 100644
--- a/tests/test_gui/test_gui_docviewer.py
+++ b/tests/test_gui/test_gui_docviewer.py
@@ -18,13 +18,14 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import pytest
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 +59,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 +131,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
diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py
index cff00ccf..1ff773ae 100644
--- a/tests/test_gui/test_gui_docviewerpanel.py
+++ b/tests/test_gui/test_gui_docviewerpanel.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 52e2bc0f..4ed784be 100644
--- a/tests/test_gui/test_gui_guimain.py
+++ b/tests/test_gui/test_gui_guimain.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 a53ef837..210dc710 100644
--- a/tests/test_gui/test_gui_i18n.py
+++ b/tests/test_gui/test_gui_i18n.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 c00fe9da..5bf25674 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -18,15 +18,16 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 e01c0dd9..7cd0eb18 100644
--- a/tests/test_gui/test_gui_noveltree.py
+++ b/tests/test_gui/test_gui_noveltree.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 85ea4640..8e0f72ce 100644
--- a/tests/test_gui/test_gui_outline.py
+++ b/tests/test_gui/test_gui_outline.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import time
import pytest
diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py
index 76495311..679f4826 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
diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py
index 2a024f8a..e500dfd9 100644
--- a/tests/test_gui/test_gui_statusbar.py
+++ b/tests/test_gui/test_gui_statusbar.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 cfb16ab1..9bc452d7 100644
--- a/tests/test_gui/test_gui_theme.py
+++ b/tests/test_gui/test_gui_theme.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 5c3b471e..eee0f0f7 100644
--- a/tests/test_tools/test_tools_dictionaries.py
+++ b/tests/test_tools/test_tools_dictionaries.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 31944091..19160851 100644
--- a/tests/test_tools/test_tools_lipsum.py
+++ b/tests/test_tools/test_tools_lipsum.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 a23f383e..d29f3145 100644
--- a/tests/test_tools/test_tools_manusbuild.py
+++ b/tests/test_tools/test_tools_manusbuild.py
@@ -18,9 +18,8 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 pytestqt.qtbot import QtBot
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 af8a8146..d734a117 100644
--- a/tests/test_tools/test_tools_manuscript.py
+++ b/tests/test_tools/test_tools_manuscript.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 0a5f9c5a..a752c966 100644
--- a/tests/test_tools/test_tools_manussettings.py
+++ b/tests/test_tools/test_tools_manussettings.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 9fe8865b..65a80687 100644
--- a/tests/test_tools/test_tools_projwizard.py
+++ b/tests/test_tools/test_tools_projwizard.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+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 64de8775..b5868f6e 100644
--- a/tests/test_tools/test_tools_writingstats.py
+++ b/tests/test_tools/test_tools_writingstats.py
@@ -18,6 +18,7 @@ General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+from __future__ import annotations
import json
import pytest