Add a context menu entry to set item name from heading (#1614)
This commit is contained in:
+147
-113
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+22
-17
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
import zipfile
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import pytest
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
import enchant
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import pytest
|
||||
|
||||
Reference in New Issue
Block a user