diff --git a/novelwriter/assets/images/wizard-back.jpg b/novelwriter/assets/images/wizard-back.jpg deleted file mode 100644 index 8db614c8..00000000 Binary files a/novelwriter/assets/images/wizard-back.jpg and /dev/null differ diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index 584d4cfd..ed0bb822 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -326,11 +326,9 @@ class ProjectBuilder: ## def buildProject(self, data: dict) -> bool: - """Build a project from a data dictionary of specifications - provided by the wizard. - """ + """Build or copy a project from a data dictionary.""" if not isinstance(data, dict): - logger.error("Invalid call to newProject function") + logger.error("Invalid call to buildProject function") return False path = data.get("path", None) @@ -353,9 +351,7 @@ class ProjectBuilder: ## def _buildAndPopulate(self, path: Path, data: dict) -> bool: - """Build a project from a data dictionary of specifications - provided by the wizard. - """ + """Build a blank project from a data dictionary.""" project = NWProject() status = project.storage.createNewProject(path) if status == NWStorageCreate.NOT_EMPTY: diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 6c8b4fca..66ba4b93 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -487,7 +487,6 @@ class GuiIcons: } IMAGE_MAP: dict[str, tuple[str, str]] = { - "wiz-back": ("wizard-back.jpg", "wizard-back.jpg"), "welcome": ("welcome.jpg", "welcome.jpg"), "nw-text": ("novelwriter-text-light.svg", "novelwriter-text-dark.svg"), } diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 2340c799..75505273 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -58,16 +58,13 @@ from novelwriter.dialogs.projdetails import GuiProjectDetails from novelwriter.dialogs.projsettings import GuiProjectSettings from novelwriter.tools.welcome import GuiWelcome from novelwriter.tools.manuscript import GuiManuscript -from novelwriter.tools.projwizard import GuiProjectWizard from novelwriter.tools.dictionaries import GuiDictionaries from novelwriter.tools.writingstats import GuiWritingStats -from novelwriter.core.coretools import ProjectBuilder from novelwriter.enum import ( - nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwItemClass, nwWidget, nwView + nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView ) from novelwriter.common import hexToInt -from novelwriter.constants import nwFiles logger = logging.getLogger(__name__) @@ -373,42 +370,6 @@ class GuiMain(QMainWindow): # Project Actions ## - def newProject(self, projData: dict | None = None) -> bool: - """Create a new project via the new project wizard.""" - if SHARED.hasProject: - if not self.closeProject(): - SHARED.error(self.tr( - "Cannot create a new project when another project is open." - )) - return False - - if projData is None: - projData = self.showNewProjectDialog() - - if projData is None: - return False - - projPath = projData.get("projPath", None) - if projPath is None or projData is None: - logger.error("No projData or projPath set") - return False - - if (Path(projPath) / nwFiles.PROJ_FILE).is_file(): - SHARED.error(self.tr( - "A project already exists in that location. " - "Please choose another folder." - )) - return False - - logger.info("Creating new project") - nwProject = ProjectBuilder() - if nwProject.buildProject(projData): - self.openProject(projPath) - else: - return False - - return True - def closeProject(self, isYes: bool = False) -> bool: """Close the project if one is open. isYes is passed on from the close application event so the user doesn't get prompted twice @@ -850,8 +811,6 @@ class GuiMain(QMainWindow): if dlgProj.result() == QDialog.Accepted: if dlgProj.openState == GuiProjectLoad.OPEN_STATE: self.openProject(dlgProj.openPath) - elif dlgProj.openState == GuiProjectLoad.NEW_STATE: - self.newProject() return @pyqtSlot() @@ -862,16 +821,6 @@ class GuiMain(QMainWindow): dialog.exec_() return - def showNewProjectDialog(self) -> dict | None: - """Open the wizard and assemble a project options dict.""" - newProj = GuiProjectWizard(self) - newProj.exec_() - - if newProj.result() == QDialog.Accepted: - return self._assembleProjectWizardData(newProj) - - return None - @pyqtSlot() def showPreferencesDialog(self) -> None: """Open the preferences dialog.""" @@ -1472,44 +1421,6 @@ class GuiMain(QMainWindow): self.setWindowTitle(winTitle) return - def _assembleProjectWizardData(self, newProj: GuiProjectWizard) -> dict: - """Extract the user choices from the New Project Wizard and - store them in a dictionary. - """ - projData = { - "projName": newProj.field("projName"), - "projTitle": newProj.field("projTitle"), - "projAuthor": newProj.field("projAuthor"), - "projPath": newProj.field("projPath"), - "popSample": newProj.field("popSample"), - "popMinimal": newProj.field("popMinimal"), - "popCustom": newProj.field("popCustom"), - "addRoots": [], - "addNotes": False, - "numChapters": 0, - "numScenes": 0, - } - if newProj.field("popCustom"): - addRoots = [] - if newProj.field("addPlot"): - addRoots.append(nwItemClass.PLOT) - if newProj.field("addChar"): - addRoots.append(nwItemClass.CHARACTER) - if newProj.field("addWorld"): - addRoots.append(nwItemClass.WORLD) - projData["addRoots"] = addRoots - projData["addNotes"] = newProj.field("addNotes") - projData["numChapters"] = newProj.field("numChapters") - projData["numScenes"] = newProj.field("numScenes") - - try: - langIdx = newProj.field("projLang") - projData["projLang"] = CONFIG.listLanguages(CONFIG.LANG_PROJ)[langIdx][0] - except Exception: - projData["projLang"] = "en_GB" - - return projData - def _getTagSource(self, tag: str) -> tuple[str | None, str | None]: """Handle the index lookup of a tag and display an alert if the tag cannot be found. diff --git a/novelwriter/tools/projwizard.py b/novelwriter/tools/projwizard.py deleted file mode 100644 index b819435f..00000000 --- a/novelwriter/tools/projwizard.py +++ /dev/null @@ -1,478 +0,0 @@ -""" -novelWriter – GUI New Project Wizard -==================================== - -File History: -Created: 2020-07-11 [0.10.1] GuiProjectWizard - -This file is a part of novelWriter -Copyright 2018–2023, Veronica Berglyd Olsen - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -""" -from __future__ import annotations - -import os -import logging - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import ( - QComboBox, QFileDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel, - QLineEdit, QPushButton, QRadioButton, QSpinBox, QVBoxLayout, QWizard, - QWizardPage -) - -from novelwriter import CONFIG, SHARED -from novelwriter.common import makeFileNameSafe -from novelwriter.extensions.switch import NSwitch - -logger = logging.getLogger(__name__) - -PAGE_INTRO = 0 -PAGE_STORE = 1 -PAGE_POP = 2 -PAGE_CUSTOM = 3 -PAGE_FINAL = 4 - - -class GuiProjectWizard(QWizard): - - def __init__(self, mainGui): - super().__init__(parent=mainGui) - - logger.debug("Create: GuiProjectWizard") - self.setObjectName("GuiProjectWizard") - - self.mainGui = mainGui - - self.sideImage = SHARED.theme.loadDecoration( - "wiz-back", None, CONFIG.pxInt(370) - ) - self.setWizardStyle(QWizard.ModernStyle) - self.setPixmap(QWizard.WatermarkPixmap, self.sideImage) - - self.introPage = ProjWizardIntroPage(self) - self.storagePage = ProjWizardFolderPage(self) - self.popPage = ProjWizardPopulatePage(self) - self.customPage = ProjWizardCustomPage(self) - self.finalPage = ProjWizardFinalPage(self) - - self.setPage(PAGE_INTRO, self.introPage) - self.setPage(PAGE_STORE, self.storagePage) - self.setPage(PAGE_POP, self.popPage) - self.setPage(PAGE_CUSTOM, self.customPage) - self.setPage(PAGE_FINAL, self.finalPage) - - self.setOption(QWizard.NoBackButtonOnStartPage, True) - - logger.debug("Ready: GuiProjectWizard") - - return - - def __del__(self) -> None: # pragma: no cover - logger.debug("Delete: GuiProjectWizard") - return - -# END Class GuiProjectWizard - - -class ProjWizardIntroPage(QWizardPage): - - def __init__(self, theWizard): - super().__init__() - - self.setTitle(self.tr("Create New Project")) - self.theText = QLabel(self.tr( - "Provide at least a project name. The project name should not " - "be changed beyond this point as it is used for generating file " - "names for for instance backups. The other fields are optional " - "and can be changed at any time in Project Settings." - )) - self.theText.setWordWrap(True) - - self.imgCredit = QLabel(self.tr("Side image by {0}, {1}").format( - "Peter Mitterhofer", "CC BY-SA 4.0" - )) - lblFont = self.imgCredit.font() - lblFont.setPointSizeF(0.6*SHARED.theme.fontPointSize) - self.imgCredit.setFont(lblFont) - - xW = CONFIG.pxInt(300) - vS = CONFIG.pxInt(12) - fS = CONFIG.pxInt(4) - - # The Page Form - self.projName = QLineEdit() - self.projName.setMaxLength(200) - self.projName.setFixedWidth(xW) - self.projName.setPlaceholderText(self.tr("Required")) - - self.projTitle = QLineEdit() - self.projTitle.setMaxLength(200) - self.projTitle.setFixedWidth(xW) - self.projTitle.setPlaceholderText(self.tr("Optional")) - - self.projAuthor = QLineEdit() - self.projAuthor.setMaxLength(200) - self.projAuthor.setFixedWidth(xW) - self.projAuthor.setPlaceholderText(self.tr("Optional")) - - self.projLang = QComboBox(self) - self.projLang.setMaximumWidth(xW) - for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): - self.projLang.addItem(language, tag) - - langIdx = self.projLang.findData(CONFIG.guiLocale) - if langIdx == -1: - langIdx = self.projLang.findData("en_GB") - if langIdx != -1: - self.projLang.setCurrentIndex(langIdx) - - self.mainForm = QFormLayout() - self.mainForm.addRow(self.tr("Project Name"), self.projName) - self.mainForm.addRow(self.tr("Novel Title"), self.projTitle) - self.mainForm.addRow(self.tr("Author(s)"), self.projAuthor) - self.mainForm.addRow(self.tr("Language"), self.projLang) - self.mainForm.setVerticalSpacing(fS) - - self.registerField("projName*", self.projName) - self.registerField("projTitle", self.projTitle) - self.registerField("projAuthor", self.projAuthor) - self.registerField("projLang", self.projLang) - - # Assemble - self.outerBox = QVBoxLayout() - self.outerBox.setSpacing(vS) - self.outerBox.addWidget(self.theText) - self.outerBox.addLayout(self.mainForm) - self.outerBox.addStretch(1) - self.outerBox.addWidget(self.imgCredit) - self.setLayout(self.outerBox) - - return - -# END Class ProjWizardIntroPage - - -class ProjWizardFolderPage(QWizardPage): - - def __init__(self, theWizard): - super().__init__() - - self.setTitle(self.tr("Select Project Folder")) - self.theText = QLabel(self.tr( - "Select a location to store the project. A new project folder " - "will be created in the selected location." - )) - self.theText.setWordWrap(True) - - xW = CONFIG.pxInt(300) - vS = CONFIG.pxInt(12) - fS = CONFIG.pxInt(8) - - self.projPath = QLineEdit("") - self.projPath.setFixedWidth(xW) - self.projPath.setPlaceholderText(self.tr("Required")) - - self.browseButton = QPushButton("...") - self.browseButton.setMaximumWidth(int(2.5*SHARED.theme.getTextWidth("..."))) - self.browseButton.clicked.connect(self._doBrowse) - - self.errLabel = QLabel("") - self.errLabel.setWordWrap(True) - - self.mainForm = QHBoxLayout() - self.mainForm.addWidget(QLabel(self.tr("Project Path")), 0) - self.mainForm.addWidget(self.projPath, 1) - self.mainForm.addWidget(self.browseButton, 0) - self.mainForm.setSpacing(fS) - - self.registerField("projPath*", self.projPath) - - # Assemble - self.outerBox = QVBoxLayout() - self.outerBox.setSpacing(vS) - self.outerBox.addWidget(self.theText) - self.outerBox.addLayout(self.mainForm) - self.outerBox.addWidget(self.errLabel) - self.outerBox.addStretch(1) - self.setLayout(self.outerBox) - - return - - def isComplete(self): - """Check that the selected path isn't already being used. - """ - self.errLabel.setText("") - if not super().isComplete(): - return False - - setPath = os.path.abspath(os.path.expanduser(self.projPath.text())) - parPath = os.path.dirname(setPath) - logger.debug("Path is: %s", setPath) - if parPath and not os.path.isdir(parPath): - self.errLabel.setText(self.tr( - "Error: A project folder cannot be created using this path." - )) - return False - - if os.path.exists(setPath): - self.errLabel.setText(self.tr( - "Error: The selected path already exists." - )) - return False - - return True - - ## - # Slots - ## - - def _doBrowse(self): - """Select a project folder. - """ - lastPath = CONFIG.lastPath() - projDir = QFileDialog.getExistingDirectory( - self, self.tr("Select Project Folder"), str(lastPath), options=QFileDialog.ShowDirsOnly - ) - if projDir: - projName = self.field("projName") - if projName is not None: - fullDir = os.path.join(os.path.abspath(projDir), makeFileNameSafe(projName)) - self.projPath.setText(fullDir) - else: - self.projPath.setText("") - - return - -# END Class ProjWizardFolderPage - - -class ProjWizardPopulatePage(QWizardPage): - - def __init__(self, theWizard): - super().__init__() - - self.setTitle(self.tr("Populate Project")) - self.theText = QLabel(self.tr( - "Choose how to pre-fill the project. Either with a minimal set of " - "starter items, an example project explaining and showing many of " - "the features, or show further custom options on the next page." - )) - self.theText.setWordWrap(True) - - vS = CONFIG.pxInt(12) - fS = CONFIG.pxInt(4) - - self.popMinimal = QRadioButton(self.tr("Fill the project with a minimal set of items")) - self.popSample = QRadioButton(self.tr("Fill the project with example files")) - self.popCustom = QRadioButton(self.tr("Show detailed options for filling the project")) - self.popMinimal.setChecked(True) - - self.popBox = QVBoxLayout() - self.popBox.setSpacing(fS) - self.popBox.addWidget(self.popMinimal) - self.popBox.addWidget(self.popSample) - self.popBox.addWidget(self.popCustom) - - self.registerField("popMinimal", self.popMinimal) - self.registerField("popSample", self.popSample) - self.registerField("popCustom", self.popCustom) - - # Assemble - self.outerBox = QVBoxLayout() - self.outerBox.setSpacing(vS) - self.outerBox.addWidget(self.theText) - self.outerBox.addLayout(self.popBox) - self.outerBox.addStretch(1) - self.setLayout(self.outerBox) - - return - - def nextId(self): - """Overload the nextID function to skip further pages if custom - is not selected. - """ - if self.popCustom.isChecked(): - return PAGE_CUSTOM - else: - return PAGE_FINAL - -# END Class ProjWizardPopulatePage - - -class ProjWizardCustomPage(QWizardPage): - - def __init__(self, theWizard): - super().__init__() - - self.setTitle(self.tr("Custom Project Options")) - self.theText = QLabel(self.tr( - "Select which additional elements to populate the project with. " - "You can skip making chapters and add only scenes by setting the " - "number of chapters to 0." - )) - self.theText.setWordWrap(True) - - cM = CONFIG.pxInt(12) - mH = CONFIG.pxInt(26) - fS = CONFIG.pxInt(4) - - # Root Folders - self.addPlot = NSwitch() - self.addPlot.setChecked(True) - self.addPlot.clicked.connect(self._syncSwitches) - - self.addChar = NSwitch() - self.addChar.setChecked(True) - self.addChar.clicked.connect(self._syncSwitches) - - self.addWorld = NSwitch() - self.addWorld.setChecked(False) - self.addWorld.clicked.connect(self._syncSwitches) - - self.addNotes = NSwitch() - self.addNotes.setChecked(False) - - # Generate Content - self.numChapters = QSpinBox() - self.numChapters.setRange(0, 100) - self.numChapters.setValue(5) - - self.numScenes = QSpinBox() - self.numScenes.setRange(0, 200) - self.numScenes.setValue(5) - - # Grid Form - self.addBox = QGridLayout() - self.addBox.addWidget(QLabel(self.tr("Add a folder for plot notes")), 0, 0) - self.addBox.addWidget(QLabel(self.tr("Add a folder for character notes")), 1, 0) - self.addBox.addWidget(QLabel(self.tr("Add a folder for location notes")), 2, 0) - self.addBox.addWidget(QLabel(self.tr("Add example notes to the above")), 3, 0) - self.addBox.addWidget(QLabel(self.tr("Add chapters to the novel folder")), 4, 0) - self.addBox.addWidget(QLabel(self.tr("Add scenes to each chapter")), 5, 0) - self.addBox.addWidget(self.addPlot, 0, 1, 1, 1, Qt.AlignRight) - self.addBox.addWidget(self.addChar, 1, 1, 1, 1, Qt.AlignRight) - self.addBox.addWidget(self.addWorld, 2, 1, 1, 1, Qt.AlignRight) - self.addBox.addWidget(self.addNotes, 3, 1, 1, 1, Qt.AlignRight) - self.addBox.addWidget(self.numChapters, 4, 1, 1, 1, Qt.AlignRight) - self.addBox.addWidget(self.numScenes, 5, 1, 1, 1, Qt.AlignRight) - self.addBox.setVerticalSpacing(fS) - self.addBox.setHorizontalSpacing(cM) - self.addBox.setContentsMargins(cM, 0, cM, 0) - self.addBox.setColumnStretch(2, 1) - for i in range(6): - self.addBox.setRowMinimumHeight(i, mH) - - # Wizard Fields - self.registerField("addPlot", self.addPlot) - self.registerField("addChar", self.addChar) - self.registerField("addWorld", self.addWorld) - self.registerField("addNotes", self.addNotes) - self.registerField("numChapters", self.numChapters) - self.registerField("numScenes", self.numScenes) - - # Assemble - self.outerBox = QVBoxLayout() - self.outerBox.setSpacing(cM) - self.outerBox.addWidget(self.theText) - self.outerBox.addLayout(self.addBox) - self.outerBox.addStretch(1) - self.setLayout(self.outerBox) - - return - - ## - # Internal Functions - ## - - def _syncSwitches(self): - """Check if the add notes option should also be switched off. - """ - addPlot = self.addPlot.isChecked() - addChar = self.addChar.isChecked() - addWorld = self.addWorld.isChecked() - if not (addPlot or addChar or addWorld): - self.addNotes.setChecked(False) - return - -# END Class ProjWizardCustomPage - - -class ProjWizardFinalPage(QWizardPage): - - def __init__(self, theWizard): - super().__init__() - - self.setTitle(self.tr("Summary")) - self.theText = QLabel("") - self.theText.setWordWrap(True) - - # Assemble - self.outerBox = QVBoxLayout() - self.outerBox.setSpacing(CONFIG.pxInt(12)) - self.outerBox.addWidget(self.theText) - self.outerBox.addStretch(1) - self.setLayout(self.outerBox) - - return - - def initializePage(self): - """Update the summary information on the final page. - """ - super().initializePage() - - sumList = [] - sumList.append(self.tr("Project Name: {0}").format(self.field("projName"))) - sumList.append(self.tr("Project Path: {0}").format(self.field("projPath"))) - - if self.field("popMinimal"): - sumList.append(self.tr("Fill the project with a minimal set of items")) - elif self.field("popSample"): - sumList.append(self.tr("Fill the project with example files")) - elif self.field("popCustom"): - if self.field("addPlot"): - sumList.append(self.tr("Add a folder for plot notes")) - if self.field("addChar"): - sumList.append(self.tr("Add a folder for character notes")) - if self.field("addWorld"): - sumList.append(self.tr("Add a folder for location notes")) - if self.field("addNotes"): - sumList.append(self.tr("Add example notes to the above")) - if self.field("numChapters") > 0: - sumList.append(self.tr("Add {0} chapters to the novel folder").format( - self.field("numChapters") - )) - if self.field("numScenes") > 0: - sumList.append(self.tr("Add {0} scenes to each chapter").format( - self.field("numScenes") - )) - else: - if self.field("numScenes") > 0: - sumList.append(self.tr("Add {0} scenes").format( - self.field("numScenes") - )) - - self.theText.setText( - "

