From a17e73b5a2b979db791843b64706fc04ccffe143 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Mon, 14 Aug 2023 17:08:53 +0200
Subject: [PATCH] Let the shared class handle alert dialogs
---
novelwriter/core/coretools.py | 19 ++--
novelwriter/core/project.py | 5 +-
novelwriter/dialogs/projload.py | 9 +-
novelwriter/dialogs/projsettings.py | 8 +-
novelwriter/dialogs/wordlist.py | 11 +--
novelwriter/gui/doceditor.py | 29 +++---
novelwriter/gui/projtree.py | 50 +++++------
novelwriter/guimain.py | 104 ++++++----------------
novelwriter/shared.py | 12 +--
novelwriter/tools/manusbuild.py | 14 +--
novelwriter/tools/manuscript.py | 4 +-
novelwriter/tools/manussettings.py | 4 +-
novelwriter/tools/writingstats.py | 9 +-
tests/mocked.py | 28 ------
tests/test_core/test_core_coretools.py | 14 +--
tests/test_gui/test_gui_mainmenu.py | 19 +---
tests/test_tools/test_tools_manusbuild.py | 4 +-
17 files changed, 107 insertions(+), 236 deletions(-)
diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py
index 7e161f02..017ea082 100644
--- a/novelwriter/core/coretools.py
+++ b/novelwriter/core/coretools.py
@@ -28,21 +28,17 @@ from __future__ import annotations
import shutil
import logging
-from typing import TYPE_CHECKING, Iterable
+from typing import Iterable
from functools import partial
from PyQt5.QtCore import QCoreApplication
-from novelwriter import CONFIG
-from novelwriter.enum import nwAlert
+from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax, simplified
from novelwriter.constants import nwItemClass
from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject
-if TYPE_CHECKING: # pragma: no cover
- from novelwriter.guimain import GuiMain
-
logger = logging.getLogger(__name__)
@@ -312,8 +308,7 @@ class ProjectBuilder:
parameter provided by the New Project Wizard.
"""
- def __init__(self, mainGui: GuiMain) -> None:
- self.mainGui = mainGui
+ def __init__(self) -> None:
self.tr = partial(QCoreApplication.translate, "NWProject")
return
@@ -478,17 +473,17 @@ class ProjectBuilder:
try:
shutil.unpack_archive(pkgSample, projPath)
except Exception as exc:
- self.mainGui.makeAlert(self.tr(
+ SHARED.error(self.tr(
"Failed to create a new example project."
- ), level=nwAlert.ERROR, exc=exc)
+ ), exc=exc)
return False
else:
- self.mainGui.makeAlert(self.tr(
+ SHARED.error(self.tr(
"Failed to create a new example project. "
"Could not find the necessary files. "
"They seem to be missing from this installation."
- ), level=nwAlert.ERROR)
+ ))
return False
return True
diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py
index 4b427721..b0ed4ad2 100644
--- a/novelwriter/core/project.py
+++ b/novelwriter/core/project.py
@@ -216,7 +216,7 @@ class NWProject(QObject):
# Project Methods
##
- def openProject(self, projPath: str | Path) -> bool:
+ def openProject(self, projPath: str | Path, clearLock: bool = False) -> bool:
"""Open the project file provided. If it doesn't exist, assume
it is a folder and look for the file within it. If successful,
parse the XML of the file and populate the project variables and
@@ -230,6 +230,9 @@ class NWProject(QObject):
# Project Lock
# ============
+ if clearLock:
+ self._storage.clearLockFile()
+
lockStatus = self._storage.readLockFile()
if len(lockStatus) > 0:
if lockStatus[0] == "ERROR":
diff --git a/novelwriter/dialogs/projload.py b/novelwriter/dialogs/projload.py
index d2f0f7f6..d15635a5 100644
--- a/novelwriter/dialogs/projload.py
+++ b/novelwriter/dialogs/projload.py
@@ -25,6 +25,7 @@ from __future__ import annotations
import logging
+from typing import TYPE_CHECKING
from pathlib import Path
from datetime import datetime
@@ -40,6 +41,9 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import formatInt
from novelwriter.constants import nwFiles
+if TYPE_CHECKING: # pragma: no cover
+ from novelwriter.guimain import GuiMain
+
logger = logging.getLogger(__name__)
@@ -55,13 +59,12 @@ class GuiProjectLoad(QDialog):
D_PATH = Qt.ItemDataRole.UserRole
- def __init__(self, mainGui):
+ def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
logger.debug("Create: GuiProjectLoad")
self.setObjectName("GuiProjectLoad")
- self.mainGui = mainGui
self.openState = self.NONE_STATE
self.openPath = None
@@ -225,7 +228,7 @@ class GuiProjectLoad(QDialog):
selList = self.listBox.selectedItems()
if selList:
projName = selList[0].text(self.C_NAME)
- msgYes = self.mainGui.askQuestion(self.tr(
+ msgYes = SHARED.question(self.tr(
"Remove '{0}' from the recent projects list? "
"The project files will not be deleted."
).format(projName))
diff --git a/novelwriter/dialogs/projsettings.py b/novelwriter/dialogs/projsettings.py
index 1680117d..dac33288 100644
--- a/novelwriter/dialogs/projsettings.py
+++ b/novelwriter/dialogs/projsettings.py
@@ -35,7 +35,6 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
-from novelwriter.enum import nwAlert
from novelwriter.common import simplified
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.pageddialog import NPagedDialog
@@ -288,8 +287,6 @@ class GuiProjectEditStatus(QWidget):
def __init__(self, projGui, isStatus):
super().__init__(parent=projGui)
- self.mainGui = projGui.mainGui
-
if isStatus:
self.theStatus = SHARED.project.data.itemStatus
pageLabel = self.tr("Novel File Status Levels")
@@ -441,9 +438,7 @@ class GuiProjectEditStatus(QWidget):
if isinstance(selItem, QTreeWidgetItem):
iRow = self.listBox.indexOfTopLevelItem(selItem)
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
- self.mainGui.makeAlert(self.tr(
- "Cannot delete a status item that is in use."
- ), level=nwAlert.ERROR)
+ SHARED.error(self.tr("Cannot delete a status item that is in use."))
else:
self.listBox.takeTopLevelItem(iRow)
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
@@ -574,7 +569,6 @@ class GuiProjectEditReplace(QWidget):
def __init__(self, projGui):
super().__init__(parent=projGui)
- self.mainGui = projGui.mainGui
self.arChanged = False
wCol0 = CONFIG.pxInt(
diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py
index e1f3704f..7bfb5d95 100644
--- a/novelwriter/dialogs/wordlist.py
+++ b/novelwriter/dialogs/wordlist.py
@@ -34,7 +34,6 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
-from novelwriter.enum import nwAlert
from novelwriter.core.spellcheck import UserDictionary
if TYPE_CHECKING: # pragma: no cover
@@ -52,8 +51,6 @@ class GuiWordList(QDialog):
self.setObjectName("GuiWordList")
self.setWindowTitle(self.tr("Project Word List"))
- self.mainGui = mainGui
-
mS = CONFIG.pxInt(250)
wW = CONFIG.pxInt(320)
wH = CONFIG.pxInt(340)
@@ -123,15 +120,13 @@ class GuiWordList(QDialog):
"""Add a new word to the word list."""
word = self.newEntry.text().strip()
if word == "":
- self.mainGui.makeAlert(self.tr(
- "Cannot add a blank word."
- ), level=nwAlert.ERROR)
+ SHARED.error(self.tr("Cannot add a blank word."))
return
if self.listBox.findItems(word, Qt.MatchExactly):
- self.mainGui.makeAlert(self.tr(
+ SHARED.error(self.tr(
"The word '{0}' is already in the word list."
- ).format(word), level=nwAlert.ERROR)
+ ).format(word))
return
self.listBox.addItem(word)
diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py
index 633805d8..cbe7f260 100644
--- a/novelwriter/gui/doceditor.py
+++ b/novelwriter/gui/doceditor.py
@@ -50,7 +50,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
-from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode, nwItemClass
+from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass
from novelwriter.common import minmax, transferCase
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode
from novelwriter.core.index import countWords
@@ -380,14 +380,14 @@ class GuiDocEditor(QTextEdit):
docSize = len(theDoc)
if docSize > nwConst.MAX_DOCSIZE:
- self.mainGui.makeAlert(self.tr(
+ SHARED.error(self.tr(
"The document you are trying to open is too big. "
"The document size is {0} MB. "
"The maximum size allowed is {1} MB."
).format(
f"{docSize/1.0e6:.2f}",
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
- ), level=nwAlert.ERROR)
+ ))
self.clearEditor()
return False
@@ -478,14 +478,14 @@ class GuiDocEditor(QTextEdit):
"""
docSize = len(theText)
if docSize > nwConst.MAX_DOCSIZE:
- self.mainGui.makeAlert(self.tr(
+ SHARED.error(self.tr(
"The text you are trying to add is too big. "
"The text size is {0} MB. "
"The maximum size allowed is {1} MB."
).format(
f"{docSize/1.0e6:.2f}",
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
- ), level=nwAlert.ERROR)
+ ))
return False
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
@@ -524,7 +524,7 @@ class GuiDocEditor(QTextEdit):
if not self._nwDocument.writeDocument(docText):
saveOk = False
if self._nwDocument._currHash != self._nwDocument._prevHash:
- msgYes = self.mainGui.askQuestion(self.tr(
+ msgYes = SHARED.question(self.tr(
"This document has been changed outside of novelWriter "
"while it was open. Overwrite the file on disk?"
))
@@ -532,10 +532,9 @@ class GuiDocEditor(QTextEdit):
saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
if not saveOk:
- self.mainGui.makeAlert(
+ SHARED.error(
self.tr("Could not save document."),
- info=self._nwDocument.getError(),
- level=nwAlert.ERROR
+ info=self._nwDocument.getError()
)
return False
@@ -723,7 +722,7 @@ class GuiDocEditor(QTextEdit):
if not CONFIG.hasEnchant:
if theMode:
- self.mainGui.makeAlert(self.tr(
+ SHARED.info(self.tr(
"Spell checking requires the package PyEnchant. "
"It does not appear to be installed."
))
@@ -867,7 +866,7 @@ class GuiDocEditor(QTextEdit):
if self._nwDocument is None:
logger.error("No document open")
return False
- self.mainGui.makeAlert(
+ SHARED.info(
self.tr("The currently open file is saved in:"),
info=self._nwDocument.getFileLocation()
)
@@ -1103,12 +1102,12 @@ class GuiDocEditor(QTextEdit):
self._lastFind = None
if self.document().characterCount() > nwConst.MAX_DOCSIZE:
- self.mainGui.makeAlert(self.tr(
+ SHARED.error(self.tr(
"The document has grown too big and you cannot add more text to it. "
"The maximum size of a single novelWriter document is {0} MB."
).format(
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
- ), level=nwAlert.ERROR)
+ ))
self.undo()
return
@@ -1665,9 +1664,7 @@ class GuiDocEditor(QTextEdit):
"""
theCursor = self.textCursor()
if not theCursor.hasSelection():
- self.mainGui.makeAlert(self.tr(
- "Please select some text before calling replace quotes."
- ), level=nwAlert.ERROR)
+ SHARED.error(self.tr("Please select some text before calling replace quotes."))
return False
posS = theCursor.selectionStart()
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 52235374..1905bdec 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -49,7 +49,7 @@ from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.dialogs.projsettings import GuiProjectSettings
from novelwriter.enum import (
- nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwWidget
+ nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwWidget
)
if TYPE_CHECKING: # pragma: no cover
@@ -582,9 +582,7 @@ class GuiProjectTree(QTreeWidget):
sHandle = self.getSelectedHandle()
pItem = SHARED.project.tree[sHandle] if sHandle else None
if sHandle is None or pItem is None:
- self.mainGui.makeAlert(self.tr(
- "Did not find anywhere to add the file or folder!"
- ), level=nwAlert.ERROR)
+ SHARED.error(self.tr("Did not find anywhere to add the file or folder!"))
return False
# Collect some information about the selected item
@@ -593,9 +591,7 @@ class GuiProjectTree(QTreeWidget):
sIsParent = False if qItem is None else qItem.childCount() > 0
if SHARED.project.tree.isTrash(sHandle):
- self.mainGui.makeAlert(self.tr(
- "Cannot add new files or folders to the Trash folder."
- ), level=nwAlert.ERROR)
+ SHARED.error(self.tr("Cannot add new files or folders to the Trash folder."))
return False
# Set default label and determine if new item is to be added
@@ -831,9 +827,7 @@ class GuiProjectTree(QTreeWidget):
logger.debug("Emptying Trash folder")
if trashHandle is None:
- self.mainGui.makeAlert(self.tr(
- "There is currently no Trash folder in this project."
- ))
+ SHARED.info(self.tr("There is currently no Trash folder in this project."))
return False
theTrash = self.getTreeFromHandle(trashHandle)
@@ -842,12 +836,10 @@ class GuiProjectTree(QTreeWidget):
nTrash = len(theTrash)
if nTrash == 0:
- self.mainGui.makeAlert(self.tr(
- "The Trash folder is already empty."
- ))
+ SHARED.info(self.tr("The Trash folder is already empty."))
return False
- msgYes = self.mainGui.askQuestion(
+ msgYes = SHARED.question(
self.tr("Permanently delete {0} file(s) from Trash?").format(nTrash)
)
if not msgYes:
@@ -893,8 +885,8 @@ class GuiProjectTree(QTreeWidget):
return False
if askFirst:
- msgYes = self.mainGui.askQuestion(
- self.tr("Move '{0}' to Trash?").format(nwItemS.itemName),
+ msgYes = SHARED.question(
+ self.tr("Move '{0}' to Trash?").format(nwItemS.itemName)
)
if not msgYes:
logger.info("Action cancelled by user")
@@ -928,9 +920,7 @@ class GuiProjectTree(QTreeWidget):
if nwItemS.isRootType():
# Only an empty ROOT folder can be deleted
if trItemS.childCount() > 0:
- self.mainGui.makeAlert(self.tr(
- "Root folders can only be deleted when they are empty."
- ), level=nwAlert.ERROR)
+ SHARED.error(self.tr("Root folders can only be deleted when they are empty."))
return False
logger.debug("Permanently deleting root folder '%s'", tHandle)
@@ -948,7 +938,7 @@ class GuiProjectTree(QTreeWidget):
else:
if askFirst:
- msgYes = self.mainGui.askQuestion(
+ msgYes = SHARED.question(
self.tr("Permanently delete '{0}'?").format(nwItemS.itemName)
)
if not msgYes:
@@ -1517,7 +1507,7 @@ class GuiProjectTree(QTreeWidget):
"""Convert a folder to a note or document."""
tItem = SHARED.project.tree[tHandle]
if tItem is not None and tItem.isFolderType():
- msgYes = self.mainGui.askQuestion(self.tr(
+ msgYes = SHARED.question(self.tr(
"Do you want to convert the folder to a {0}? "
"This action cannot be reversed."
).format(trConst(nwLabels.LAYOUT_NAME[itemLayout])))
@@ -1559,7 +1549,7 @@ class GuiProjectTree(QTreeWidget):
mrgData = dlgMerge.getData()
mrgList = mrgData.get("finalItems", [])
if not mrgList:
- self.mainGui.makeAlert(self.tr("No documents selected for merging."))
+ SHARED.info(self.tr("No documents selected for merging."))
return False
# Save the open document first, in case it's part of merge
@@ -1582,9 +1572,9 @@ class GuiProjectTree(QTreeWidget):
docMerger.appendText(sHandle, True, mLabel)
if not docMerger.writeTargetDoc():
- self.mainGui.makeAlert(
+ SHARED.error(
self.tr("Could not write document content."),
- info=docMerger.getError(), level=nwAlert.ERROR
+ info=docMerger.getError()
)
return False
@@ -1646,9 +1636,9 @@ class GuiProjectTree(QTreeWidget):
self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
self._alertTreeChange(dHandle, flush=False)
if not writeOk:
- self.mainGui.makeAlert(
+ SHARED.error(
self.tr("Could not write document content."),
- info=docSplit.getError(), level=nwAlert.ERROR
+ info=docSplit.getError()
)
if splitData.get("moveToTrash", False):
@@ -1673,7 +1663,7 @@ class GuiProjectTree(QTreeWidget):
else:
question = self.tr("Do you want to duplicate this item and all child items?")
- if not self.mainGui.askQuestion(question):
+ if not SHARED.question(question):
return False
docDup = DocDuplicator(SHARED.project)
@@ -1685,7 +1675,7 @@ class GuiProjectTree(QTreeWidget):
dupCount += 1
if dupCount != nItems:
- self.mainGui.makeAlert(self.tr("Could not duplicate all items."), level=nwAlert.WARN)
+ SHARED.warn(self.tr("Could not duplicate all items."))
self.saveTreeOrder()
@@ -1742,9 +1732,9 @@ class GuiProjectTree(QTreeWidget):
elif pHandle and pHandle in self._treeMap:
pItem = self._treeMap[pHandle]
else:
- self.mainGui.makeAlert(self.tr(
+ SHARED.error(self.tr(
"There is nowhere to add item with name '{0}'."
- ).format(nwItem.itemName), level=nwAlert.ERROR)
+ ).format(nwItem.itemName))
return None
byIndex = -1
diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py
index cabfeda9..3b7f7e8a 100644
--- a/novelwriter/guimain.py
+++ b/novelwriter/guimain.py
@@ -31,7 +31,7 @@ from pathlib import Path
from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
-from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence, QPixmap
+from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence
from PyQt5.QtWidgets import (
qApp, QDialog, QFileDialog, QMainWindow, QMessageBox, QShortcut, QSplitter,
QStackedWidget, QVBoxLayout, QWidget
@@ -63,10 +63,10 @@ from novelwriter.core.project import NWProject
from novelwriter.core.coretools import ProjectBuilder
from novelwriter.enum import (
- nwDocAction, nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwView
+ nwDocAction, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView
)
from novelwriter.common import getGuiItem, hexToInt
-from novelwriter.constants import nwFiles, nwLabels, trConst
+from novelwriter.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -133,7 +133,6 @@ class GuiMain(QMainWindow):
# =============
# Sizes
- iPx = SHARED.theme.fontPixelSize
mPx = CONFIG.pxInt(4)
hWd = CONFIG.pxInt(4)
@@ -301,15 +300,6 @@ class GuiMain(QMainWindow):
keyEscape.setKey(QKeySequence(Qt.Key_Escape))
keyEscape.activated.connect(self._keyPressEscape)
- # Cache Alert Pixmaps
- pxSize = (2*iPx, 2*iPx)
- self.alertPix: dict[nwAlert, QPixmap] = {
- nwAlert.INFO: SHARED.theme.getPixmap("alert_info", pxSize),
- nwAlert.WARN: SHARED.theme.getPixmap("alert_warn", pxSize),
- nwAlert.ERROR: SHARED.theme.getPixmap("alert_error", pxSize),
- nwAlert.ASK: SHARED.theme.getPixmap("alert_question", pxSize),
- }
-
# Check that config loaded fine
self.reportConfErr()
@@ -325,11 +315,11 @@ class GuiMain(QMainWindow):
logger.debug("Ready: GUI")
if __hexversion__[-2] == "a" and logger.getEffectiveLevel() > logging.DEBUG:
- self.makeAlert(self.tr(
+ SHARED.warn(self.tr(
"You are running an untested development version of novelWriter. "
"Please be careful when working on a live project "
"and make sure you take regular backups."
- ), level=nwAlert.WARN)
+ ))
logger.info("novelWriter is ready ...")
self.mainStatus.setStatusMessage(self.tr("novelWriter is ready ..."))
@@ -377,9 +367,9 @@ class GuiMain(QMainWindow):
"""Create a new project via the new project wizard."""
if SHARED.hasProject:
if not self.closeProject():
- self.makeAlert(self.tr(
+ SHARED.error(self.tr(
"Cannot create a new project when another project is open."
- ), level=nwAlert.ERROR)
+ ))
return False
if projData is None:
@@ -394,14 +384,14 @@ class GuiMain(QMainWindow):
return False
if (Path(projPath) / nwFiles.PROJ_FILE).is_file():
- self.makeAlert(self.tr(
+ SHARED.error(self.tr(
"A project already exists in that location. "
"Please choose another folder."
- ), level=nwAlert.ERROR)
+ ))
return False
logger.info("Creating new project")
- nwProject = ProjectBuilder(self)
+ nwProject = ProjectBuilder()
if nwProject.buildProject(projData):
self.openProject(projPath)
else:
@@ -419,7 +409,7 @@ class GuiMain(QMainWindow):
return True
if not isYes:
- msgYes = self.askQuestion("%s
%s" % (
+ msgYes = SHARED.question("%s
%s" % (
self.tr("Close the current project?"),
self.tr("Changes are saved automatically.")
))
@@ -434,7 +424,7 @@ class GuiMain(QMainWindow):
if SHARED.project.data.doBackup and CONFIG.backupOnClose:
doBackup = True
if CONFIG.askBeforeBackup:
- doBackup = self.askQuestion(self.tr("Backup the current project?"))
+ doBackup = SHARED.question(self.tr("Backup the current project?"))
if doBackup:
SHARED.project.backupProject(False)
@@ -505,9 +495,8 @@ class GuiMain(QMainWindow):
except Exception:
lockDetails = ""
- if self.askQuestion(lockText, info=lockInfo, details=lockDetails, level=nwAlert.WARN):
- SHARED.unlockProject()
- if not SHARED.openProject(projFile):
+ if SHARED.question(lockText, info=lockInfo, details=lockDetails, warn=True):
+ if not SHARED.openProject(projFile, clearLock=True):
return False
else:
return False
@@ -545,7 +534,7 @@ class GuiMain(QMainWindow):
# Check if we need to rebuild the index
if SHARED.project.index.indexBroken:
- self.makeAlert(self.tr("The project index is outdated or broken. Rebuilding index."))
+ SHARED.info(self.tr("The project index is outdated or broken. Rebuilding index."))
self.rebuildIndex()
# Make sure the changed status is set to false on things opened
@@ -731,19 +720,19 @@ class GuiMain(QMainWindow):
theText = inFile.read()
CONFIG.setLastPath(loadFile)
except Exception as exc:
- self.makeAlert(self.tr(
+ SHARED.error(self.tr(
"Could not read file. The file must be an existing text file."
- ), level=nwAlert.ERROR, exc=exc)
+ ), exc=exc)
return False
if self.docEditor.docHandle is None:
- self.makeAlert(self.tr(
+ SHARED.error(self.tr(
"Please open a document to import the text file into."
- ), level=nwAlert.ERROR)
+ ))
return False
if not self.docEditor.isEmpty:
- msgYes = self.askQuestion(self.tr(
+ msgYes = SHARED.question(self.tr(
"Importing the file will overwrite the current content of "
"the document. Do you want to proceed?"
))
@@ -845,7 +834,7 @@ class GuiMain(QMainWindow):
qApp.restoreOverrideCursor()
if not beQuiet:
- self.makeAlert(self.tr("The project index has been successfully rebuilt."))
+ SHARED.info(self.tr("The project index has been successfully rebuilt."))
return True
@@ -891,7 +880,7 @@ class GuiMain(QMainWindow):
self.saveDocument()
if dlgConf.needsRestart:
- self.makeAlert(self.tr(
+ SHARED.info(self.tr(
"Some changes will not be applied until novelWriter has been restarted."
))
@@ -1074,54 +1063,13 @@ class GuiMain(QMainWindow):
return
- def makeAlert(self, text: str, info: str = "", details: str = "",
- level: nwAlert = nwAlert.INFO, exc: Exception | None = None) -> None:
- """Alert both the user and the logger at the same time."""
- logText = " ".join(filter(None, [text, info, details]))
- if level == nwAlert.INFO:
- logger.info(logText, stacklevel=2)
- elif level == nwAlert.WARN:
- logger.warning(logText, stacklevel=2)
- elif level == nwAlert.ERROR:
- logger.error(logText, stacklevel=2, exc_info=exc)
-
- if exc is not None:
- tExc = f"{type(exc).__name__}: {str(exc)}"
- info = f"{info}
{tExc}" if info else tExc
-
- msgBox = QMessageBox(self)
- msgBox.setWindowTitle(trConst(nwLabels.ALERT_NAME[level]))
- msgBox.setText(text)
- msgBox.setInformativeText(info)
- msgBox.setDetailedText(details)
- msgBox.setStandardButtons(QMessageBox.Ok)
- msgBox.setIconPixmap(self.alertPix[level])
- msgBox.adjustSize()
- msgBox.exec_()
-
- return
-
- def askQuestion(self, text: str, info: str = "", details: str = "",
- level: nwAlert = nwAlert.ASK) -> bool:
- """Ask the user a Yes/No question, and return the answer."""
- msgBox = QMessageBox(self)
- msgBox.setWindowTitle(trConst(nwLabels.ALERT_NAME[level]))
- msgBox.setText(text)
- msgBox.setInformativeText(info)
- msgBox.setDetailedText(details)
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
- msgBox.setIconPixmap(self.alertPix[level])
- msgBox.adjustSize()
- msgBox.exec_()
- return msgBox.result() == QMessageBox.Yes
-
def reportConfErr(self) -> bool:
"""Checks if the Config module has any errors to report, and let
the user know if this is the case. The Config module caches
errors since it is initialised before the GUI itself.
"""
if CONFIG.hasError:
- self.makeAlert(CONFIG.errorText(), level=nwAlert.ERROR)
+ SHARED.error(CONFIG.errorText())
return True
return False
@@ -1132,7 +1080,7 @@ class GuiMain(QMainWindow):
def closeMain(self) -> bool:
"""Save everything, and close novelWriter."""
if SHARED.hasProject:
- msgYes = self.askQuestion("%s
%s" % (
+ msgYes = SHARED.question("%s
%s" % (
self.tr("Do you want to exit novelWriter?"),
self.tr("Changes are saved automatically.")
))
@@ -1380,13 +1328,13 @@ class GuiMain(QMainWindow):
"""
tHandle, sTitle = SHARED.project.index.getTagSource(tag)
if tHandle is None:
- self.makeAlert(self.tr(
+ SHARED.error(self.tr(
"Could not find the reference for tag '{0}'. It either doesn't "
"exist, or the index is out of date. The index can be updated "
"from the Tools menu, or by pressing {1}."
).format(
tag, "F9"
- ), level=nwAlert.ERROR)
+ ))
return None, None
return tHandle, sTitle
diff --git a/novelwriter/shared.py b/novelwriter/shared.py
index 4df3d24e..20a4677f 100644
--- a/novelwriter/shared.py
+++ b/novelwriter/shared.py
@@ -104,14 +104,14 @@ class SharedData(QObject):
logger.debug("SharedData instance initialised")
return
- def openProject(self, path: str | Path) -> bool:
+ def openProject(self, path: str | Path, clearLock: bool = False) -> bool:
"""Open a project."""
if self.project.isValid:
logger.error("A project is already open")
return False
self._lockedBy = None
- status = self.project.openProject(path)
+ status = self.project.openProject(path, clearLock=clearLock)
if status is False:
# We must cache the lock status before resetting the project
self._lockedBy = self.project.lockStatus
@@ -132,10 +132,6 @@ class SharedData(QObject):
self._resetProject()
return
- def unlockProject(self) -> bool:
- """Remove the project lock."""
- return self.project.storage.clearLockFile()
-
##
# Alert Boxes
##
@@ -171,10 +167,10 @@ class SharedData(QObject):
return
def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
- """Open an error alert box."""
+ """Open a question box."""
self._alert = _GuiAlert(self.mainGui, self.theme)
self._alert.setMessage(text, info, details)
- self._alert.setAlertType(_GuiAlert.ERROR, True)
+ self._alert.setAlertType(_GuiAlert.WARN if warn else _GuiAlert.ASK, True)
self._alert.exec_()
return self._alert.result() == QMessageBox.Yes
diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py
index 9aafaac0..390d626c 100644
--- a/novelwriter/tools/manusbuild.py
+++ b/novelwriter/tools/manusbuild.py
@@ -25,7 +25,6 @@ from __future__ import annotations
import logging
-from typing import TYPE_CHECKING
from pathlib import Path
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
@@ -36,7 +35,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
-from novelwriter.enum import nwAlert, nwBuildFmt
+from novelwriter.enum import nwBuildFmt
from novelwriter.common import makeFileNameSafe
from novelwriter.constants import nwLabels
from novelwriter.core.item import NWItem
@@ -44,9 +43,6 @@ from novelwriter.core.docbuild import NWBuildDocument
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.extensions.simpleprogress import NProgressSimple
-if TYPE_CHECKING: # pragma: no cover
- from novelwriter.guimain import GuiMain
-
logger = logging.getLogger(__name__)
@@ -59,14 +55,12 @@ class GuiManuscriptBuild(QDialog):
D_KEY = Qt.ItemDataRole.UserRole
- def __init__(self, parent: QWidget, mainGui: GuiMain, build: BuildSettings):
+ def __init__(self, parent: QWidget, build: BuildSettings):
super().__init__(parent=parent)
logger.debug("Create: GuiManuscriptBuild")
self.setObjectName("GuiManuscriptBuild")
- self.mainGui = mainGui
-
self._parent = parent
self._build = build
@@ -308,14 +302,14 @@ class GuiManuscriptBuild(QDialog):
self.buildProgress.setValue(0)
bPath = Path(self.buildPath.text())
if not bPath.is_dir():
- self.mainGui.makeAlert(self.tr("Output folder does not exist."), level=nwAlert.ERROR)
+ SHARED.error(self.tr("Output folder does not exist."))
return False
bExt = nwLabels.BUILD_EXT[bFormat]
buildPath = (bPath / makeFileNameSafe(bName)).with_suffix(bExt)
if buildPath.exists():
- if not self.mainGui.askQuestion(
+ if not SHARED.question(
self.tr("The file already exists. Do you want to overwrite it?")
):
return False
diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py
index 13b6f089..1a1e4d1d 100644
--- a/novelwriter/tools/manuscript.py
+++ b/novelwriter/tools/manuscript.py
@@ -268,7 +268,7 @@ class GuiManuscript(QDialog):
"""Delete the currently selected build settings entry."""
build = self._getSelectedBuild()
if build is not None:
- if self.mainGui.askQuestion(self.tr("Delete build '{0}'?".format(build.name))):
+ if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))):
self._builds.removeBuild(build.buildID)
self._updateBuildsList()
return
@@ -325,7 +325,7 @@ class GuiManuscript(QDialog):
"""Open the build dialog and build the manuscript."""
build = self._getSelectedBuild()
if isinstance(build, BuildSettings):
- dlgBuild = GuiManuscriptBuild(self, self.mainGui, build)
+ dlgBuild = GuiManuscriptBuild(self, build)
dlgBuild.exec_()
# After the build is done, save build settings changes
diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py
index dc7195f6..842114a9 100644
--- a/novelwriter/tools/manussettings.py
+++ b/novelwriter/tools/manussettings.py
@@ -76,8 +76,6 @@ class GuiBuildSettings(QDialog):
if CONFIG.osDarwin:
self.setWindowFlag(Qt.WindowType.Tool)
- self.mainGui = mainGui
-
self._build = build
self.setWindowTitle(self.tr("Manuscript Build Settings"))
@@ -245,7 +243,7 @@ class GuiBuildSettings(QDialog):
whether the user wants to save them.
"""
if self._build.changed:
- response = self.mainGui.askQuestion(self.tr(
+ response = SHARED.question(self.tr(
"Do you want to save your changes to '{0}'?".format(self._build.name)
))
if response:
diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py
index 83da4cb7..74b11da7 100644
--- a/novelwriter/tools/writingstats.py
+++ b/novelwriter/tools/writingstats.py
@@ -37,7 +37,6 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
-from novelwriter.enum import nwAlert
from novelwriter.error import formatException
from novelwriter.common import formatTime, checkInt, checkIntTuple, minmax
from novelwriter.constants import nwConst
@@ -72,8 +71,6 @@ class GuiWritingStats(QDialog):
if CONFIG.osDarwin:
self.setWindowFlag(Qt.WindowType.Tool)
- self.mainGui = mainGui
-
self.logData = []
self.filterData = []
self.timeFilter = 0.0
@@ -413,14 +410,14 @@ class GuiWritingStats(QDialog):
# Report to user
if wSuccess:
- self.mainGui.makeAlert(
+ SHARED.info(
self.tr("{0} file successfully written to:").format(textFmt),
info=savePath
)
else:
- self.mainGui.makeAlert(
+ SHARED.error(
self.tr("Failed to write {0} file.").format(textFmt),
- info=errMsg, level=nwAlert.ERROR
+ info=errMsg
)
return wSuccess
diff --git a/tests/mocked.py b/tests/mocked.py
index 0960138f..a22258f5 100644
--- a/tests/mocked.py
+++ b/tests/mocked.py
@@ -31,31 +31,13 @@ class MockGuiMain(QWidget):
def __init__(self):
super().__init__()
-
self.mainStatus = MockStatusBar()
self.projPath = ""
-
- # Test Variables
- self.askResponse = True
- self.lastAlert = ""
- self.lastQuestion = ""
-
return
def postLaunchTasks(self, cmdOpen):
return
- def makeAlert(self, text, info="", detals="", level=0, exc=None):
- assert isinstance(text, str)
- print("%s: %s" % (str(level), text))
- self.lastAlert = str(text)
- return
-
- def askQuestion(self, text, info="", details="", level=3):
- print("Question: %s" % text)
- self.lastQuestion = text
- return self.askResponse
-
def setStatus(self, theMessage):
return
@@ -72,16 +54,6 @@ class MockGuiMain(QWidget):
def close(self):
return "close"
- # Test Functions
-
- def undo(self):
- self.askResponse = True
- return
-
- def clear(self):
- self.lastAlert = ""
- return
-
# END Class MockGuiMain
diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py
index 765c2e08..6a39d96b 100644
--- a/tests/test_core/test_core_coretools.py
+++ b/tests/test_core/test_core_coretools.py
@@ -410,7 +410,7 @@ def testCoreTools_NewMinimal(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
testFile = tstPaths.outDir / "coreTools_NewMinimal_nwProject.nwx"
compFile = tstPaths.refDir / "coreTools_NewMinimal_nwProject.nwx"
- projBuild = ProjectBuilder(mockGUI)
+ projBuild = ProjectBuilder()
# Setting no data should fail
assert projBuild.buildProject({}) is False
@@ -432,7 +432,7 @@ def testCoreTools_NewMinimal(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
@pytest.mark.core
-def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
+def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockRnd):
"""Create a new project from a project wizard dictionary.
Custom type with chapters and scenes.
"""
@@ -460,7 +460,7 @@ def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
"numScenes": 3,
}
- projBuild = ProjectBuilder(mockGUI)
+ projBuild = ProjectBuilder()
assert projBuild.buildProject(projData) is True
copyfile(projFile, testFile)
@@ -470,7 +470,7 @@ def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
@pytest.mark.core
-def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
+def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockRnd):
"""Create a new project from a project wizard dictionary.
Custom type without chapters, but with scenes.
"""
@@ -498,7 +498,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
"numScenes": 6,
}
- projBuild = ProjectBuilder(mockGUI)
+ projBuild = ProjectBuilder()
assert projBuild.buildProject(projData) is True
copyfile(projFile, testFile)
@@ -508,7 +508,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
@pytest.mark.core
-def testCoreTools_NewSample(monkeypatch, fncPath, tstPaths, mockGUI):
+def testCoreTools_NewSample(monkeypatch, fncPath, tstPaths):
"""Check that we can create a new project can be created from the
provided sample project via a zip file.
"""
@@ -522,7 +522,7 @@ def testCoreTools_NewSample(monkeypatch, fncPath, tstPaths, mockGUI):
"popCustom": False,
}
- projBuild = ProjectBuilder(mockGUI)
+ projBuild = ProjectBuilder()
# No path set
assert projBuild.buildProject({"popSample": True}) is False
diff --git a/tests/test_gui/test_gui_mainmenu.py b/tests/test_gui/test_gui_mainmenu.py
index f0215f41..626c71aa 100644
--- a/tests/test_gui/test_gui_mainmenu.py
+++ b/tests/test_gui/test_gui_mainmenu.py
@@ -27,7 +27,7 @@ from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
from tools import C, writeFile, buildTestProject
-from novelwriter import CONFIG
+from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert
from novelwriter.constants import nwKeyWords, nwUnicode
from novelwriter.gui.doceditor import GuiDocEditor
@@ -653,21 +653,10 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
# Reveal File Location
# ====================
- theMessage = ""
-
- def recordMsg(*args, **kwargs):
- nonlocal theMessage
- theMessage = "%s|%s" % (args[0], kwargs["info"])
- return None
-
- assert not theMessage
- monkeypatch.setattr(nwGUI, "makeAlert", recordMsg)
nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)
-
- theBits = theMessage.split("|")
- assert len(theBits) == 2
- assert theBits[0] == "The currently open file is saved in:"
- assert theBits[1] == str(projPath / "content" / "000000000000f.nwd")
+ path = str(projPath / "content" / "000000000000f.nwd")
+ logMsg = SHARED.alert.logMessage if SHARED.alert else ""
+ assert logMsg == f"The currently open file is saved in: {path}"
# qtbot.stop()
diff --git a/tests/test_tools/test_tools_manusbuild.py b/tests/test_tools/test_tools_manusbuild.py
index 7e78f073..271f9b0f 100644
--- a/tests/test_tools/test_tools_manusbuild.py
+++ b/tests/test_tools/test_tools_manusbuild.py
@@ -45,7 +45,7 @@ def testManuscriptBuild_Main(
build = BuildSettings()
build.setLastPath(fncPath)
- manus = GuiManuscriptBuild(nwGUI, nwGUI, build)
+ manus = GuiManuscriptBuild(nwGUI, build)
manus.show()
# Check initial values
@@ -101,7 +101,7 @@ def testManuscriptBuild_Main(
# Error Handling
# ==============
- manus = GuiManuscriptBuild(nwGUI, nwGUI, build)
+ manus = GuiManuscriptBuild(nwGUI, build)
manus.show()
# Name, path and format should be remembered