Update various features in the doc editor using new helper code
This commit is contained in:
@@ -38,13 +38,12 @@ from enum import Enum
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, QUrl,
|
||||
pyqtSignal, pyqtSlot
|
||||
QObject, QPoint, QRegularExpression, QRunnable, Qt, QTimer, pyqtSignal,
|
||||
pyqtSlot
|
||||
)
|
||||
from PyQt5.QtGui import (
|
||||
QColor, QCursor, QDesktopServices, QKeyEvent, QKeySequence, QMouseEvent,
|
||||
QPalette, QPixmap, QResizeEvent, QTextBlock, QTextCursor, QTextDocument,
|
||||
QTextOption
|
||||
QColor, QCursor, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap,
|
||||
QResizeEvent, QTextBlock, QTextCursor, QTextDocument, QTextOption
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
QAction, QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit,
|
||||
@@ -52,10 +51,13 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
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.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.eventfilters import WheelEventFilter
|
||||
from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton
|
||||
@@ -392,9 +394,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
"""
|
||||
self._nwDocument = SHARED.project.storage.getDocument(tHandle)
|
||||
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 is None:
|
||||
if (docText := self._nwDocument.readDocument()) is None:
|
||||
# There was an I/O error
|
||||
self.clearEditor()
|
||||
return False
|
||||
@@ -415,10 +420,10 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.setReadOnly(False)
|
||||
self.updateDocMargins()
|
||||
|
||||
if tLine is None and self._nwItem is not None:
|
||||
self.setCursorPosition(self._nwItem.cursorPos)
|
||||
elif isinstance(tLine, int):
|
||||
if isinstance(tLine, int):
|
||||
self.setCursorLine(tLine)
|
||||
else:
|
||||
self.setCursorPosition(nwItem.cursorPos)
|
||||
|
||||
self.docHeader.setHandle(tHandle)
|
||||
self.docFooter.setHandle(tHandle)
|
||||
@@ -433,15 +438,16 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.setDocumentChanged(False)
|
||||
self._qDocument.clearUndoRedoStacks()
|
||||
self.docToolBar.setVisible(CONFIG.showEditToolBar)
|
||||
|
||||
# Process State Changes
|
||||
SHARED.project.data.setLastHandle(tHandle, "editor")
|
||||
self.itemHandleChanged.emit(tHandle)
|
||||
|
||||
# Finalise
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
# Update the status bar
|
||||
if self._nwItem is not None:
|
||||
self.updateStatusMessage.emit(
|
||||
self.tr("Opened Document: {0}").format(self._nwItem.itemName)
|
||||
)
|
||||
self.updateStatusMessage.emit(
|
||||
self.tr("Opened Document: {0}").format(nwItem.itemName)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@@ -995,7 +1001,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
cursor = self.cursorForPosition(event.pos())
|
||||
mData, mType = self._qDocument.metaDataAtPos(cursor.position())
|
||||
if mData and mType == "url":
|
||||
self._openWebsite(mData)
|
||||
SHARED.openWebsite(mData)
|
||||
else:
|
||||
self._processTag(cursor)
|
||||
super().mouseReleaseEvent(event)
|
||||
@@ -1126,48 +1132,48 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
ctxMenu.setObjectName("ContextMenu")
|
||||
if pBlock.userState() == BLOCK_TITLE:
|
||||
action = ctxMenu.addAction(self.tr("Set as Document Name"))
|
||||
action.triggered.connect(lambda: self._emitRenameItem(pBlock))
|
||||
action.triggered.connect(qtLambda(self._emitRenameItem, pBlock))
|
||||
|
||||
# URL
|
||||
(mData, mType) = self._qDocument.metaDataAtPos(pCursor.position())
|
||||
if mData and mType == "url":
|
||||
action = ctxMenu.addAction(self.tr("Open URL"))
|
||||
action.triggered.connect(lambda: self._openWebsite(mData))
|
||||
action.triggered.connect(qtLambda(SHARED.openWebsite, mData))
|
||||
ctxMenu.addSeparator()
|
||||
|
||||
# Follow
|
||||
status = self._processTag(cursor=pCursor, follow=False)
|
||||
if status == nwTrinary.POSITIVE:
|
||||
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()
|
||||
elif status == nwTrinary.NEGATIVE:
|
||||
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()
|
||||
|
||||
# Cut, Copy and Paste
|
||||
if uCursor.hasSelection():
|
||||
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.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
|
||||
action.triggered.connect(qtLambda(self.docAction, nwDocAction.COPY))
|
||||
|
||||
action = ctxMenu.addAction(self.tr("Paste"))
|
||||
action.triggered.connect(lambda: self.docAction(nwDocAction.PASTE))
|
||||
action.triggered.connect(qtLambda(self.docAction, nwDocAction.PASTE))
|
||||
ctxMenu.addSeparator()
|
||||
|
||||
# Selections
|
||||
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.triggered.connect(
|
||||
lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, pos)
|
||||
)
|
||||
action.triggered.connect(qtLambda(
|
||||
self._makePosSelection, QTextCursor.SelectionType.WordUnderCursor, pos,
|
||||
))
|
||||
action = ctxMenu.addAction(self.tr("Select Paragraph"))
|
||||
action.triggered.connect(lambda: self._makePosSelection(
|
||||
QTextCursor.SelectionType.BlockUnderCursor, pos)
|
||||
)
|
||||
action.triggered.connect(qtLambda(
|
||||
self._makePosSelection, QTextCursor.SelectionType.BlockUnderCursor, pos
|
||||
))
|
||||
|
||||
# Spell Checking
|
||||
if SHARED.project.data.spellCheck:
|
||||
@@ -1183,18 +1189,16 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
|
||||
for option in suggest[:15]:
|
||||
action = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}")
|
||||
action.triggered.connect(
|
||||
lambda _, option=option: self._correctWord(sCursor, option)
|
||||
)
|
||||
action.triggered.connect(qtLambda(self._correctWord, sCursor, option))
|
||||
else:
|
||||
trNone = self.tr("No Suggestions")
|
||||
ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {trNone}")
|
||||
|
||||
ctxMenu.addSeparator()
|
||||
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.triggered.connect(lambda: self._addWord(word, block, True))
|
||||
action.triggered.connect(qtLambda(self._addWord, word, block, True))
|
||||
|
||||
# Execute the context menu
|
||||
ctxMenu.exec(self.viewport().mapToGlobal(pos))
|
||||
@@ -1202,12 +1206,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _openWebsite(self, url: str) -> None:
|
||||
"""Open a URL in the system's default browser."""
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _runDocumentTasks(self) -> None:
|
||||
"""Run timer document tasks."""
|
||||
@@ -2207,7 +2205,7 @@ class MetaCompleter(QMenu):
|
||||
for value in sorted(options):
|
||||
rep = value + suffix
|
||||
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
|
||||
|
||||
@@ -2307,61 +2305,61 @@ class GuiDocToolBar(QWidget):
|
||||
self.tbBoldMD = NIconToolButton(self, iSz)
|
||||
self.tbBoldMD.setToolTip(self.tr("Markdown Bold"))
|
||||
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.setToolTip(self.tr("Markdown Italic"))
|
||||
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.setToolTip(self.tr("Markdown Strikethrough"))
|
||||
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.setToolTip(self.tr("Shortcode Bold"))
|
||||
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.setToolTip(self.tr("Shortcode Italic"))
|
||||
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.setToolTip(self.tr("Shortcode Strikethrough"))
|
||||
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.setToolTip(self.tr("Shortcode Underline"))
|
||||
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.setToolTip(self.tr("Shortcode Highlight"))
|
||||
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.setToolTip(self.tr("Shortcode Superscript"))
|
||||
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.setToolTip(self.tr("Shortcode Subscript"))
|
||||
self.tbSubscript.clicked.connect(
|
||||
lambda: self.requestDocAction.emit(nwDocAction.SC_SUB)
|
||||
qtLambda(self.requestDocAction.emit, nwDocAction.SC_SUB)
|
||||
)
|
||||
|
||||
# Assemble
|
||||
@@ -2825,7 +2823,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.tbButton = NIconToolButton(self, iSz)
|
||||
self.tbButton.setVisible(False)
|
||||
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.setVisible(False)
|
||||
@@ -2840,7 +2838,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.minmaxButton = NIconToolButton(self, iSz)
|
||||
self.minmaxButton.setVisible(False)
|
||||
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.setVisible(False)
|
||||
@@ -2903,9 +2901,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.outlineMenu.clear()
|
||||
for number, text in data.items():
|
||||
action = self.outlineMenu.addAction(text)
|
||||
action.triggered.connect(
|
||||
lambda _, number=number: self._gotoBlock(number)
|
||||
)
|
||||
action.triggered.connect(qtLambda(self._gotoBlock, number))
|
||||
self._docOutline = data
|
||||
logger.debug("Document outline updated in %.3f ms", 1000*(time() - tStart))
|
||||
return
|
||||
|
||||
@@ -28,8 +28,7 @@ import logging
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import QAction, QMenuBar
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
@@ -107,12 +106,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.mainGui.docEditor.toggleSpellCheck(None)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _openWebsite(self, url: str) -> None:
|
||||
"""Open a URL in the system's default browser."""
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _openUserManualFile(self) -> None:
|
||||
"""Open the documentation in PDF format."""
|
||||
@@ -1033,7 +1026,7 @@ class GuiMainMenu(QMenuBar):
|
||||
# Help > User Manual (Online)
|
||||
self.aHelpDocs = self.helpMenu.addAction(self.tr("User Manual (Online)"))
|
||||
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)
|
||||
|
||||
# Help > User Manual (PDF)
|
||||
@@ -1048,14 +1041,14 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
# Document > Report an Issue
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
+13
-21
@@ -44,7 +44,7 @@ from novelwriter.dialogs.about import GuiAbout
|
||||
from novelwriter.dialogs.preferences import GuiPreferences
|
||||
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
||||
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.docviewer import GuiDocViewer
|
||||
from novelwriter.gui.docviewerpanel import GuiDocViewerPanel
|
||||
@@ -481,8 +481,7 @@ class GuiMain(QMainWindow):
|
||||
QApplication.processEvents()
|
||||
self.openDocument(lastEdited, doScroll=True)
|
||||
|
||||
lastViewed = SHARED.project.data.getLastHandle("viewer")
|
||||
if lastViewed is not None:
|
||||
if lastViewed := SHARED.project.data.getLastHandle("viewer"):
|
||||
QApplication.processEvents()
|
||||
self.viewDocument(lastViewed)
|
||||
|
||||
@@ -512,7 +511,7 @@ class GuiMain(QMainWindow):
|
||||
# Document Actions
|
||||
##
|
||||
|
||||
def closeDocument(self, beforeOpen: bool = False) -> None:
|
||||
def closeDocument(self) -> None:
|
||||
"""Close the document and clear the editor and title field."""
|
||||
if SHARED.hasProject:
|
||||
# Disable focus mode if it is active
|
||||
@@ -531,12 +530,8 @@ class GuiMain(QMainWindow):
|
||||
doScroll: bool = False
|
||||
) -> bool:
|
||||
"""Open a specific document, optionally at a given line."""
|
||||
if not SHARED.hasProject:
|
||||
logger.error("No project 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)
|
||||
if not (SHARED.hasProject and tHandle):
|
||||
logger.error("Nothing to open open")
|
||||
return False
|
||||
|
||||
if sTitle and tLine is None:
|
||||
@@ -546,18 +541,15 @@ class GuiMain(QMainWindow):
|
||||
self._changeView(nwView.EDITOR)
|
||||
if tHandle == self.docEditor.docHandle:
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
+12
-2
@@ -30,8 +30,8 @@ from pathlib import Path
|
||||
from time import time
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, pyqtSignal
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QDesktopServices, QFont
|
||||
from PyQt5.QtWidgets import QFileDialog, QFontDialog, QMessageBox, QWidget
|
||||
|
||||
from novelwriter.common import formatFileFilter
|
||||
@@ -292,6 +292,16 @@ class SharedData(QObject):
|
||||
return widget
|
||||
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
|
||||
##
|
||||
|
||||
@@ -20,8 +20,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget
|
||||
|
||||
from novelwriter.core.project import NWProject
|
||||
@@ -63,6 +67,20 @@ def testBaseSharedData_Init():
|
||||
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
|
||||
def testBaseSharedData_Projects(monkeypatch, caplog, fncPath):
|
||||
"""Test SharedData handling of projects."""
|
||||
|
||||
@@ -24,7 +24,6 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices, QTextBlock, QTextCursor
|
||||
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
|
||||
|
||||
@@ -42,14 +41,6 @@ def testGuiMainMenu_Slots(qtbot, monkeypatch, nwGUI, projPath):
|
||||
"""Test the main menu slots."""
|
||||
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
|
||||
with monkeypatch.context() as mp:
|
||||
openUrl = MagicMock()
|
||||
|
||||
Reference in New Issue
Block a user