%s

 • %s

%s

" % ( - self.tr("You have selected the following:"), - "
 • ".join(sumList), - self.tr("Press '{0}' to create the new project.").format( - self.tr("Done") if CONFIG.osDarwin else self.tr("Finish") - ) - ) - ) - return - -# END Class ProjWizardFinalPage diff --git a/tests/test_core/test_core_storage.py b/tests/test_core/test_core_storage.py index 1407d898..e2b877db 100644 --- a/tests/test_core/test_core_storage.py +++ b/tests/test_core/test_core_storage.py @@ -297,48 +297,6 @@ def testCoreStorage_ZipIt(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd): # END Test testCoreStorage_ZipIt -# @pytest.mark.core -# def testCoreStorage_PrepareStorage(monkeypatch, fncPath): -# """Test the project path preparation functions.""" -# storage = NWStorage(MockProject()) # type: ignore -# assert storage.isOpen() is False - -# # No path set -# assert storage._prepareStorage() is False - -# # Set path to home -# storage._runtimePath = fncPath -# with monkeypatch.context() as mp: -# mp.setattr("pathlib.Path.home", lambda: fncPath) -# assert storage._prepareStorage() is False - -# # Fail on mkdir -# storage._runtimePath = fncPath -# with monkeypatch.context() as mp: -# mp.setattr("pathlib.Path.mkdir", causeOSError) -# assert storage._prepareStorage() is False - -# # Set up the folder -# storage._runtimePath = fncPath -# assert storage._prepareStorage(checkLegacy=False) is True -# assert (fncPath / "content").exists() -# assert (fncPath / "meta").exists() -# assert not (fncPath / "cache").exists() # Removed in 2.1b1 - -# # Add a legacy folder -# storage._runtimePath = fncPath -# dataDir = fncPath / "data_0" -# dataDir.mkdir() -# assert storage._prepareStorage(checkLegacy=True) is True -# assert not dataDir.exists() - -# # We cannot add a new project here -# storage._runtimePath = fncPath -# assert storage._prepareStorage(checkLegacy=False, newProject=True) is False - -# # END Test testCoreStorage_PrepareStorage - - @pytest.mark.core def testCoreStorage_LegacyDataFolder(monkeypatch, fncPath): """Test project file format 1.0 folder structure conversion.""" diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index a43e86e8..657ce7e8 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -26,16 +26,15 @@ import pytest from shutil import copyfile from tools import ( - C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem, writeFile + C, NWD_IGNORE, cmpFiles, buildTestProject, XML_IGNORE, getGuiItem ) from PyQt5.QtGui import QColor, QPalette from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox, QInputDialog +from PyQt5.QtWidgets import QDialog, QMenu, QInputDialog from novelwriter import CONFIG, SHARED from novelwriter.enum import nwItemType, nwView, nwWidget -from novelwriter.constants import nwFiles from novelwriter.gui.outline import GuiOutlineView from novelwriter.gui.projtree import GuiProjectTree from novelwriter.gui.doceditor import GuiDocEditor @@ -43,7 +42,6 @@ from novelwriter.gui.noveltree import GuiNovelView from novelwriter.dialogs.about import GuiAbout from novelwriter.dialogs.projload import GuiProjectLoad from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.tools.projwizard import GuiProjectWizard KEY_DELAY = 1 @@ -114,42 +112,6 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath): # END Test testGuiMain_Launch -@pytest.mark.gui -def testGuiMain_NewProject(monkeypatch, nwGUI, projPath): - """Test creating a new project.""" - # Open wizard, but return no data - with monkeypatch.context() as mp: - mp.setattr(GuiProjectWizard, "exec_", lambda *a: None) - assert nwGUI.newProject(projData=None) is False - - # Close project - with monkeypatch.context() as mp: - SHARED.project._valid = True - mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No) - assert nwGUI.newProject(projData={"projPath": projPath}) is False - SHARED.project._valid = False - - # No project path - assert nwGUI.newProject(projData={}) is False - - # Project file already exists - projFile = projPath / nwFiles.PROJ_FILE - writeFile(projFile, "Stuff") - assert nwGUI.newProject(projData={"projPath": projPath}) is False - projFile.unlink() - - # An unreachable path should also fail - stuffPath = projPath / "stuff" / "stuff" / "stuff" - assert nwGUI.newProject(projData={"projPath": stuffPath}) is False - - # This one should work just fine - assert nwGUI.newProject(projData={"projPath": projPath}) is True - assert (projPath / nwFiles.PROJ_FILE).is_file() - assert (projPath / "content").is_dir() - -# END Test testGuiMain_NewProject - - @pytest.mark.gui def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd): """Test handling of project tree items based on GUI focus states.""" diff --git a/tests/test_tools/test_tools_projwizard.py b/tests/test_tools/test_tools_projwizard.py deleted file mode 100644 index 65a80687..00000000 --- a/tests/test_tools/test_tools_projwizard.py +++ /dev/null @@ -1,249 +0,0 @@ -""" -novelWriter – New Project Wizard Class Tester -============================================= - -This file is a part of novelWriter -Copyright 2018–2023, Veronica Berglyd Olsen - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -""" -from __future__ import annotations - -import sys -import pytest - -from tools import buildTestProject, getGuiItem - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QFileDialog, QWizard, QDialog - -from novelwriter.enum import nwItemClass -from novelwriter.tools.projwizard import ( - GuiProjectWizard, ProjWizardIntroPage, ProjWizardFolderPage, - ProjWizardPopulatePage, ProjWizardCustomPage, ProjWizardFinalPage -) - - -@pytest.mark.gui -@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin") -def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, projPath): - """Test the launch of the project wizard. - Disabled for macOS because the test segfaults on QWizard.show() - """ - # Test New Project Function - # ======================== - - # New with a project open should cause an error - buildTestProject(nwGUI, projPath) - with monkeypatch.context() as mp: - mp.setattr(nwGUI, "closeProject", lambda *a: False) - assert nwGUI.newProject() is False - - # Close project, but call with invalid path - assert nwGUI.closeProject() - with monkeypatch.context() as mp: - mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: None) - assert nwGUI.newProject() is False - - # Now, with an empty dictionary - mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {}) - assert nwGUI.newProject() is False - - # Now, with a non-empty folder - mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": projPath}) - assert nwGUI.newProject() is False - - # Test the Wizard Launching - # ========================= - - monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None) - - result = nwGUI.showNewProjectDialog() - qtbot.waitUntil(lambda: getGuiItem("GuiProjectWizard") is not None, timeout=1000) - - nwWiz = getGuiItem("GuiProjectWizard") - assert isinstance(nwWiz, GuiProjectWizard) - nwWiz.show() - - qtbot.mouseClick(nwWiz.button(QWizard.CancelButton), Qt.LeftButton) - assert result is None - - with monkeypatch.context() as mp: - mp.setattr(GuiProjectWizard, "result", lambda *a: QDialog.Accepted) - result = nwGUI.showNewProjectDialog() - nwWiz.button(QWizard.CancelButton).click() - assert isinstance(result, dict) - - nwWiz.reject() - nwWiz.close() - - # qtbot.stop() - -# END Test testToolProjectWizard_Handling - - -@pytest.mark.gui -@pytest.mark.parametrize("prjType", ["minimal", "custom1", "custom2", "sample"]) -@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin") -def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncPath, prjType): - """Test the new project wizard with a set of selection scenarios. - """ - monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None) - - nwWiz = GuiProjectWizard(nwGUI) - nwWiz.show() - qtbot.addWidget(nwWiz) - - # Intro Page - # ========== - - introPage = nwWiz.currentPage() - assert isinstance(introPage, ProjWizardIntroPage) - assert not nwWiz.button(QWizard.NextButton).isEnabled() - - introPage.projName.setText("Test Wizard") - introPage.projTitle.setText("My Novel") - introPage.projAuthor.setText("Jane Doe") - - # Setting projName should activate the button - assert nwWiz.button(QWizard.NextButton).isEnabled() - - qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) - - # Folder Page - # =========== - - storagePage = nwWiz.currentPage() - assert isinstance(storagePage, ProjWizardFolderPage) - assert not nwWiz.button(QWizard.NextButton).isEnabled() - assert storagePage.errLabel.text() == "" - - # Set an invalid path - storagePage.projPath.setText(str(fncPath / "not" / "a" / "path")) - assert not nwWiz.button(QWizard.NextButton).isEnabled() - assert storagePage.errLabel.text().startswith("Error") - - # Set an existing path - storagePage.projPath.setText(str(fncPath)) - assert not nwWiz.button(QWizard.NextButton).isEnabled() - assert storagePage.errLabel.text().startswith("Error") - - # Return a non-result from browse - with monkeypatch.context() as mp: - mp.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: "") - qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100) - assert storagePage.errLabel.text() == "" - - # Let the browse feature handle it - projPath = fncPath / "Test Wizard" - with monkeypatch.context() as mp: - mp.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: str(fncPath)) - qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100) - - assert storagePage.projPath.text() == str(projPath) - assert storagePage.errLabel.text() == "" - - # Setting projPath should activate the button - assert nwWiz.button(QWizard.NextButton).isEnabled() - - qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) - - # Populate Page - # ============= - - popPage = nwWiz.currentPage() - assert isinstance(popPage, ProjWizardPopulatePage) - assert nwWiz.button(QWizard.NextButton).isEnabled() - - if prjType.startswith("minimal"): - popPage.popMinimal.setChecked(True) - elif prjType.startswith("custom"): - popPage.popCustom.setChecked(True) - elif prjType.startswith("sample"): - popPage.popSample.setChecked(True) - - qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) - - # Custom Page - # =========== - if prjType.startswith("custom"): - - customPage = nwWiz.currentPage() - assert isinstance(customPage, ProjWizardCustomPage) - assert nwWiz.button(QWizard.NextButton).isEnabled() - - # Make sure the fourth option is also turned off - customPage.addPlot.setChecked(False) - customPage.addChar.setChecked(False) - customPage.addWorld.setChecked(False) - customPage._syncSwitches() - assert not customPage.addNotes.isChecked() - - # Switch everything back on again - customPage.addPlot.setChecked(True) - customPage.addChar.setChecked(True) - customPage.addWorld.setChecked(True) - customPage.addNotes.setChecked(True) - - if prjType == "custom2": - customPage.numChapters.setValue(0) - customPage.numScenes.setValue(10) - - qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) - - # Final Page - # ========== - - finalPage = nwWiz.currentPage() - assert isinstance(finalPage, ProjWizardFinalPage) - assert nwWiz.button(QWizard.FinishButton).isEnabled() # But we don't click it - - # Check Data - # ========== - - projData = nwGUI._assembleProjectWizardData(nwWiz) - assert projData["projName"] == "Test Wizard" - assert projData["projTitle"] == "My Novel" - assert projData["projAuthor"] == "Jane Doe" - assert projData["projPath"] == str(projPath) - assert projData["popMinimal"] == prjType.startswith("minimal") - assert projData["popCustom"] == prjType.startswith("custom") - assert projData["popSample"] == prjType.startswith("sample") - if prjType.startswith("custom"): - assert projData["addRoots"] == [ - nwItemClass.PLOT, - nwItemClass.CHARACTER, - nwItemClass.WORLD, - ] - if prjType == "custom1": - assert projData["numChapters"] == 5 - assert projData["numScenes"] == 5 - assert projData["addNotes"] is True - else: - assert projData["numChapters"] == 0 - assert projData["numScenes"] == 10 - assert projData["addNotes"] is True - else: - assert projData["addRoots"] == [] - assert projData["numChapters"] == 0 - assert projData["numScenes"] == 0 - assert projData["addNotes"] is False - - # Cleanup - nwWiz.reject() - nwWiz.close() - - # qtbot.stop() - -# END Test testToolProjectWizard_Run