Let the shared class handle alert dialogs

This commit is contained in:
Veronica Berglyd Olsen
2023-08-14 17:08:53 +02:00
parent 3441ff38de
commit a17e73b5a2
17 changed files with 107 additions and 236 deletions
+7 -12
View File
@@ -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
+4 -1
View File
@@ -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":
+6 -3
View File
@@ -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))
+1 -7
View File
@@ -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(
+3 -8
View File
@@ -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)
+13 -16
View File
@@ -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()
+20 -30
View File
@@ -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
+26 -78
View File
@@ -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<br>%s" % (
msgYes = SHARED.question("%s<br>%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}<br>{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<br>%s" % (
msgYes = SHARED.question("%s<br>%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
+4 -8
View File
@@ -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
+4 -10
View File
@@ -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
+2 -2
View File
@@ -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
+1 -3
View File
@@ -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:
+3 -6
View File
@@ -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
-28
View File
@@ -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
+7 -7
View File
@@ -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
+4 -15
View File
@@ -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()
+2 -2
View File
@@ -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