Remove dependence of main GUI class in project class
This commit is contained in:
@@ -344,7 +344,7 @@ class ProjectBuilder:
|
||||
logger.error("No project path set for the new project")
|
||||
return False
|
||||
|
||||
project = NWProject(self.mainGui)
|
||||
project = NWProject()
|
||||
if not project.storage.openProjectInPlace(projPath, newProject=True):
|
||||
return False
|
||||
|
||||
|
||||
+24
-40
@@ -33,8 +33,8 @@ from functools import partial
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, QObject, pyqtSignal
|
||||
|
||||
from novelwriter import CONFIG, __version__, __hexversion__
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||
from novelwriter import CONFIG, SHARED, __version__, __hexversion__
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
from novelwriter.core.tree import NWTree
|
||||
@@ -49,7 +49,6 @@ from novelwriter.common import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.status import NWStatus
|
||||
|
||||
@@ -61,11 +60,8 @@ class NWProject(QObject):
|
||||
statusChanged = pyqtSignal(bool)
|
||||
statusMessage = pyqtSignal(str)
|
||||
|
||||
def __init__(self, mainGui: GuiMain) -> None:
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
# Internal
|
||||
self.mainGui = mainGui
|
||||
def __init__(self, parent: QObject | None = None) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# Core Elements
|
||||
self._options = OptionState(self) # Project-specific GUI options
|
||||
@@ -197,9 +193,9 @@ class NWProject(QObject):
|
||||
if self._tree.checkType(tHandle, nwItemType.FILE):
|
||||
delDoc = self._storage.getDocument(tHandle)
|
||||
if not delDoc.deleteDocument():
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.error(
|
||||
self.tr("Could not delete document file."),
|
||||
info=delDoc.getError(), level=nwAlert.ERROR
|
||||
info=delDoc.getError()
|
||||
)
|
||||
return False
|
||||
|
||||
@@ -228,9 +224,7 @@ class NWProject(QObject):
|
||||
"""
|
||||
logger.info("Opening project: %s", projPath)
|
||||
if not self._storage.openProjectInPlace(projPath):
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Could not open project with path: {0}"
|
||||
).format(projPath), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Could not open project with path: {0}").format(projPath))
|
||||
return False
|
||||
|
||||
# Project Lock
|
||||
@@ -262,26 +256,24 @@ class NWProject(QObject):
|
||||
|
||||
if not xmlParsed:
|
||||
if xmlReader.state == XMLReadState.NOT_NWX_FILE:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Project file does not appear to be a novelWriterXML file."
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
elif xmlReader.state == XMLReadState.UNKNOWN_VERSION:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Unknown or unsupported novelWriter project file format. "
|
||||
"The project cannot be opened by this version of novelWriter. "
|
||||
"The file was saved with novelWriter version {0}."
|
||||
).format(appVersion), level=nwAlert.ERROR)
|
||||
).format(appVersion))
|
||||
else:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Failed to parse project xml."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Failed to parse project xml."))
|
||||
return False
|
||||
|
||||
# Check Legacy Upgrade
|
||||
# ====================
|
||||
|
||||
if xmlReader.state == XMLReadState.WAS_LEGACY:
|
||||
msgYes = self.mainGui.askQuestion(self.tr(
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"The file format of your project is about to be updated. "
|
||||
"If you proceed, older versions of novelWriter will no "
|
||||
"longer be able to open this project. Continue?"
|
||||
@@ -293,13 +285,13 @@ class NWProject(QObject):
|
||||
# =========================
|
||||
|
||||
if xmlReader.hexVersion > hexToInt(__hexversion__):
|
||||
msgYes = self.mainGui.askQuestion(self.tr(
|
||||
msgYes = SHARED.question(self.tr(
|
||||
"This project was saved by a newer version of "
|
||||
"novelWriter, version {0}. This is version {1}. If you "
|
||||
"continue to open the project, some attributes and "
|
||||
"settings may not be preserved, but the overall project "
|
||||
"should be fine. Continue opening the project?"
|
||||
).format(appVersion, __version__))
|
||||
).format(appVersion, __version__), warn=True)
|
||||
if not msgYes:
|
||||
return False
|
||||
|
||||
@@ -321,9 +313,9 @@ class NWProject(QObject):
|
||||
# This also handles any orphaned files found
|
||||
orphans, recovered = self._tree.checkConsistency(self.tr("Recovered"))
|
||||
if orphans > 0:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.warn(self.tr(
|
||||
"Found {0} orphaned file(s) in the project. {1} file(s) were recovered."
|
||||
).format(orphans, recovered), level=nwAlert.WARN)
|
||||
).format(orphans, recovered))
|
||||
|
||||
self._index.loadIndex()
|
||||
if xmlReader.state == XMLReadState.WAS_LEGACY:
|
||||
@@ -347,9 +339,7 @@ class NWProject(QObject):
|
||||
file.
|
||||
"""
|
||||
if not self._storage.isOpen():
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"There is no project open."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("There is no project open."))
|
||||
return False
|
||||
|
||||
saveTime = time()
|
||||
@@ -372,9 +362,7 @@ class NWProject(QObject):
|
||||
editTime = self._data.editTime + max(round(saveTime - self._session.start), 0)
|
||||
content = self._tree.pack()
|
||||
if not xmlWriter.write(self._data, content, saveTime, editTime):
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Failed to save project."
|
||||
), level=nwAlert.ERROR, exc=xmlWriter.error)
|
||||
SHARED.error(self.tr("Failed to save project."), exc=xmlWriter.error)
|
||||
return False
|
||||
|
||||
# Save other project data
|
||||
@@ -415,10 +403,10 @@ class NWProject(QObject):
|
||||
self.statusMessage.emit(self.tr("Backing up project ..."))
|
||||
|
||||
if not self._data.name:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
SHARED.error(self.tr(
|
||||
"Cannot backup project because no project name is set. "
|
||||
"Please set a Project Name in Project Settings."
|
||||
), level=nwAlert.ERROR)
|
||||
))
|
||||
return False
|
||||
|
||||
cleanName = makeFileNameSafe(self._data.name)
|
||||
@@ -427,9 +415,7 @@ class NWProject(QObject):
|
||||
try:
|
||||
baseDir.mkdir(exist_ok=True, parents=True)
|
||||
except Exception as exc:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Could not create backup folder."
|
||||
), level=nwAlert.ERROR, exc=exc)
|
||||
SHARED.error(self.tr("Could not create backup folder."), exc=exc)
|
||||
return False
|
||||
|
||||
timeStamp = formatTimeStamp(time(), fileSafe=True)
|
||||
@@ -437,14 +423,12 @@ class NWProject(QObject):
|
||||
if self._storage.zipIt(archName, compression=2):
|
||||
size = formatInt(archName.stat().st_size)
|
||||
if doNotify:
|
||||
self.mainGui.makeAlert(
|
||||
SHARED.info(
|
||||
self.tr("Created a backup of your project of size {0}B.").format(size),
|
||||
info=self.tr("Path: {0}").format(str(backupPath))
|
||||
)
|
||||
else:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Could not write backup archive."
|
||||
), level=nwAlert.ERROR)
|
||||
SHARED.error(self.tr("Could not write backup archive."))
|
||||
return False
|
||||
|
||||
self.statusMessage.emit(self.tr("Project backed up to '{0}'").format(str(archName)))
|
||||
|
||||
+110
-1
@@ -29,6 +29,7 @@ from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import QMessageBox, QWidget
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.guimain import GuiMain
|
||||
@@ -49,6 +50,7 @@ class SharedData(QObject):
|
||||
self._theme = None
|
||||
self._project = None
|
||||
self._lockedBy = None
|
||||
self._alert = None
|
||||
return
|
||||
|
||||
@property
|
||||
@@ -74,12 +76,19 @@ class SharedData(QObject):
|
||||
|
||||
@property
|
||||
def hasProject(self) -> bool:
|
||||
"""Return True of the project instance is populated."""
|
||||
return self.project.isValid
|
||||
|
||||
@property
|
||||
def projectLock(self) -> list | None:
|
||||
"""Return cached lock information for the last project."""
|
||||
return self._lockedBy
|
||||
|
||||
@property
|
||||
def alert(self) -> _GuiAlert | None:
|
||||
"""Return a pointer to the last alert box."""
|
||||
return self._alert
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -127,6 +136,48 @@ class SharedData(QObject):
|
||||
"""Remove the project lock."""
|
||||
return self.project.storage.clearLockFile()
|
||||
|
||||
##
|
||||
# Alert Boxes
|
||||
##
|
||||
|
||||
def info(self, text: str, info: str = "", details: str = "") -> None:
|
||||
"""Open an information alert box."""
|
||||
self._alert = _GuiAlert(self.mainGui, self.theme)
|
||||
self._alert.setMessage(text, info, details)
|
||||
self._alert.setAlertType(_GuiAlert.INFO, False)
|
||||
logger.info(self._alert.logMessage, stacklevel=2)
|
||||
self._alert.exec_()
|
||||
return
|
||||
|
||||
def warn(self, text: str, info: str = "", details: str = "") -> None:
|
||||
"""Open a warning alert box."""
|
||||
self._alert = _GuiAlert(self.mainGui, self.theme)
|
||||
self._alert.setMessage(text, info, details)
|
||||
self._alert.setAlertType(_GuiAlert.WARN, False)
|
||||
logger.warning(self._alert.logMessage, stacklevel=2)
|
||||
self._alert.exec_()
|
||||
return
|
||||
|
||||
def error(self, text: str, info: str = "", details: str = "",
|
||||
exc: Exception | None = None) -> None:
|
||||
"""Open an error alert box."""
|
||||
self._alert = _GuiAlert(self.mainGui, self.theme)
|
||||
self._alert.setMessage(text, info, details)
|
||||
self._alert.setAlertType(_GuiAlert.ERROR, False)
|
||||
if exc:
|
||||
self._alert.setException(exc)
|
||||
logger.error(self._alert.logMessage, stacklevel=2)
|
||||
self._alert.exec_()
|
||||
return
|
||||
|
||||
def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
|
||||
"""Open an error alert box."""
|
||||
self._alert = _GuiAlert(self.mainGui, self.theme)
|
||||
self._alert.setMessage(text, info, details)
|
||||
self._alert.setAlertType(_GuiAlert.ERROR, True)
|
||||
self._alert.exec_()
|
||||
return self._alert.result() == QMessageBox.Yes
|
||||
|
||||
##
|
||||
# Internal Slots
|
||||
##
|
||||
@@ -154,9 +205,67 @@ class SharedData(QObject):
|
||||
self._project.statusChanged.disconnect()
|
||||
self._project.statusMessage.disconnect()
|
||||
self._project.deleteLater()
|
||||
self._project = NWProject(self.mainGui)
|
||||
self._project = NWProject(self)
|
||||
self._project.statusChanged.connect(self._emitProjectStatusChange)
|
||||
self._project.statusMessage.connect(self._emitProjectStatusMeesage)
|
||||
return
|
||||
|
||||
# END Class SharedData
|
||||
|
||||
|
||||
class _GuiAlert(QMessageBox):
|
||||
|
||||
INFO = 0
|
||||
WARN = 1
|
||||
ERROR = 2
|
||||
ASK = 3
|
||||
|
||||
def __init__(self, parent: QWidget, theme: GuiTheme) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self._theme = theme
|
||||
self._message = ""
|
||||
return
|
||||
|
||||
@property
|
||||
def logMessage(self) -> str:
|
||||
return self._message
|
||||
|
||||
def setMessage(self, text: str, info: str, details: str) -> None:
|
||||
"""Set the alert box message."""
|
||||
self._message = " ".join(filter(None, [text, info, details]))
|
||||
self.setText(text)
|
||||
self.setInformativeText(info)
|
||||
self.setDetailedText(details)
|
||||
return
|
||||
|
||||
def setException(self, exception: Exception) -> None:
|
||||
"""Add exception details."""
|
||||
info = self.informativeText()
|
||||
text = f"<b>{type(exception).__name__}</b>: {str(exception)}"
|
||||
self.setInformativeText(f"{info}<br>{text}" if info else text)
|
||||
return
|
||||
|
||||
def setAlertType(self, level: int, isYesNo: bool) -> None:
|
||||
"""Set the type of alert and whether the dialog should have
|
||||
Yes/No buttons or just an Ok button.
|
||||
"""
|
||||
if isYesNo:
|
||||
self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
||||
else:
|
||||
self.setStandardButtons(QMessageBox.Ok)
|
||||
pSz = 2*self._theme.baseIconSize
|
||||
if level == self.INFO:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz)))
|
||||
self.setWindowTitle(self.tr("Information"))
|
||||
elif level == self.WARN:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_warn", (pSz, pSz)))
|
||||
self.setWindowTitle(self.tr("Warning"))
|
||||
elif level == self.ERROR:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_error", (pSz, pSz)))
|
||||
self.setWindowTitle(self.tr("Error"))
|
||||
elif level == self.ASK:
|
||||
self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz)))
|
||||
self.setWindowTitle(self.tr("Question"))
|
||||
return
|
||||
|
||||
# END Class _GuiAlert
|
||||
|
||||
Reference in New Issue
Block a user