Update various features in the doc editor using new helper code

This commit is contained in:
Veronica Berglyd Olsen
2024-10-30 19:13:13 +01:00
parent fa0bef5500
commit a2b09fdfec
6 changed files with 104 additions and 104 deletions
+56 -60
View File
@@ -38,13 +38,12 @@ from enum import Enum
from time import time from time import time
from PyQt5.QtCore import ( from PyQt5.QtCore import (
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, QUrl, QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
pyqtSignal, pyqtSlot pyqtSlot
) )
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QCursor, QDesktopServices, QKeyEvent, QKeySequence, QMouseEvent, QColor, QCursor, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap,
QPalette, QPixmap, QResizeEvent, QTextBlock, QTextCursor, QTextDocument, QResizeEvent, QTextBlock, QTextCursor, QTextDocument, QTextOption
QTextOption
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
@@ -52,10 +51,13 @@ from PyQt5.QtWidgets import (
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax, transferCase from novelwriter.common import minmax, qtLambda, transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
from novelwriter.core.document import NWDocument from novelwriter.core.document import NWDocument
from novelwriter.enum import nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary from novelwriter.enum import (
nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwItemType,
nwTrinary
)
from novelwriter.extensions.configlayout import NColourLabel from novelwriter.extensions.configlayout import NColourLabel
from novelwriter.extensions.eventfilters import WheelEventFilter from novelwriter.extensions.eventfilters import WheelEventFilter
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton
@@ -392,9 +394,12 @@ class GuiDocEditor(QPlainTextEdit):
""" """
self._nwDocument = SHARED.project.storage.getDocument(tHandle) self._nwDocument = SHARED.project.storage.getDocument(tHandle)
self._nwItem = self._nwDocument.nwItem self._nwItem = self._nwDocument.nwItem
if not ((nwItem := self._nwItem) and nwItem.itemType == nwItemType.FILE):
logger.debug("Requested item '%s' is not a document", tHandle)
self.clearEditor()
return False
docText = self._nwDocument.readDocument() if (docText := self._nwDocument.readDocument()) is None:
if docText is None:
# There was an I/O error # There was an I/O error
self.clearEditor() self.clearEditor()
return False return False
@@ -415,10 +420,10 @@ class GuiDocEditor(QPlainTextEdit):
self.setReadOnly(False) self.setReadOnly(False)
self.updateDocMargins() self.updateDocMargins()
if tLine is None and self._nwItem is not None: if isinstance(tLine, int):
self.setCursorPosition(self._nwItem.cursorPos)
elif isinstance(tLine, int):
self.setCursorLine(tLine) self.setCursorLine(tLine)
else:
self.setCursorPosition(nwItem.cursorPos)
self.docHeader.setHandle(tHandle) self.docHeader.setHandle(tHandle)
self.docFooter.setHandle(tHandle) self.docFooter.setHandle(tHandle)
@@ -433,15 +438,16 @@ class GuiDocEditor(QPlainTextEdit):
self.setDocumentChanged(False) self.setDocumentChanged(False)
self._qDocument.clearUndoRedoStacks() self._qDocument.clearUndoRedoStacks()
self.docToolBar.setVisible(CONFIG.showEditToolBar) self.docToolBar.setVisible(CONFIG.showEditToolBar)
# Process State Changes
SHARED.project.data.setLastHandle(tHandle, "editor")
self.itemHandleChanged.emit(tHandle) self.itemHandleChanged.emit(tHandle)
# Finalise
QApplication.restoreOverrideCursor() QApplication.restoreOverrideCursor()
self.updateStatusMessage.emit(
# Update the status bar self.tr("Opened Document: {0}").format(nwItem.itemName)
if self._nwItem is not None: )
self.updateStatusMessage.emit(
self.tr("Opened Document: {0}").format(self._nwItem.itemName)
)
return True return True
@@ -995,7 +1001,7 @@ class GuiDocEditor(QPlainTextEdit):
cursor = self.cursorForPosition(event.pos()) cursor = self.cursorForPosition(event.pos())
mData, mType = self._qDocument.metaDataAtPos(cursor.position()) mData, mType = self._qDocument.metaDataAtPos(cursor.position())
if mData and mType == "url": if mData and mType == "url":
self._openWebsite(mData) SHARED.openWebsite(mData)
else: else:
self._processTag(cursor) self._processTag(cursor)
super().mouseReleaseEvent(event) super().mouseReleaseEvent(event)
@@ -1126,48 +1132,48 @@ class GuiDocEditor(QPlainTextEdit):
ctxMenu.setObjectName("ContextMenu") ctxMenu.setObjectName("ContextMenu")
if pBlock.userState() == BLOCK_TITLE: if pBlock.userState() == BLOCK_TITLE:
action = ctxMenu.addAction(self.tr("Set as Document Name")) action = ctxMenu.addAction(self.tr("Set as Document Name"))
action.triggered.connect(lambda: self._emitRenameItem(pBlock)) action.triggered.connect(qtLambda(self._emitRenameItem, pBlock))
# URL # URL
(mData, mType) = self._qDocument.metaDataAtPos(pCursor.position()) (mData, mType) = self._qDocument.metaDataAtPos(pCursor.position())
if mData and mType == "url": if mData and mType == "url":
action = ctxMenu.addAction(self.tr("Open URL")) action = ctxMenu.addAction(self.tr("Open URL"))
action.triggered.connect(lambda: self._openWebsite(mData)) action.triggered.connect(qtLambda(SHARED.openWebsite, mData))
ctxMenu.addSeparator() ctxMenu.addSeparator()
# Follow # Follow
status = self._processTag(cursor=pCursor, follow=False) status = self._processTag(cursor=pCursor, follow=False)
if status == nwTrinary.POSITIVE: if status == nwTrinary.POSITIVE:
action = ctxMenu.addAction(self.tr("Follow Tag")) action = ctxMenu.addAction(self.tr("Follow Tag"))
action.triggered.connect(lambda: self._processTag(cursor=pCursor, follow=True)) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, follow=True))
ctxMenu.addSeparator() ctxMenu.addSeparator()
elif status == nwTrinary.NEGATIVE: elif status == nwTrinary.NEGATIVE:
action = ctxMenu.addAction(self.tr("Create Note for Tag")) action = ctxMenu.addAction(self.tr("Create Note for Tag"))
action.triggered.connect(lambda: self._processTag(cursor=pCursor, create=True)) action.triggered.connect(qtLambda(self._processTag, cursor=pCursor, create=True))
ctxMenu.addSeparator() ctxMenu.addSeparator()
# Cut, Copy and Paste # Cut, Copy and Paste
if uCursor.hasSelection(): if uCursor.hasSelection():
action = ctxMenu.addAction(self.tr("Cut")) action = ctxMenu.addAction(self.tr("Cut"))
action.triggered.connect(lambda: self.docAction(nwDocAction.CUT)) action.triggered.connect(qtLambda(self.docAction, nwDocAction.CUT))
action = ctxMenu.addAction(self.tr("Copy")) action = ctxMenu.addAction(self.tr("Copy"))
action.triggered.connect(lambda: self.docAction(nwDocAction.COPY)) action.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY))
action = ctxMenu.addAction(self.tr("Paste")) action = ctxMenu.addAction(self.tr("Paste"))
action.triggered.connect(lambda: self.docAction(nwDocAction.PASTE)) action.triggered.connect(qtLambda(self.docAction, nwDocAction.PASTE))
ctxMenu.addSeparator() ctxMenu.addSeparator()
# Selections # Selections
action = ctxMenu.addAction(self.tr("Select All")) action = ctxMenu.addAction(self.tr("Select All"))
action.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL)) action.triggered.connect(qtLambda(self.docAction, nwDocAction.SEL_ALL))
action = ctxMenu.addAction(self.tr("Select Word")) action = ctxMenu.addAction(self.tr("Select Word"))
action.triggered.connect( action.triggered.connect(qtLambda(
lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, pos) self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos,
) ))
action = ctxMenu.addAction(self.tr("Select Paragraph")) action = ctxMenu.addAction(self.tr("Select Paragraph"))
action.triggered.connect(lambda: self._makePosSelection( action.triggered.connect(qtLambda(
QTextCursor.SelectionType.BlockUnderCursor, pos) self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos
) ))
# Spell Checking # Spell Checking
if SHARED.project.data.spellCheck: if SHARED.project.data.spellCheck:
@@ -1183,18 +1189,16 @@ class GuiDocEditor(QPlainTextEdit):
ctxMenu.addAction(self.tr("Spelling Suggestion(s)")) ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
for option in suggest[:15]: for option in suggest[:15]:
action = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}") action = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}")
action.triggered.connect( action.triggered.connect(qtLambda(self._correctWord, sCursor, option))
lambda _, option=option: self._correctWord(sCursor, option)
)
else: else:
trNone = self.tr("No Suggestions") trNone = self.tr("No Suggestions")
ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {trNone}") ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {trNone}")
ctxMenu.addSeparator() ctxMenu.addSeparator()
action = ctxMenu.addAction(self.tr("Ignore Word")) action = ctxMenu.addAction(self.tr("Ignore Word"))
action.triggered.connect(lambda: self._addWord(word, block, False)) action.triggered.connect(qtLambda(self._addWord, word, block, False))
action = ctxMenu.addAction(self.tr("Add Word to Dictionary")) action = ctxMenu.addAction(self.tr("Add Word to Dictionary"))
action.triggered.connect(lambda: self._addWord(word, block, True)) action.triggered.connect(qtLambda(self._addWord, word, block, True))
# Execute the context menu # Execute the context menu
ctxMenu.exec(self.viewport().mapToGlobal(pos)) ctxMenu.exec(self.viewport().mapToGlobal(pos))
@@ -1202,12 +1206,6 @@ class GuiDocEditor(QPlainTextEdit):
return return
@pyqtSlot(str)
def _openWebsite(self, url: str) -> None:
"""Open a URL in the system's default browser."""
QDesktopServices.openUrl(QUrl(url))
return
@pyqtSlot() @pyqtSlot()
def _runDocumentTasks(self) -> None: def _runDocumentTasks(self) -> None:
"""Run timer document tasks.""" """Run timer document tasks."""
@@ -2207,7 +2205,7 @@ class MetaCompleter(QMenu):
for value in sorted(options): for value in sorted(options):
rep = value + suffix rep = value + suffix
action = self.addAction(value) action = self.addAction(value)
action.triggered.connect(lambda _, r=rep: self._emitComplete(offset, length, r)) action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
return True return True
@@ -2307,61 +2305,61 @@ class GuiDocToolBar(QWidget):
self.tbBoldMD = NIconToolButton(self, iSz) self.tbBoldMD = NIconToolButton(self, iSz)
self.tbBoldMD.setToolTip(self.tr("Markdown Bold")) self.tbBoldMD.setToolTip(self.tr("Markdown Bold"))
self.tbBoldMD.clicked.connect( self.tbBoldMD.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.MD_BOLD) qtLambda(self.requestDocAction.emit, nwDocAction.MD_BOLD)
) )
self.tbItalicMD = NIconToolButton(self, iSz) self.tbItalicMD = NIconToolButton(self, iSz)
self.tbItalicMD.setToolTip(self.tr("Markdown Italic")) self.tbItalicMD.setToolTip(self.tr("Markdown Italic"))
self.tbItalicMD.clicked.connect( self.tbItalicMD.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.MD_ITALIC) qtLambda(self.requestDocAction.emit, nwDocAction.MD_ITALIC)
) )
self.tbStrikeMD = NIconToolButton(self, iSz) self.tbStrikeMD = NIconToolButton(self, iSz)
self.tbStrikeMD.setToolTip(self.tr("Markdown Strikethrough")) self.tbStrikeMD.setToolTip(self.tr("Markdown Strikethrough"))
self.tbStrikeMD.clicked.connect( self.tbStrikeMD.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.MD_STRIKE) qtLambda(self.requestDocAction.emit, nwDocAction.MD_STRIKE)
) )
self.tbBold = NIconToolButton(self, iSz) self.tbBold = NIconToolButton(self, iSz)
self.tbBold.setToolTip(self.tr("Shortcode Bold")) self.tbBold.setToolTip(self.tr("Shortcode Bold"))
self.tbBold.clicked.connect( self.tbBold.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_BOLD) qtLambda(self.requestDocAction.emit, nwDocAction.SC_BOLD)
) )
self.tbItalic = NIconToolButton(self, iSz) self.tbItalic = NIconToolButton(self, iSz)
self.tbItalic.setToolTip(self.tr("Shortcode Italic")) self.tbItalic.setToolTip(self.tr("Shortcode Italic"))
self.tbItalic.clicked.connect( self.tbItalic.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_ITALIC) qtLambda(self.requestDocAction.emit, nwDocAction.SC_ITALIC)
) )
self.tbStrike = NIconToolButton(self, iSz) self.tbStrike = NIconToolButton(self, iSz)
self.tbStrike.setToolTip(self.tr("Shortcode Strikethrough")) self.tbStrike.setToolTip(self.tr("Shortcode Strikethrough"))
self.tbStrike.clicked.connect( self.tbStrike.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_STRIKE) qtLambda(self.requestDocAction.emit, nwDocAction.SC_STRIKE)
) )
self.tbUnderline = NIconToolButton(self, iSz) self.tbUnderline = NIconToolButton(self, iSz)
self.tbUnderline.setToolTip(self.tr("Shortcode Underline")) self.tbUnderline.setToolTip(self.tr("Shortcode Underline"))
self.tbUnderline.clicked.connect( self.tbUnderline.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE) qtLambda(self.requestDocAction.emit, nwDocAction.SC_ULINE)
) )
self.tbMark = NIconToolButton(self, iSz) self.tbMark = NIconToolButton(self, iSz)
self.tbMark.setToolTip(self.tr("Shortcode Highlight")) self.tbMark.setToolTip(self.tr("Shortcode Highlight"))
self.tbMark.clicked.connect( self.tbMark.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_MARK) qtLambda(self.requestDocAction.emit, nwDocAction.SC_MARK)
) )
self.tbSuperscript = NIconToolButton(self, iSz) self.tbSuperscript = NIconToolButton(self, iSz)
self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript")) self.tbSuperscript.setToolTip(self.tr("Shortcode Superscript"))
self.tbSuperscript.clicked.connect( self.tbSuperscript.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_SUP) qtLambda(self.requestDocAction.emit, nwDocAction.SC_SUP)
) )
self.tbSubscript = NIconToolButton(self, iSz) self.tbSubscript = NIconToolButton(self, iSz)
self.tbSubscript.setToolTip(self.tr("Shortcode Subscript")) self.tbSubscript.setToolTip(self.tr("Shortcode Subscript"))
self.tbSubscript.clicked.connect( self.tbSubscript.clicked.connect(
lambda: self.requestDocAction.emit(nwDocAction.SC_SUB) qtLambda(self.requestDocAction.emit, nwDocAction.SC_SUB)
) )
# Assemble # Assemble
@@ -2825,7 +2823,7 @@ class GuiDocEditHeader(QWidget):
self.tbButton = NIconToolButton(self, iSz) self.tbButton = NIconToolButton(self, iSz)
self.tbButton.setVisible(False) self.tbButton.setVisible(False)
self.tbButton.setToolTip(self.tr("Toggle Tool Bar")) self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit()) self.tbButton.clicked.connect(qtLambda(self.toggleToolBarRequest.emit))
self.outlineButton = NIconToolButton(self, iSz) self.outlineButton = NIconToolButton(self, iSz)
self.outlineButton.setVisible(False) self.outlineButton.setVisible(False)
@@ -2840,7 +2838,7 @@ class GuiDocEditHeader(QWidget):
self.minmaxButton = NIconToolButton(self, iSz) self.minmaxButton = NIconToolButton(self, iSz)
self.minmaxButton.setVisible(False) self.minmaxButton.setVisible(False)
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode")) self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit()) self.minmaxButton.clicked.connect(qtLambda(self.docEditor.toggleFocusModeRequest.emit))
self.closeButton = NIconToolButton(self, iSz) self.closeButton = NIconToolButton(self, iSz)
self.closeButton.setVisible(False) self.closeButton.setVisible(False)
@@ -2903,9 +2901,7 @@ class GuiDocEditHeader(QWidget):
self.outlineMenu.clear() self.outlineMenu.clear()
for number, text in data.items(): for number, text in data.items():
action = self.outlineMenu.addAction(text) action = self.outlineMenu.addAction(text)
action.triggered.connect( action.triggered.connect(qtLambda(self._gotoBlock, number))
lambda _, number=number: self._gotoBlock(number)
)
self._docOutline = data self._docOutline = data
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart)) logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
return return
+5 -12
View File
@@ -28,8 +28,7 @@ import logging
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QAction, QMenuBar from PyQt5.QtWidgets import QAction, QMenuBar
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -107,12 +106,6 @@ class GuiMainMenu(QMenuBar):
self.mainGui.docEditor.toggleSpellCheck(None) self.mainGui.docEditor.toggleSpellCheck(None)
return return
@pyqtSlot(str)
def _openWebsite(self, url: str) -> None:
"""Open a URL in the system's default browser."""
QDesktopServices.openUrl(QUrl(url))
return
@pyqtSlot() @pyqtSlot()
def _openUserManualFile(self) -> None: def _openUserManualFile(self) -> None:
"""Open the documentation in PDF format.""" """Open the documentation in PDF format."""
@@ -1033,7 +1026,7 @@ class GuiMainMenu(QMenuBar):
# Help > User Manual (Online) # Help > User Manual (Online)
self.aHelpDocs = self.helpMenu.addAction(self.tr("User Manual (Online)")) self.aHelpDocs = self.helpMenu.addAction(self.tr("User Manual (Online)"))
self.aHelpDocs.setShortcut("F1") self.aHelpDocs.setShortcut("F1")
self.aHelpDocs.triggered.connect(qtLambda(self._openWebsite, nwConst.URL_DOCS)) self.aHelpDocs.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_DOCS))
self.mainGui.addAction(self.aHelpDocs) self.mainGui.addAction(self.aHelpDocs)
# Help > User Manual (PDF) # Help > User Manual (PDF)
@@ -1048,14 +1041,14 @@ class GuiMainMenu(QMenuBar):
# Document > Report an Issue # Document > Report an Issue
self.aIssue = self.helpMenu.addAction(self.tr("Report an Issue (GitHub)")) self.aIssue = self.helpMenu.addAction(self.tr("Report an Issue (GitHub)"))
self.aIssue.triggered.connect(qtLambda(self._openWebsite, nwConst.URL_REPORT)) self.aIssue.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_REPORT))
# Document > Ask a Question # Document > Ask a Question
self.aQuestion = self.helpMenu.addAction(self.tr("Ask a Question (GitHub)")) self.aQuestion = self.helpMenu.addAction(self.tr("Ask a Question (GitHub)"))
self.aQuestion.triggered.connect(qtLambda(self._openWebsite, nwConst.URL_HELP)) self.aQuestion.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_HELP))
# Document > Main Website # Document > Main Website
self.aWebsite = self.helpMenu.addAction(self.tr("The novelWriter Website")) self.aWebsite = self.helpMenu.addAction(self.tr("The novelWriter Website"))
self.aWebsite.triggered.connect(qtLambda(self._openWebsite, nwConst.URL_WEB)) self.aWebsite.triggered.connect(qtLambda(SHARED.openWebsite, nwConst.URL_WEB))
return return
+13 -21
View File
@@ -44,7 +44,7 @@ from novelwriter.dialogs.about import GuiAbout
from novelwriter.dialogs.preferences import GuiPreferences from novelwriter.dialogs.preferences import GuiPreferences
from novelwriter.dialogs.projectsettings import GuiProjectSettings from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.dialogs.wordlist import GuiWordList from novelwriter.dialogs.wordlist import GuiWordList
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwItemType, nwView from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwFocus, nwView
from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.gui.docviewer import GuiDocViewer from novelwriter.gui.docviewer import GuiDocViewer
from novelwriter.gui.docviewerpanel import GuiDocViewerPanel from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
@@ -481,8 +481,7 @@ class GuiMain(QMainWindow):
QApplication.processEvents() QApplication.processEvents()
self.openDocument(lastEdited, doScroll=True) self.openDocument(lastEdited, doScroll=True)
lastViewed = SHARED.project.data.getLastHandle("viewer") if lastViewed := SHARED.project.data.getLastHandle("viewer"):
if lastViewed is not None:
QApplication.processEvents() QApplication.processEvents()
self.viewDocument(lastViewed) self.viewDocument(lastViewed)
@@ -512,7 +511,7 @@ class GuiMain(QMainWindow):
# Document Actions # Document Actions
## ##
def closeDocument(self, beforeOpen: bool = False) -> None: def closeDocument(self) -> None:
"""Close the document and clear the editor and title field.""" """Close the document and clear the editor and title field."""
if SHARED.hasProject: if SHARED.hasProject:
# Disable focus mode if it is active # Disable focus mode if it is active
@@ -531,12 +530,8 @@ class GuiMain(QMainWindow):
doScroll: bool = False doScroll: bool = False
) -> bool: ) -> bool:
"""Open a specific document, optionally at a given line.""" """Open a specific document, optionally at a given line."""
if not SHARED.hasProject: if not (SHARED.hasProject and tHandle):
logger.error("No project open") logger.error("Nothing to open open")
return False
if not tHandle or not SHARED.project.tree.checkType(tHandle, nwItemType.FILE):
logger.debug("Requested item '%s' is not a document", tHandle)
return False return False
if sTitle and tLine is None: if sTitle and tLine is None:
@@ -546,18 +541,15 @@ class GuiMain(QMainWindow):
self._changeView(nwView.EDITOR) self._changeView(nwView.EDITOR)
if tHandle == self.docEditor.docHandle: if tHandle == self.docEditor.docHandle:
self.docEditor.setCursorLine(tLine) self.docEditor.setCursorLine(tLine)
if changeFocus:
self.docEditor.setFocus()
return True
self.closeDocument(beforeOpen=True)
if self.docEditor.loadText(tHandle, tLine):
SHARED.project.data.setLastHandle(tHandle, "editor")
self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
if changeFocus:
self.docEditor.setFocus()
else: else:
return False self.closeDocument()
if self.docEditor.loadText(tHandle, tLine):
self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
else:
return False
if changeFocus:
self.docEditor.setFocus()
return True return True
+12 -2
View File
@@ -30,8 +30,8 @@ from pathlib import Path
from time import time from time import time
from typing import TYPE_CHECKING, TypeVar from typing import TYPE_CHECKING, TypeVar
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QFont from PyQt5.QtGui import QDesktopServices, QFont
from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter from novelwriter.common import formatFileFilter
@@ -292,6 +292,16 @@ class SharedData(QObject):
return widget return widget
return None return None
##
# Public Slots
##
@pyqtSlot(str)
def openWebsite(self, url: str) -> None:
"""Open a URL in the system's default browser."""
QDesktopServices.openUrl(QUrl(url))
return
## ##
# Signal Proxy # Signal Proxy
## ##
+18
View File
@@ -20,8 +20,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
@@ -63,6 +67,20 @@ def testBaseSharedData_Init():
assert shared.projectLock is None assert shared.projectLock is None
@pytest.mark.base
def testBaseSharedData_Functions(monkeypatch):
"""Test SharedData class functions."""
shared = SharedData()
# Open URL
with monkeypatch.context() as mp:
openUrl = MagicMock()
mp.setattr(QDesktopServices, "openUrl", openUrl)
shared.openWebsite("http://www.example.com")
assert openUrl.called is True
assert openUrl.call_args[0][0] == QUrl("http://www.example.com")
@pytest.mark.base @pytest.mark.base
def testBaseSharedData_Projects(monkeypatch, caplog, fncPath): def testBaseSharedData_Projects(monkeypatch, caplog, fncPath):
"""Test SharedData handling of projects.""" """Test SharedData handling of projects."""
-9
View File
@@ -24,7 +24,6 @@ from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices, QTextBlock, QTextCursor from PyQt5.QtGui import QDesktopServices, QTextBlock, QTextCursor
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
@@ -42,14 +41,6 @@ def testGuiMainMenu_Slots(qtbot, monkeypatch, nwGUI, projPath):
"""Test the main menu slots.""" """Test the main menu slots."""
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
# Open URL
with monkeypatch.context() as mp:
openUrl = MagicMock()
mp.setattr(QDesktopServices, "openUrl", openUrl)
nwGUI.mainMenu._openWebsite("http://www.example.com")
assert openUrl.called is True
assert openUrl.call_args[0][0] == QUrl("http://www.example.com")
# Open Manual # Open Manual
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
openUrl = MagicMock() openUrl = MagicMock()