From 8cc75fcbd42ec51bbc907cdfdc2c612ceabe520e Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 11 Jul 2020 13:49:48 +0200 Subject: [PATCH 01/22] Added project wizard class file --- nw/gui/__init__.py | 2 ++ nw/gui/build.py | 2 -- nw/gui/projwizard.py | 49 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 nw/gui/projwizard.py diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 6359a342..cc2933e7 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -15,6 +15,7 @@ from nw.gui.preferences import GuiPreferences from nw.gui.projload import GuiProjectLoad from nw.gui.projsettings import GuiProjectSettings from nw.gui.projtree import GuiProjectTree +from nw.gui.projwizard import GuiProjectWizard from nw.gui.writingstats import GuiWritingStats from nw.gui.statusbar import GuiMainStatus from nw.gui.theme import GuiIcons, GuiTheme @@ -36,6 +37,7 @@ __all__ = [ "GuiProjectLoad", "GuiProjectSettings", "GuiProjectTree", + "GuiProjectWizard", "GuiWritingStats", "GuiMainStatus", "GuiIcons", diff --git a/nw/gui/build.py b/nw/gui/build.py index 34a01f05..a9fa5571 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -401,8 +401,6 @@ class GuiBuildNovel(QDialog): self.setLayout(self.outerBox) self.buildNovel.setFocus() - self.show() - logger.debug("GuiBuildNovel initialisation complete") # Load from Cache diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py new file mode 100644 index 00000000..fe24d9bf --- /dev/null +++ b/nw/gui/projwizard.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +"""novelWriter GUI New Project Wizard + + novelWriter – GUI New project Wizard +====================================== + Class holding the new project wizard dialog + + File History: + Created: 2020-07-11 [0.10.1] + + This file is a part of novelWriter + Copyright 2020, 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 . +""" + +import logging +import nw + +from PyQt5.QtWidgets import QWizard + +logger = logging.getLogger(__name__) + +class GuiProjectWizard(QWizard): + + def __init__(self, theParent): + QWizard.__init__(self, theParent) + + logger.debug("Initialising GuiProjectWizard ...") + + self.mainConf = nw.CONFIG + self.theParent = theParent + + logger.debug("GuiProjectWizard initialisation complete") + + return + +# END Class GuiProjectWizard From 1277e5025d276020706c4e9c9838fc3f21559c5b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 11 Jul 2020 15:07:11 +0200 Subject: [PATCH 02/22] Added first page to wizard --- nw/gui/projwizard.py | 77 +++++++++++++++++++++++++++++++++++++++++++- nw/guimain.py | 20 ++++++------ 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index fe24d9bf..f6c5c4c4 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -28,7 +28,11 @@ import logging import nw -from PyQt5.QtWidgets import QWizard +from PyQt5.QtGui import QPixmap, QColor +from PyQt5.QtWidgets import ( + QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit, + QFormLayout +) logger = logging.getLogger(__name__) @@ -41,9 +45,80 @@ class GuiProjectWizard(QWizard): self.mainConf = nw.CONFIG self.theParent = theParent + self.theTheme = theParent.theTheme + + nPx = self.mainConf.pxInt(96) + wmW = self.mainConf.pxInt(100) + wmH = self.mainConf.pxInt(340) + + self.sideImage = QPixmap(wmW, wmH) + self.sideImage.fill(self.palette().dark().color()) + + # self.setWizardStyle(QWizard.ModernStyle) + self.setPixmap(QWizard.WatermarkPixmap, self.sideImage) + + self.introPage = ProjWizardIntroPage() + self.addPage(self.introPage) + + self.setOption(QWizard.NoBackButtonOnStartPage, True) + # self.setOption(QWizard.ExtendedWatermarkPixmap, True) logger.debug("GuiProjectWizard initialisation complete") return # END Class GuiProjectWizard + +class ProjWizardIntroPage(QWizardPage): + + def __init__(self): + QWizardPage.__init__(self) + + self.mainConf = nw.CONFIG + + self.setTitle("Create New Project") + self.theText = QLabel( + "Provide at least a working title. The working title should not " + "be change beyond this point as it is used by the application 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) + + xW = self.mainConf.pxInt(250) + xH = self.mainConf.pxInt(100) + vS = self.mainConf.pxInt(12) + fS = self.mainConf.pxInt(4) + + self.projName = QLineEdit() + self.projName.setMaxLength(200) + self.projName.setFixedWidth(xW) + self.projName.setPlaceholderText("Required") + + self.projTitle = QLineEdit() + self.projTitle.setMaxLength(200) + self.projTitle.setFixedWidth(xW) + self.projTitle.setPlaceholderText("Optional") + + self.projAuthors = QPlainTextEdit() + self.projAuthors.setFixedHeight(xH) + self.projAuthors.setFixedWidth(xW) + self.projAuthors.setPlaceholderText("Optional. One name per line.") + + self.mainForm = QFormLayout() + self.mainForm.addRow("Working title", self.projName) + self.mainForm.addRow("Novel title", self.projTitle) + self.mainForm.addRow("Author(s)", self.projAuthors) + self.mainForm.setVerticalSpacing(fS) + + # Assemble + self.outerBox = QVBoxLayout() + self.outerBox.setSpacing(vS) + self.outerBox.addWidget(self.theText) + self.outerBox.addLayout(self.mainForm) + self.outerBox.addStretch(1) + self.setLayout(self.outerBox) + + return + +# END Class ProjWizardIntroPage diff --git a/nw/guimain.py b/nw/guimain.py index 668ebf9f..1136142e 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -43,7 +43,7 @@ from nw.gui import ( GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus, GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiTheme, - GuiProjectSettings, GuiProjectTree, GuiWritingStats + GuiProjectSettings, GuiProjectTree, GuiWritingStats, GuiProjectWizard ) from nw.core import NWProject, NWDoc, NWIndex from nw.constants import nwFiles, nwItemType, nwAlert @@ -766,14 +766,16 @@ class GuiMain(QMainWindow): def newProjectDialog(self): """Select where to save new project. """ - dlgOpt = QFileDialog.Options() - dlgOpt |= QFileDialog.ShowDirsOnly - dlgOpt |= QFileDialog.DontUseNativeDialog - projPath = QFileDialog.getExistingDirectory( - self, "Select Location for New novelWriter Project", "", options=dlgOpt - ) - if projPath: - return projPath + newProj = GuiProjectWizard(self) + newProj.exec_() + # dlgOpt = QFileDialog.Options() + # dlgOpt |= QFileDialog.ShowDirsOnly + # dlgOpt |= QFileDialog.DontUseNativeDialog + # projPath = QFileDialog.getExistingDirectory( + # self, "Select Location for New novelWriter Project", "", options=dlgOpt + # ) + # if projPath: + # return projPath return None def editConfigDialog(self): From 8c895319e58397bfd6092c7ac06412110a1d5670 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 11 Jul 2020 15:48:31 +0200 Subject: [PATCH 03/22] Added the wizard project location page --- nw/common.py | 9 ++++ nw/core/project.py | 15 ++---- nw/gui/build.py | 4 +- nw/gui/preferences.py | 1 - nw/gui/projload.py | 3 +- nw/gui/projwizard.py | 107 ++++++++++++++++++++++++++++++++++++++---- 6 files changed, 114 insertions(+), 25 deletions(-) diff --git a/nw/common.py b/nw/common.py index 872365df..e3454931 100644 --- a/nw/common.py +++ b/nw/common.py @@ -227,3 +227,12 @@ def fuzzyTime(secDiff): return "%d years ago" % int(round(secDiff/31557600)) return "beyond time and space" + +def makeFileNameSafe(theText): + """Returns a filename safe version of the text. + """ + cleanName = "" + for c in theText.strip(): + if c.isalpha() or c.isdigit() or c == " ": + cleanName += c + return cleanName diff --git a/nw/core/project.py b/nw/core/project.py index 496e8bc4..f35c007a 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -41,7 +41,9 @@ from nw.core.item import NWItem from nw.core.document import NWDoc from nw.core.status import NWStatus from nw.core.options import OptionState -from nw.common import checkString, checkBool, checkInt, formatTimeStamp +from nw.common import ( + checkString, checkBool, checkInt, formatTimeStamp, makeFileNameSafe +) from nw.constants import ( nwFiles, nwItemType, nwItemClass, nwItemLayout, nwAlert ) @@ -667,7 +669,7 @@ class NWProject(): ), nwAlert.ERROR) return False - cleanName = self.getFileSafeProjectName() + cleanName = makeFileNameSafe(self.projName) baseDir = path.abspath(path.join(self.mainConf.backupPath, cleanName)) if not path.isdir(baseDir): try: @@ -891,15 +893,6 @@ class NWProject(): # Getters ## - def getFileSafeProjectName(self): - """Returns a filename safe version of the project name. - """ - cleanName = "" - for c in self.projName.strip(): - if c.isalpha() or c.isdigit() or c == " ": - cleanName += c - return cleanName - def getSessionWordCount(self): """Returns the number of words added or removed this session. """ diff --git a/nw/gui/build.py b/nw/gui/build.py index a9fa5571..cee6c9fa 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -44,7 +44,7 @@ from PyQt5.QtWidgets import ( QFileDialog, QFontDialog, QSpinBox ) -from nw.common import fuzzyTime +from nw.common import fuzzyTime, makeFileNameSafe from nw.gui.custom import QSwitch from nw.core import ToHtml from nw.constants import ( @@ -623,7 +623,7 @@ class GuiBuildNovel(QDialog): # Generate the file name if fileExt: - cleanName = self.theProject.getFileSafeProjectName() + cleanName = makeFileNameSafe(self.theProject.projName) fileName = "%s.%s" % (cleanName, fileExt) saveDir = self.mainConf.lastPath savePath = path.join(saveDir, fileName) diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py index 0882b089..f0242edb 100644 --- a/nw/gui/preferences.py +++ b/nw/gui/preferences.py @@ -331,7 +331,6 @@ class GuiConfigEditGeneralTab(QWidget): def _backupFolder(self): """Open a dialog to select the backup folder. """ - currDir = self.backupPath if not path.isdir(currDir): currDir = "" diff --git a/nw/gui/projload.py b/nw/gui/projload.py index 738a493c..30332690 100644 --- a/nw/gui/projload.py +++ b/nw/gui/projload.py @@ -171,8 +171,7 @@ class GuiProjectLoad(QDialog): return def _doBrowse(self): - """Close the dialog window with no selected path, triggering the - project browser dialog. + """Browse for a folder path. """ logger.verbose("GuiProjectLoad browse button clicked") if self.mainConf.showGUI: diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index f6c5c4c4..d606ed23 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -28,12 +28,17 @@ import logging import nw +from os import path + from PyQt5.QtGui import QPixmap, QColor from PyQt5.QtWidgets import ( QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit, - QFormLayout + QPushButton, QFileDialog, QHBoxLayout ) +from nw.common import makeFileNameSafe +from nw.gui.custom import QSwitch, QConfigLayout + logger = logging.getLogger(__name__) class GuiProjectWizard(QWizard): @@ -57,8 +62,11 @@ class GuiProjectWizard(QWizard): # self.setWizardStyle(QWizard.ModernStyle) self.setPixmap(QWizard.WatermarkPixmap, self.sideImage) - self.introPage = ProjWizardIntroPage() + self.introPage = ProjWizardIntroPage(self.theParent) + self.storagePage = ProjWizardFolderPage(self.theParent) + self.addPage(self.introPage) + self.addPage(self.storagePage) self.setOption(QWizard.NoBackButtonOnStartPage, True) # self.setOption(QWizard.ExtendedWatermarkPixmap, True) @@ -71,10 +79,12 @@ class GuiProjectWizard(QWizard): class ProjWizardIntroPage(QWizardPage): - def __init__(self): + def __init__(self, theParent): QWizardPage.__init__(self) - self.mainConf = nw.CONFIG + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theTheme = theParent.theTheme self.setTitle("Create New Project") self.theText = QLabel( @@ -85,11 +95,12 @@ class ProjWizardIntroPage(QWizardPage): ) self.theText.setWordWrap(True) - xW = self.mainConf.pxInt(250) + xW = self.mainConf.pxInt(300) xH = self.mainConf.pxInt(100) vS = self.mainConf.pxInt(12) fS = self.mainConf.pxInt(4) + # The Page Form self.projName = QLineEdit() self.projName.setMaxLength(200) self.projName.setFixedWidth(xW) @@ -105,12 +116,16 @@ class ProjWizardIntroPage(QWizardPage): self.projAuthors.setFixedWidth(xW) self.projAuthors.setPlaceholderText("Optional. One name per line.") - self.mainForm = QFormLayout() - self.mainForm.addRow("Working title", self.projName) - self.mainForm.addRow("Novel title", self.projTitle) - self.mainForm.addRow("Author(s)", self.projAuthors) + self.mainForm = QConfigLayout() + self.mainForm.addRow("Working Title", self.projName) + self.mainForm.addRow("Novel Title", self.projTitle) + self.mainForm.addRow("Author(s)", self.projAuthors) self.mainForm.setVerticalSpacing(fS) + self.registerField("projName*", self.projName) + self.registerField("projTitle", self.projTitle) + self.registerField("projAuthors", self.projAuthors) + # Assemble self.outerBox = QVBoxLayout() self.outerBox.setSpacing(vS) @@ -122,3 +137,77 @@ class ProjWizardIntroPage(QWizardPage): return # END Class ProjWizardIntroPage + +class ProjWizardFolderPage(QWizardPage): + + def __init__(self, theParent): + QWizardPage.__init__(self) + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theTheme = theParent.theTheme + + self.setTitle("Select Project Folder") + self.theText = QLabel( + "Select a location to store the project. A new project folder " + "will be created in the selected location." + ) + self.theText.setWordWrap(True) + + xW = self.mainConf.pxInt(300) + # xH = self.mainConf.pxInt(100) + vS = self.mainConf.pxInt(12) + fS = self.mainConf.pxInt(4) + + self.projPath = QLineEdit("") + self.projPath.setFixedWidth(xW) + self.projPath.setPlaceholderText("Required") + + self.browseButton = QPushButton("...") + self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("..."))) + self.browseButton.clicked.connect(self._doBrowse) + + self.mainForm = QConfigLayout() + self.mainForm.addRow("Project Path", self.projPath, theButton=self.browseButton) + self.mainForm.setVerticalSpacing(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.addStretch(1) + self.setLayout(self.outerBox) + + return + + ## + # Slots + ## + + def _doBrowse(self): + """Select a project folder. + """ + lastPath = self.mainConf.lastPath + if not path.isdir(lastPath): + lastPath = "" + + dlgOpt = QFileDialog.Options() + dlgOpt |= QFileDialog.ShowDirsOnly + dlgOpt |= QFileDialog.DontUseNativeDialog + projDir = QFileDialog.getExistingDirectory( + self,"Select Project Folder", lastPath, options=dlgOpt + ) + if projDir: + projName = self.field("projName") + if projName is not None: + fullDir = path.join(path.abspath(projDir), makeFileNameSafe(projName)) + self.projPath.setText(fullDir) + else: + self.projPath.setText("") + + return + +# END Class ProjWizardFolderPage From 5749b1948f3702302422b5b416e3c20cf77a4082 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jul 2020 17:30:40 +0200 Subject: [PATCH 04/22] Changed a bit how novelWriter icon and dialog decoration is loaded --- nw/assets/images/wizard-back.jpg | Bin 0 -> 119193 bytes nw/gui/about.py | 7 +++--- nw/gui/projload.py | 5 +++-- nw/gui/projwizard.py | 21 +++++++++--------- nw/gui/theme.py | 36 +++++++++++++++++++------------ 5 files changed, 40 insertions(+), 29 deletions(-) create mode 100644 nw/assets/images/wizard-back.jpg diff --git a/nw/assets/images/wizard-back.jpg b/nw/assets/images/wizard-back.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8db614c8028e9d483803fab9918b7daed0931094 GIT binary patch literal 119193 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

VmY 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 literal 0 HcmV?d00001 diff --git a/nw/gui/about.py b/nw/gui/about.py index d432a4d9..5f4c9b87 100644 --- a/nw/gui/about.py +++ b/nw/gui/about.py @@ -58,15 +58,16 @@ class GuiAbout(QDialog): self.setMinimumWidth(self.mainConf.pxInt(650)) self.setMinimumHeight(self.mainConf.pxInt(600)) - iPx = self.mainConf.pxInt(96) - self.guiDeco = self.theParent.theTheme.loadDecoration("nwicon", (iPx, iPx)) + nPx = self.mainConf.pxInt(96) + self.nwIcon = QLabel() + self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx))) self.lblName = QLabel("%s" % nw.__package__) self.lblVers = QLabel("v%s" % nw.__version__) self.lblDate = QLabel(datetime.strptime(nw.__date__, "%Y-%m-%d").strftime("%x")) self.leftBox = QVBoxLayout() self.leftBox.setSpacing(self.mainConf.pxInt(4)) - self.leftBox.addWidget(self.guiDeco, 0, Qt.AlignCenter) + self.leftBox.addWidget(self.nwIcon, 0, Qt.AlignCenter) self.leftBox.addWidget(self.lblName, 0, Qt.AlignCenter) self.leftBox.addWidget(self.lblVers, 0, Qt.AlignCenter) self.leftBox.addWidget(self.lblDate, 0, Qt.AlignCenter) diff --git a/nw/gui/projload.py b/nw/gui/projload.py index 30332690..296adb94 100644 --- a/nw/gui/projload.py +++ b/nw/gui/projload.py @@ -79,8 +79,9 @@ class GuiProjectLoad(QDialog): self.setMinimumHeight(self.mainConf.pxInt(400)) self.setModal(True) - self.guiDeco = self.theTheme.loadDecoration("nwicon", (nPx, nPx)) - self.innerBox.addWidget(self.guiDeco, 0, Qt.AlignTop) + self.nwIcon = QLabel() + self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx))) + self.innerBox.addWidget(self.nwIcon, 0, Qt.AlignTop) self.projectForm = QGridLayout() self.projectForm.setContentsMargins(0, 0, 0, 0) diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index d606ed23..288b5b76 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -33,7 +33,7 @@ from os import path from PyQt5.QtGui import QPixmap, QColor from PyQt5.QtWidgets import ( QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit, - QPushButton, QFileDialog, QHBoxLayout + QPushButton, QFileDialog, QHBoxLayout, QLayout ) from nw.common import makeFileNameSafe @@ -52,14 +52,10 @@ class GuiProjectWizard(QWizard): self.theParent = theParent self.theTheme = theParent.theTheme - nPx = self.mainConf.pxInt(96) - wmW = self.mainConf.pxInt(100) - wmH = self.mainConf.pxInt(340) - - self.sideImage = QPixmap(wmW, wmH) - self.sideImage.fill(self.palette().dark().color()) - - # self.setWizardStyle(QWizard.ModernStyle) + self.sideImage = self.theTheme.loadDecoration( + "wiz-back", None, self.mainConf.pxInt(370) + ) + self.setWizardStyle(QWizard.ModernStyle) self.setPixmap(QWizard.WatermarkPixmap, self.sideImage) self.introPage = ProjWizardIntroPage(self.theParent) @@ -69,7 +65,6 @@ class GuiProjectWizard(QWizard): self.addPage(self.storagePage) self.setOption(QWizard.NoBackButtonOnStartPage, True) - # self.setOption(QWizard.ExtendedWatermarkPixmap, True) logger.debug("GuiProjectWizard initialisation complete") @@ -95,6 +90,11 @@ class ProjWizardIntroPage(QWizardPage): ) self.theText.setWordWrap(True) + self.imgCredit = QLabel("Side image by Peter Mitterhofer, CC BY-SA 4.0") + lblFont = self.imgCredit.font() + lblFont.setPointSizeF(0.6*self.theTheme.fontPointSize) + self.imgCredit.setFont(lblFont) + xW = self.mainConf.pxInt(300) xH = self.mainConf.pxInt(100) vS = self.mainConf.pxInt(12) @@ -132,6 +132,7 @@ class ProjWizardIntroPage(QWizardPage): self.outerBox.addWidget(self.theText) self.outerBox.addLayout(self.mainForm) self.outerBox.addStretch(1) + self.outerBox.addWidget(self.imgCredit) self.setLayout(self.outerBox) return diff --git a/nw/gui/theme.py b/nw/gui/theme.py index 0265df62..2cb1affa 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -33,7 +33,7 @@ import nw from os import path, listdir from math import ceil -from PyQt5.QtCore import QSize +from PyQt5.QtCore import Qt, QSize from PyQt5.QtSvg import QSvgWidget from PyQt5.QtWidgets import QStyle, qApp from PyQt5.QtGui import ( @@ -497,6 +497,7 @@ class GuiIcons: ICON_MAP = { # Project and GUI icons + "novelwriter" : (None, None), "cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), "cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), @@ -552,7 +553,7 @@ class GuiIcons: } DECO_MAP = { - "nwicon" : ["icons", "novelwriter.svg"], + "wiz-back" : "wizard-back.jpg", } def __init__(self, theParent): @@ -645,7 +646,7 @@ class GuiIcons: # Access Functions ## - def loadDecoration(self, decoKey, decoSize=None): + def loadDecoration(self, decoKey, pxW, pxH): """Load graphical decoration element based on the decoration map. This function always returns a QSwgWidget. """ @@ -653,20 +654,22 @@ class GuiIcons: logger.error("Decoration with name '%s' does not exist" % decoKey) return QSvgWidget() - svgPath = path.join( - self.mainConf.assetPath, - self.DECO_MAP[decoKey][0], - self.DECO_MAP[decoKey][1] + imgPath = path.join( + self.mainConf.assetPath, "images", self.DECO_MAP[decoKey] ) - if not path.isfile(svgPath): + if not path.isfile(imgPath): logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey]) - return QSvgWidget() + return QPixmap() - svgDeco = QSvgWidget(svgPath) - if decoSize is not None: - svgDeco.setFixedSize(QSize(decoSize[0],decoSize[1])) + theDeco = QPixmap(imgPath) + if pxW is not None and pxH is not None: + return theDeco.scaled(pxW, pxH, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) + elif pxW is None and pxH is not None: + return theDeco.scaledToHeight(pxH, Qt.SmoothTransformation) + elif pxW is not None and pxH is None: + return theDeco.scaledToWidth(pxW, Qt.SmoothTransformation) - return svgDeco + return theDeco def getIcon(self, iconKey, iconSize=None): """Return an icon from the icon buffer. If it doesn't exist, @@ -730,11 +733,16 @@ class GuiIcons: an icon exists. Prefer svg files over png files. Always returns a QIcon. """ - if iconKey not in self.ICON_MAP: logger.error("Requested unknown icon name '%s'" % iconKey) return QIcon() + # If we just want the app icon, return it right away + if iconKey == "novelwriter": + return QIcon(path.join(self.mainConf.iconPath, "novelwriter.svg")) + + # Otherwise, we start looking for it + # First in the theme folder if iconKey in self.themeMap: logger.verbose("Loading: %s" % path.relpath(self.themeMap[iconKey])) return QIcon(self.themeMap[iconKey]) From 9db7550d301a868ab1bdc8b2b2b0c24d50493938 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 12 Jul 2020 18:11:56 +0200 Subject: [PATCH 05/22] Added populate project page, and two more dummy pages --- nw/gui/projwizard.py | 135 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 131 insertions(+), 4 deletions(-) diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index 288b5b76..82c95513 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -33,7 +33,7 @@ from os import path from PyQt5.QtGui import QPixmap, QColor from PyQt5.QtWidgets import ( QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit, - QPushButton, QFileDialog, QHBoxLayout, QLayout + QPushButton, QFileDialog, QHBoxLayout, QLayout, QRadioButton ) from nw.common import makeFileNameSafe @@ -41,6 +41,12 @@ from nw.gui.custom import QSwitch, QConfigLayout 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, theParent): @@ -60,9 +66,15 @@ class GuiProjectWizard(QWizard): self.introPage = ProjWizardIntroPage(self.theParent) self.storagePage = ProjWizardFolderPage(self.theParent) + self.popPage = ProjWizardPopulatePage(self.theParent) + self.customPage = ProjWizardCustomPage(self.theParent) + self.finalPage = ProjWizardFinalPage(self.theParent) - self.addPage(self.introPage) - self.addPage(self.storagePage) + 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) @@ -156,7 +168,6 @@ class ProjWizardFolderPage(QWizardPage): self.theText.setWordWrap(True) xW = self.mainConf.pxInt(300) - # xH = self.mainConf.pxInt(100) vS = self.mainConf.pxInt(12) fS = self.mainConf.pxInt(4) @@ -212,3 +223,119 @@ class ProjWizardFolderPage(QWizardPage): return # END Class ProjWizardFolderPage + +class ProjWizardPopulatePage(QWizardPage): + + def __init__(self, theParent): + QWizardPage.__init__(self) + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theTheme = theParent.theTheme + + self.setTitle("Populate Project") + self.theText = QLabel( + "Choose how to pre-fill the project. Either with a minimal set of " + "starter items, a sample project explaining and showing many of " + "the features, or show further custom options on the next page." + ) + self.theText.setWordWrap(True) + + vS = self.mainConf.pxInt(12) + fS = self.mainConf.pxInt(4) + + self.popSample = QRadioButton("Fill the project with sample files") + self.popMinimal = QRadioButton("Fill the project with a minimal set of items") + self.popCustom = QRadioButton("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("popSample", self.popSample) + self.registerField("popMinimal", self.popMinimal) + 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, theParent): + QWizardPage.__init__(self) + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theTheme = theParent.theTheme + + self.setTitle("Custom Project Options") + self.theText = QLabel( + "Text" + ) + self.theText.setWordWrap(True) + + vS = self.mainConf.pxInt(12) + fS = self.mainConf.pxInt(4) + + # 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 + +# END Class ProjWizardCustomPage + +class ProjWizardFinalPage(QWizardPage): + + def __init__(self, theParent): + QWizardPage.__init__(self) + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theTheme = theParent.theTheme + + self.setTitle("Overview") + self.theText = QLabel( + "Text" + ) + self.theText.setWordWrap(True) + + vS = self.mainConf.pxInt(12) + fS = self.mainConf.pxInt(4) + + # 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 + +# END Class ProjWizardFinalPage From 32878a336d6a4fbfcbf04f6bef0a56a128156ec0 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 26 Jul 2020 14:14:49 +0200 Subject: [PATCH 06/22] Uncommited changes --- nw/gui/projwizard.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index 82c95513..a07956f7 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -33,11 +33,11 @@ from os import path from PyQt5.QtGui import QPixmap, QColor from PyQt5.QtWidgets import ( QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit, - QPushButton, QFileDialog, QHBoxLayout, QLayout, QRadioButton + QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout ) from nw.common import makeFileNameSafe -from nw.gui.custom import QSwitch, QConfigLayout +from nw.gui.custom import QSwitch logger = logging.getLogger(__name__) @@ -128,7 +128,7 @@ class ProjWizardIntroPage(QWizardPage): self.projAuthors.setFixedWidth(xW) self.projAuthors.setPlaceholderText("Optional. One name per line.") - self.mainForm = QConfigLayout() + self.mainForm = QFormLayout() self.mainForm.addRow("Working Title", self.projName) self.mainForm.addRow("Novel Title", self.projTitle) self.mainForm.addRow("Author(s)", self.projAuthors) @@ -169,7 +169,7 @@ class ProjWizardFolderPage(QWizardPage): xW = self.mainConf.pxInt(300) vS = self.mainConf.pxInt(12) - fS = self.mainConf.pxInt(4) + fS = self.mainConf.pxInt(8) self.projPath = QLineEdit("") self.projPath.setFixedWidth(xW) @@ -179,9 +179,11 @@ class ProjWizardFolderPage(QWizardPage): self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("..."))) self.browseButton.clicked.connect(self._doBrowse) - self.mainForm = QConfigLayout() - self.mainForm.addRow("Project Path", self.projPath, theButton=self.browseButton) - self.mainForm.setVerticalSpacing(fS) + self.mainForm = QHBoxLayout() + self.mainForm.addWidget(QLabel("Project Path"), 0) + self.mainForm.addWidget(self.projPath, 1) + self.mainForm.addWidget(self.browseButton, 0) + self.mainForm.setSpacing(fS) self.registerField("projPath*", self.projPath) From 2702edced4f982617d1bc72d9b2f7f820f079927 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 9 Aug 2020 17:23:25 +0200 Subject: [PATCH 07/22] Completed the last couple of pages --- nw/gui/projwizard.py | 140 +++++++++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 32 deletions(-) diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index a07956f7..b76f2f2c 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -30,13 +30,16 @@ import nw from os import path +from PyQt5.QtCore import Qt from PyQt5.QtGui import QPixmap, QColor from PyQt5.QtWidgets import ( QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit, - QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout + QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout, + QGroupBox, QGridLayout, QSpinBox ) from nw.common import makeFileNameSafe +from nw.constants import nwLabels, nwItemClass from nw.gui.custom import QSwitch logger = logging.getLogger(__name__) @@ -64,11 +67,11 @@ class GuiProjectWizard(QWizard): self.setWizardStyle(QWizard.ModernStyle) self.setPixmap(QWizard.WatermarkPixmap, self.sideImage) - self.introPage = ProjWizardIntroPage(self.theParent) - self.storagePage = ProjWizardFolderPage(self.theParent) - self.popPage = ProjWizardPopulatePage(self.theParent) - self.customPage = ProjWizardCustomPage(self.theParent) - self.finalPage = ProjWizardFinalPage(self.theParent) + 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) @@ -86,12 +89,12 @@ class GuiProjectWizard(QWizard): class ProjWizardIntroPage(QWizardPage): - def __init__(self, theParent): + def __init__(self, theWizard): QWizardPage.__init__(self) self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme + self.theWizard = theWizard + self.theTheme = theWizard.theTheme self.setTitle("Create New Project") self.theText = QLabel( @@ -153,12 +156,12 @@ class ProjWizardIntroPage(QWizardPage): class ProjWizardFolderPage(QWizardPage): - def __init__(self, theParent): + def __init__(self, theWizard): QWizardPage.__init__(self) self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme + self.theWizard = theWizard + self.theTheme = theWizard.theTheme self.setTitle("Select Project Folder") self.theText = QLabel( @@ -228,12 +231,11 @@ class ProjWizardFolderPage(QWizardPage): class ProjWizardPopulatePage(QWizardPage): - def __init__(self, theParent): + def __init__(self, theWizard): QWizardPage.__init__(self) self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme + self.theWizard = theWizard self.setTitle("Populate Project") self.theText = QLabel( @@ -284,27 +286,103 @@ class ProjWizardPopulatePage(QWizardPage): class ProjWizardCustomPage(QWizardPage): - def __init__(self, theParent): + def __init__(self, theWizard): QWizardPage.__init__(self) self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme + self.theWizard = theWizard self.setTitle("Custom Project Options") self.theText = QLabel( - "Text" + "Select which additional root folders to make, and how to populate " + "the Novel folder. If you don't want to add chapters or scenes, set " + "the values to 0. You can add scenes without chapters." ) self.theText.setWordWrap(True) vS = self.mainConf.pxInt(12) fS = self.mainConf.pxInt(4) + # Root Folders + self.rootGroup = QGroupBox("Additional Root Folders") + self.rootForm = QGridLayout() + self.rootGroup.setLayout(self.rootForm) + + self.lblPlot = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.PLOT]) + self.lblChar = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.CHARACTER]) + self.lblWorld = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.WORLD]) + self.lblTime = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.TIMELINE]) + self.lblObject = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.OBJECT]) + self.lblEntity = QLabel("%s folder" % nwLabels.CLASS_NAME[nwItemClass.ENTITY]) + + self.addPlot = QSwitch() + self.addChar = QSwitch() + self.addWorld = QSwitch() + self.addTime = QSwitch() + self.addObject = QSwitch() + self.addEntity = QSwitch() + + self.addPlot.setChecked(True) + self.addChar.setChecked(True) + + self.rootForm.addWidget(self.lblPlot, 0, 0) + self.rootForm.addWidget(self.lblChar, 1, 0) + self.rootForm.addWidget(self.lblWorld, 2, 0) + self.rootForm.addWidget(self.lblTime, 3, 0) + self.rootForm.addWidget(self.lblObject, 4, 0) + self.rootForm.addWidget(self.lblEntity, 5, 0) + self.rootForm.addWidget(self.addPlot, 0, 1, 1, 1, Qt.AlignRight) + self.rootForm.addWidget(self.addChar, 1, 1, 1, 1, Qt.AlignRight) + self.rootForm.addWidget(self.addWorld, 2, 1, 1, 1, Qt.AlignRight) + self.rootForm.addWidget(self.addTime, 3, 1, 1, 1, Qt.AlignRight) + self.rootForm.addWidget(self.addObject, 4, 1, 1, 1, Qt.AlignRight) + self.rootForm.addWidget(self.addEntity, 5, 1, 1, 1, Qt.AlignRight) + self.rootForm.setRowStretch(6, 1) + + # Novel Options + self.novelGroup = QGroupBox("Populate Novel Folder") + self.novelForm = QGridLayout() + self.novelGroup.setLayout(self.novelForm) + + self.numChapters = QSpinBox() + self.numChapters.setRange(0, 100) + self.numChapters.setValue(5) + + self.numScenes = QSpinBox() + self.numScenes.setRange(0, 200) + self.numScenes.setValue(5) + + self.chFolders = QSwitch() + self.chFolders.setChecked(True) + + self.novelForm.addWidget(QLabel("Add chapters"), 0, 0) + self.novelForm.addWidget(QLabel("Scenes (per chapter)"), 1, 0) + self.novelForm.addWidget(QLabel("Add chapter folders"), 2, 0) + self.novelForm.addWidget(self.numChapters, 0, 1, 1, 1, Qt.AlignRight) + self.novelForm.addWidget(self.numScenes, 1, 1, 1, 1, Qt.AlignRight) + self.novelForm.addWidget(self.chFolders, 2, 1, 1, 1, Qt.AlignRight) + self.novelForm.setRowStretch(3, 1) + + # Wizard Fields + self.registerField("addPlot", self.addPlot) + self.registerField("addChar", self.addChar) + self.registerField("addWorld", self.addWorld) + self.registerField("addTime", self.addTime) + self.registerField("addObject", self.addObject) + self.registerField("addEntity", self.addEntity) + self.registerField("numChapters", self.numChapters) + self.registerField("numScenes", self.numScenes) + self.registerField("chFolders", self.chFolders) + # Assemble + self.innerBox = QHBoxLayout() + self.innerBox.addWidget(self.rootGroup) + self.innerBox.addWidget(self.novelGroup) + self.outerBox = QVBoxLayout() self.outerBox.setSpacing(vS) self.outerBox.addWidget(self.theText) - # self.outerBox.addLayout(self.popBox) + self.outerBox.addLayout(self.innerBox) self.outerBox.addStretch(1) self.setLayout(self.outerBox) @@ -314,27 +392,25 @@ class ProjWizardCustomPage(QWizardPage): class ProjWizardFinalPage(QWizardPage): - def __init__(self, theParent): + def __init__(self, theWizard): QWizardPage.__init__(self) self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme + self.theWizard = theWizard - self.setTitle("Overview") - self.theText = QLabel( - "Text" - ) + self.setTitle("Finished") + self.theText = QLabel(( + "

All done.

" + "

Press '{finish}' to create the new project.

" + ).format( + finish = "Done" if self.mainConf.osDarwin else "Finish" + )) self.theText.setWordWrap(True) - vS = self.mainConf.pxInt(12) - fS = self.mainConf.pxInt(4) - # Assemble self.outerBox = QVBoxLayout() - self.outerBox.setSpacing(vS) + self.outerBox.setSpacing(self.mainConf.pxInt(12)) self.outerBox.addWidget(self.theText) - # self.outerBox.addLayout(self.popBox) self.outerBox.addStretch(1) self.setLayout(self.outerBox) From ebec7c22a0c69ed4c477c80d4b1ceae6a8ccfb63 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 9 Aug 2020 17:59:40 +0200 Subject: [PATCH 08/22] Wizard can now make a minimal project --- nw/core/project.py | 79 +++++++++++++++++++++++++++++++++++--------- nw/gui/projwizard.py | 2 +- nw/guimain.py | 56 +++++++++++++++++++++++-------- 3 files changed, 108 insertions(+), 29 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index 12c157bc..7f8158b9 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -171,20 +171,53 @@ class NWProject(): # Project Methods ## - def newProject(self): + def newProject(self, projData={}): """Create a new project by populating the project tree with a few starter items. """ - self.projName = "New Project" - hNovel = self.newRoot("Novel", nwItemClass.NOVEL) - hChars = self.newRoot("Characters", nwItemClass.CHARACTER) - hWorld = self.newRoot("Plot", nwItemClass.PLOT) - hWorld = self.newRoot("World", nwItemClass.WORLD) - hChapt = self.newFolder("New Chapter", nwItemClass.NOVEL, hNovel) - hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapt) + print(projData) + # Project Title and Authors + if "projName" in projData: + self.setProjectName(projData["projName"]) + else: + self.setProjectName("New Project") + if "projTitle" in projData: + self.setBookTitle(projData["projTitle"]) + if "projAuthors" in projData: + self.setBookAuthors(projData["projAuthors"]) + + popMinimal = True + popSample = False + popCustom = False + if "popMinimal" in projData: + popMinimal = projData["popMinimal"] + if "popSample" in projData: + popSample = projData["popSample"] + if "popCustom" in projData: + popCustom = projData["popCustom"] + + if popMinimal: + hNovel = self.newRoot("Novel", nwItemClass.NOVEL) + hChars = self.newRoot("Characters", nwItemClass.CHARACTER) + hWorld = self.newRoot("Plot", nwItemClass.PLOT) + hWorld = self.newRoot("World", nwItemClass.WORLD) + hChapF = self.newFolder("New Chapter", nwItemClass.NOVEL, hNovel) + hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapF) + + elif popSample: + pass + + elif popCustom: + pass + + else: + # Fallback just in case. + self.newRoot("Novel", nwItemClass.NOVEL) + self.projOpened = time() self.setProjectChanged(True) self.saveProject(autoSave=True) + return True def clearProject(self): @@ -728,13 +761,24 @@ class NWProject(): projPath = path.expanduser(projPath) self.projPath = path.abspath(projPath) - if newProject and self.mainConf.showGUI: - if listdir(self.projPath): - self.theParent.makeAlert(( - "New project folder is not empty. " - "Each project requires a dedicated project folder." - ), nwAlert.ERROR) - return False + if newProject: + if not path.isdir(projPath): + try: + mkdir(projPath) + logger.debug("Created folder %s" % projPath) + except Exception as e: + self.theParent.makeAlert(( + ["Could not create new project folder.", str(e)] + ), nwAlert.ERROR) + return False + + if path.isdir(projPath): + if self.mainConf.showGUI and listdir(self.projPath): + self.theParent.makeAlert(( + "New project folder is not empty. " + "Each project requires a dedicated project folder." + ), nwAlert.ERROR) + return False self.ensureFolderStructure() self.setProjectChanged(True) @@ -759,13 +803,18 @@ class NWProject(): def setBookAuthors(self, bookAuthors): """A line separated list of book authors, parsed into an array. """ + if not isinstance(bookAuthors, str): + return False + self.bookAuthors = [] for bookAuthor in bookAuthors.split("\n"): bookAuthor = bookAuthor.strip() if bookAuthor == "": continue self.bookAuthors.append(bookAuthor) + self.setProjectChanged(True) + return True def setProjBackup(self, doBackup): diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index b76f2f2c..17470587 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -139,7 +139,7 @@ class ProjWizardIntroPage(QWizardPage): self.registerField("projName*", self.projName) self.registerField("projTitle", self.projTitle) - self.registerField("projAuthors", self.projAuthors) + self.registerField("projAuthors", self.projAuthors, "plainText") # Assemble self.outerBox = QVBoxLayout() diff --git a/nw/guimain.py b/nw/guimain.py index fb37a4d6..ddcaa7ba 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -47,7 +47,7 @@ from nw.gui import ( GuiAbout ) from nw.core import NWProject, NWDoc, NWIndex -from nw.constants import nwItemType, nwAlert +from nw.constants import nwItemType, nwItemClass, nwAlert logger = logging.getLogger(__name__) @@ -274,8 +274,12 @@ class GuiMain(QMainWindow): ) return False + projData = {} if projPath is None: - projPath = self.newProjectDialog() + projData = self.newProjectDialog() + if "projPath" in projData: + projPath = projData["projPath"] + if projPath is None: return False @@ -289,12 +293,13 @@ class GuiMain(QMainWindow): logger.info("Creating new project") if self.theProject.setProjectPath(projPath, newProject=True): - self.theProject.newProject() + self.theProject.newProject(projData) self.rebuildTree() self.saveProject() self.hasProject = True self.statusBar.setRefTime(self.theProject.projOpened) else: + self.theProject.clearProject() return False return True @@ -767,19 +772,44 @@ class GuiMain(QMainWindow): return None def newProjectDialog(self): - """Select where to save new project. + """Open the wizard and assemble the project options dict. """ newProj = GuiProjectWizard(self) newProj.exec_() - # dlgOpt = QFileDialog.Options() - # dlgOpt |= QFileDialog.ShowDirsOnly - # dlgOpt |= QFileDialog.DontUseNativeDialog - # projPath = QFileDialog.getExistingDirectory( - # self, "Select Location for New novelWriter Project", "", options=dlgOpt - # ) - # if projPath: - # return projPath - return None + + projData = { + "projName": newProj.field("projName"), + "projTitle": newProj.field("projTitle"), + "projAuthors": newProj.field("projAuthors"), + "projPath": newProj.field("projPath"), + "popSample": newProj.field("popSample"), + "popMinimal": newProj.field("popMinimal"), + "popCustom": newProj.field("popCustom"), + "addRoots": [], + "numChapters": 0, + "numScenes": 0, + "chFolders": False, + } + 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) + if newProj.field("addTime"): + addRoots.append(nwItemClass.TIMELINE) + if newProj.field("addObject"): + addRoots.append(nwItemClass.OBJECT) + if newProj.field("addEntity"): + addRoots.append(nwItemClass.ENTITY) + projData["addRoots"] = addRoots + projData["numChapters"] = newProj.field("numChapters") + projData["numScenes"] = newProj.field("numScenes") + projData["chFolders"] = newProj.field("chFolders") + + return projData def editConfigDialog(self): """Open the preferences dialog. From b5b657650a67b18c279a0f64f1943c9ff189eb43 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 9 Aug 2020 18:17:46 +0200 Subject: [PATCH 09/22] Creating a custom project now works --- nw/core/project.py | 47 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index 7f8158b9..d92e9d74 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -45,7 +45,7 @@ from nw.common import ( checkString, checkBool, checkInt, formatTimeStamp, makeFileNameSafe ) from nw.constants import ( - nwFiles, nwItemType, nwItemClass, nwItemLayout, nwAlert + nwFiles, nwItemType, nwItemClass, nwItemLayout, nwLabels, nwAlert ) logger = logging.getLogger(__name__) @@ -175,7 +175,6 @@ class NWProject(): """Create a new project by populating the project tree with a few starter items. """ - print(projData) # Project Title and Authors if "projName" in projData: self.setProjectName(projData["projName"]) @@ -208,7 +207,49 @@ class NWProject(): pass elif popCustom: - pass + # Create Root Folders + hNovel = self.newRoot("Novel", nwItemClass.NOVEL) + if "addRoots" in projData: + for newRoot in projData["addRoots"]: + if newRoot in nwItemClass: + self.newRoot(nwLabels.CLASS_NAME[newRoot], newRoot) + + # Create Chapters and Scenes + numChapters = 0 + numScenes = 0 + chFolders = False + if "numChapters" in projData: + numChapters = projData["numChapters"] + if "numScenes" in projData: + numScenes = projData["numScenes"] + if "chFolders" in projData: + chFolders = projData["chFolders"] + + # Create Chapters + if numChapters > 0: + for ch in range(numChapters): + chTitle = "Chapter %d" % (ch+1) + hParDir = hNovel + if chFolders: + hParDir = self.newFolder(chTitle, nwItemClass.NOVEL, hNovel) + hChapD = self.newFile(chTitle, nwItemClass.NOVEL, hParDir) + chItem = self.projTree[hChapD] + if hChapD is None: + logger.error("Something went wrong when creating chapter file.") + continue + chItem.setLayout(nwItemLayout.CHAPTER) + + # Create Chapter > Scenes + if numScenes > 0: + for sc in range(numScenes): + scTitle = "Scene %d.%d" % (ch+1, sc+1) + hScene = self.newFile(scTitle, nwItemClass.NOVEL, hParDir) + + # Create Scenes (No Chapters) + elif numScenes > 0: + for sc in range(numScenes): + scTitle = "Scene %d" % (sc+1) + hScene = self.newFile(scTitle, nwItemClass.NOVEL, hNovel) else: # Fallback just in case. From 3c1e2727ad70ad866d96caa1ab668d6d9eb50159 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 9 Aug 2020 19:57:45 +0200 Subject: [PATCH 10/22] Extratcing the example project now works --- .gitignore | 1 + nw/core/project.py | 96 +++++++++++++++++++++++++++++++++++++------- nw/gui/projwizard.py | 6 +-- nw/guimain.py | 13 +++--- 4 files changed, 94 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 1cc84316..a515c603 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ novelWriter.qhc __pycache__ # Sample Project +/nw/assets/sample.zip /sample/cache /sample/meta *.bak diff --git a/nw/core/project.py b/nw/core/project.py index d92e9d74..8c4d1b8e 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -32,7 +32,7 @@ import nw from os import path, mkdir, listdir, unlink, rename, rmdir from lxml import etree from time import time -from shutil import make_archive +from shutil import make_archive, unpack_archive, copyfile from PyQt5.QtWidgets import QMessageBox @@ -175,6 +175,21 @@ class NWProject(): """Create a new project by populating the project tree with a few starter items. """ + popMinimal = True + popCustom = False + popSample = False + if "popMinimal" in projData: + popMinimal = projData["popMinimal"] + if "popCustom" in projData: + popCustom = projData["popCustom"] + if "popSample" in projData: + popSample = projData["popSample"] + + # Check if we're extracting the sample project. + # This is handled differently as it isn't actually a new project. + if popSample: + return self.extractSampleProject(projData) + # Project Title and Authors if "projName" in projData: self.setProjectName(projData["projName"]) @@ -185,17 +200,9 @@ class NWProject(): if "projAuthors" in projData: self.setBookAuthors(projData["projAuthors"]) - popMinimal = True - popSample = False - popCustom = False - if "popMinimal" in projData: - popMinimal = projData["popMinimal"] - if "popSample" in projData: - popSample = projData["popSample"] - if "popCustom" in projData: - popCustom = projData["popCustom"] - if popMinimal: + # Creating a minimal project with a few root folders, a + # single chapter folder with a single file. hNovel = self.newRoot("Novel", nwItemClass.NOVEL) hChars = self.newRoot("Characters", nwItemClass.CHARACTER) hWorld = self.newRoot("Plot", nwItemClass.PLOT) @@ -203,10 +210,11 @@ class NWProject(): hChapF = self.newFolder("New Chapter", nwItemClass.NOVEL, hNovel) hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapF) - elif popSample: - pass - elif popCustom: + # Create a project structure based on selected root folders + # and a number of chapters and scenes selected in the + # wizard's custom page. + # Create Root Folders hNovel = self.newRoot("Novel", nwItemClass.NOVEL) if "addRoots" in projData: @@ -255,12 +263,72 @@ class NWProject(): # Fallback just in case. self.newRoot("Novel", nwItemClass.NOVEL) + # Finalise self.projOpened = time() self.setProjectChanged(True) self.saveProject(autoSave=True) return True + def extractSampleProject(self, projData): + """Make a copy of the sample project. + First, try to copy the content of the sample folder to the new + project path, or if the folder doesn't exist, look for the zip + file in the assets folder. + """ + if "projName" in projData: + projName = projData["projName"] + else: + projName = "Sample Project" + + if "projPath" in projData: + projPath = projData["projPath"] + else: + logger.error("No project path set for the example project") + return False + + srcSample = path.abspath(path.join(self.mainConf.appRoot, "sample")) + pkgSample = path.join(self.mainConf.assetPath, "sample.zip") + + isSuccess = False + if path.isfile(pkgSample): + + try: + unpack_archive(pkgSample, projPath) + isSuccess = True + except Exception as e: + self.makeAlert( + ["Failed to create a new example project.", str(e)], nwAlert.ERROR + ) + + elif path.isdir(srcSample): + + try: + srcProj = path.join(srcSample, nwFiles.PROJ_FILE) + dstProj = path.join(projPath, nwFiles.PROJ_FILE) + copyfile(srcProj, dstProj) + + srcContent = path.join(srcSample, "content") + dstContent = path.join(projPath, "content") + for srcFile in listdir(srcContent): + srcDoc = path.join(srcContent, srcFile) + dstDoc = path.join(dstContent, srcFile) + copyfile(srcDoc, dstDoc) + + isSuccess = True + + except Exception as e: + self.makeAlert( + ["Failed to create a new example project.", str(e)], nwAlert.ERROR + ) + + if isSuccess: + self.clearProject() + self.theParent.openProject(projPath) + self.theParent.rebuildIndex() + + return isSuccess + def clearProject(self): """Clear the data for the current project, and set them to default values. diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index 17470587..7460dbe2 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -248,20 +248,20 @@ class ProjWizardPopulatePage(QWizardPage): vS = self.mainConf.pxInt(12) fS = self.mainConf.pxInt(4) - self.popSample = QRadioButton("Fill the project with sample files") self.popMinimal = QRadioButton("Fill the project with a minimal set of items") self.popCustom = QRadioButton("Show detailed options for filling the project") + self.popSample = QRadioButton("Fill the project with example files") 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.popBox.addWidget(self.popSample) - self.registerField("popSample", self.popSample) self.registerField("popMinimal", self.popMinimal) self.registerField("popCustom", self.popCustom) + self.registerField("popSample", self.popSample) # Assemble self.outerBox = QVBoxLayout() diff --git a/nw/guimain.py b/nw/guimain.py index ddcaa7ba..f59deeb0 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -293,11 +293,14 @@ class GuiMain(QMainWindow): logger.info("Creating new project") if self.theProject.setProjectPath(projPath, newProject=True): - self.theProject.newProject(projData) - self.rebuildTree() - self.saveProject() - self.hasProject = True - self.statusBar.setRefTime(self.theProject.projOpened) + if self.theProject.newProject(projData): + self.rebuildTree() + self.saveProject() + self.hasProject = True + self.statusBar.setRefTime(self.theProject.projOpened) + else: + self.theProject.clearProject() + return False else: self.theProject.clearProject() return False From 10067921f66b26270ee4fd12c17fb2038e8a0e6b Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 9 Aug 2020 20:20:10 +0200 Subject: [PATCH 11/22] Fixed a few issues --- nw/core/project.py | 15 +++++++++++++++ nw/guimain.py | 20 +++++++++++--------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index 8c4d1b8e..3eb1850c 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -190,6 +190,13 @@ class NWProject(): if popSample: return self.extractSampleProject(projData) + # Set the project path + if "projPath" in projData: + self.setProjectPath(projData["projPath"], newProject=True) + else: + logger.error("No project path set for the new project") + return False + # Project Title and Authors if "projName" in projData: self.setProjectName(projData["projName"]) @@ -293,6 +300,7 @@ class NWProject(): isSuccess = False if path.isfile(pkgSample): + self.setProjectPath(projPath, newProject=True) try: unpack_archive(pkgSample, projPath) isSuccess = True @@ -303,6 +311,7 @@ class NWProject(): elif path.isdir(srcSample): + self.setProjectPath(projPath, newProject=True) try: srcProj = path.join(srcSample, nwFiles.PROJ_FILE) dstProj = path.join(projPath, nwFiles.PROJ_FILE) @@ -322,6 +331,12 @@ class NWProject(): ["Failed to create a new example project.", str(e)], nwAlert.ERROR ) + else: + self.makeAlert(( + "Failed to create a new example project. " + "Could not find the necessary files." + ), nwAlert.ERROR) + if isSuccess: self.clearProject() self.theParent.openProject(projPath) diff --git a/nw/guimain.py b/nw/guimain.py index f59deeb0..00778aba 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -265,6 +265,7 @@ class GuiMain(QMainWindow): def newProject(self, projPath=None, forceNew=False): """Create new project with a few default files and folders. + The variable forceNew is used for testing. """ if self.hasProject: msgBox = QMessageBox() @@ -277,6 +278,8 @@ class GuiMain(QMainWindow): projData = {} if projPath is None: projData = self.newProjectDialog() + if projData is None: + return False if "projPath" in projData: projPath = projData["projPath"] @@ -292,15 +295,11 @@ class GuiMain(QMainWindow): return False logger.info("Creating new project") - if self.theProject.setProjectPath(projPath, newProject=True): - if self.theProject.newProject(projData): - self.rebuildTree() - self.saveProject() - self.hasProject = True - self.statusBar.setRefTime(self.theProject.projOpened) - else: - self.theProject.clearProject() - return False + if self.theProject.newProject(projData): + self.rebuildTree() + self.saveProject() + self.hasProject = True + self.statusBar.setRefTime(self.theProject.projOpened) else: self.theProject.clearProject() return False @@ -780,6 +779,9 @@ class GuiMain(QMainWindow): newProj = GuiProjectWizard(self) newProj.exec_() + if newProj.result() == QDialog.Rejected: + return None + projData = { "projName": newProj.field("projName"), "projTitle": newProj.field("projTitle"), From 9044ab4c3f7df0574ff1ff916bb8d58183b02a5c Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 9 Aug 2020 20:37:48 +0200 Subject: [PATCH 12/22] Added setup command to create sample.zip file --- setup.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/setup.py b/setup.py index 818af1a2..1ae7d6b7 100755 --- a/setup.py +++ b/setup.py @@ -12,10 +12,20 @@ from nw import __version__, __url__, __docurl__, __issuesurl__, __sourceurl__ ## buildDocs = False +buildSample = False + if "qthelp" in sys.argv: buildDocs = True sys.argv.remove("qthelp") +if "sample" in sys.argv: + buildSample = True + sys.argv.remove("sample") + +## +# Qt Assistant Documentation +## + if buildDocs: buildDir = os.path.join("docs", "build", "qthelp") @@ -72,6 +82,31 @@ if buildDocs: print("Documentation build: OK") print("") +## +# Sample Project ZIP file +## + +if buildSample: + + srcSample = "sample" + dstSample = os.path.join("nw", "assets", "sample.zip") + + if os.path.isdir(srcSample): + if os.path.isfile(dstSample): + os.unlink(dstSample) + + from zipfile import ZipFile + + with ZipFile(dstSample, "w") as zipObj: + zipObj.write(os.path.join("sample", "nwProject.nwx"), "nwProject.nwx") + for docFile in os.listdir(os.path.join(srcSample, "content")): + srcDoc = os.path.join(srcSample, "content", docFile) + zipObj.write(srcDoc, "content/"+docFile) + + else: + print("Error: Could not find sample project source directory.") + sys.exit(1) + if len(sys.argv) == 1: # Nothing more to do sys.exit(0) From bf35bda9429e69af26491b535c765b6241c8751f Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 9 Aug 2020 20:53:38 +0200 Subject: [PATCH 13/22] Fixed tests --- nw/guimain.py | 19 +++++++++++-------- tests/test_gui.py | 6 +++--- tests/test_project.py | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/nw/guimain.py b/nw/guimain.py index 00778aba..0a67ccfb 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -263,7 +263,7 @@ class GuiMain(QMainWindow): return True - def newProject(self, projPath=None, forceNew=False): + def newProject(self, projData=None, forceNew=False): """Create new project with a few default files and folders. The variable forceNew is used for testing. """ @@ -275,15 +275,18 @@ class GuiMain(QMainWindow): ) return False - projData = {} - if projPath is None: + if projData is None and self.mainConf.showGUI: projData = self.newProjectDialog() - if projData is None: - return False - if "projPath" in projData: - projPath = projData["projPath"] - if projPath is None: + if projData is None: + return False + + projPath = None + if "projPath" in projData: + projPath = projData["projPath"] + + if projPath is None or projData is None: + logger.error("No projData or projPath set") return False if path.isfile(path.join(projPath,self.theProject.projFile)) and not forceNew: diff --git a/tests/test_gui.py b/tests/test_gui.py index 7660497f..d8e6628b 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -27,7 +27,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Create new, save, close project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject(nwTempGUI, True) + assert nwGUI.newProject({"projPath": nwTempGUI}, True) assert nwGUI.saveProject() assert nwGUI.closeProject() @@ -274,7 +274,7 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp): # Create new, save, open project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject(nwTempGUI, True) + assert nwGUI.newProject({"projPath": nwTempGUI}, True) nwGUI.mainConf.backupPath = nwTempGUI projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject) @@ -363,7 +363,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): # Create new, save, open project nwGUI.theProject.projTree.setSeed(42) - assert nwGUI.newProject(nwTempGUI, True) + assert nwGUI.newProject({"projPath": nwTempGUI}, True) assert nwGUI.openDocument("31489056e0916") itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916") diff --git a/tests/test_project.py b/tests/test_project.py index a4ae401e..9baa2c4b 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -26,7 +26,7 @@ def testProjectNew(nwTempProj,nwRef,nwTemp): projFile = path.join(nwTempProj,"nwProject.nwx") refFile = path.join(nwRef,"proj","1_nwProject.nwx") assert theConf.initConfig(nwRef, nwTemp) - assert theProject.newProject() + assert theProject.newProject({"projPath": nwTempProj}) assert theProject.setProjectPath(nwTempProj) assert theProject.saveProject() assert theProject.closeProject() From 8a50590a5d6df4927971abd354b6eb1566d3f8cb Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 10 Aug 2020 21:05:26 +0200 Subject: [PATCH 14/22] Update the new project code and add title page --- nw/core/project.py | 359 +++++++++++++++++++++---------------------- nw/core/tree.py | 16 ++ nw/gui/projtree.py | 2 +- nw/gui/projwizard.py | 1 + nw/guimain.py | 5 +- 5 files changed, 192 insertions(+), 191 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index 3eb1850c..36589f3c 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -171,179 +171,6 @@ class NWProject(): # Project Methods ## - def newProject(self, projData={}): - """Create a new project by populating the project tree with a - few starter items. - """ - popMinimal = True - popCustom = False - popSample = False - if "popMinimal" in projData: - popMinimal = projData["popMinimal"] - if "popCustom" in projData: - popCustom = projData["popCustom"] - if "popSample" in projData: - popSample = projData["popSample"] - - # Check if we're extracting the sample project. - # This is handled differently as it isn't actually a new project. - if popSample: - return self.extractSampleProject(projData) - - # Set the project path - if "projPath" in projData: - self.setProjectPath(projData["projPath"], newProject=True) - else: - logger.error("No project path set for the new project") - return False - - # Project Title and Authors - if "projName" in projData: - self.setProjectName(projData["projName"]) - else: - self.setProjectName("New Project") - if "projTitle" in projData: - self.setBookTitle(projData["projTitle"]) - if "projAuthors" in projData: - self.setBookAuthors(projData["projAuthors"]) - - if popMinimal: - # Creating a minimal project with a few root folders, a - # single chapter folder with a single file. - hNovel = self.newRoot("Novel", nwItemClass.NOVEL) - hChars = self.newRoot("Characters", nwItemClass.CHARACTER) - hWorld = self.newRoot("Plot", nwItemClass.PLOT) - hWorld = self.newRoot("World", nwItemClass.WORLD) - hChapF = self.newFolder("New Chapter", nwItemClass.NOVEL, hNovel) - hScene = self.newFile("New Scene", nwItemClass.NOVEL, hChapF) - - elif popCustom: - # Create a project structure based on selected root folders - # and a number of chapters and scenes selected in the - # wizard's custom page. - - # Create Root Folders - hNovel = self.newRoot("Novel", nwItemClass.NOVEL) - if "addRoots" in projData: - for newRoot in projData["addRoots"]: - if newRoot in nwItemClass: - self.newRoot(nwLabels.CLASS_NAME[newRoot], newRoot) - - # Create Chapters and Scenes - numChapters = 0 - numScenes = 0 - chFolders = False - if "numChapters" in projData: - numChapters = projData["numChapters"] - if "numScenes" in projData: - numScenes = projData["numScenes"] - if "chFolders" in projData: - chFolders = projData["chFolders"] - - # Create Chapters - if numChapters > 0: - for ch in range(numChapters): - chTitle = "Chapter %d" % (ch+1) - hParDir = hNovel - if chFolders: - hParDir = self.newFolder(chTitle, nwItemClass.NOVEL, hNovel) - hChapD = self.newFile(chTitle, nwItemClass.NOVEL, hParDir) - chItem = self.projTree[hChapD] - if hChapD is None: - logger.error("Something went wrong when creating chapter file.") - continue - chItem.setLayout(nwItemLayout.CHAPTER) - - # Create Chapter > Scenes - if numScenes > 0: - for sc in range(numScenes): - scTitle = "Scene %d.%d" % (ch+1, sc+1) - hScene = self.newFile(scTitle, nwItemClass.NOVEL, hParDir) - - # Create Scenes (No Chapters) - elif numScenes > 0: - for sc in range(numScenes): - scTitle = "Scene %d" % (sc+1) - hScene = self.newFile(scTitle, nwItemClass.NOVEL, hNovel) - - else: - # Fallback just in case. - self.newRoot("Novel", nwItemClass.NOVEL) - - # Finalise - self.projOpened = time() - self.setProjectChanged(True) - self.saveProject(autoSave=True) - - return True - - def extractSampleProject(self, projData): - """Make a copy of the sample project. - First, try to copy the content of the sample folder to the new - project path, or if the folder doesn't exist, look for the zip - file in the assets folder. - """ - if "projName" in projData: - projName = projData["projName"] - else: - projName = "Sample Project" - - if "projPath" in projData: - projPath = projData["projPath"] - else: - logger.error("No project path set for the example project") - return False - - srcSample = path.abspath(path.join(self.mainConf.appRoot, "sample")) - pkgSample = path.join(self.mainConf.assetPath, "sample.zip") - - isSuccess = False - if path.isfile(pkgSample): - - self.setProjectPath(projPath, newProject=True) - try: - unpack_archive(pkgSample, projPath) - isSuccess = True - except Exception as e: - self.makeAlert( - ["Failed to create a new example project.", str(e)], nwAlert.ERROR - ) - - elif path.isdir(srcSample): - - self.setProjectPath(projPath, newProject=True) - try: - srcProj = path.join(srcSample, nwFiles.PROJ_FILE) - dstProj = path.join(projPath, nwFiles.PROJ_FILE) - copyfile(srcProj, dstProj) - - srcContent = path.join(srcSample, "content") - dstContent = path.join(projPath, "content") - for srcFile in listdir(srcContent): - srcDoc = path.join(srcContent, srcFile) - dstDoc = path.join(dstContent, srcFile) - copyfile(srcDoc, dstDoc) - - isSuccess = True - - except Exception as e: - self.makeAlert( - ["Failed to create a new example project.", str(e)], nwAlert.ERROR - ) - - else: - self.makeAlert(( - "Failed to create a new example project. " - "Could not find the necessary files." - ), nwAlert.ERROR) - - if isSuccess: - self.clearProject() - self.theParent.openProject(projPath) - self.theParent.rebuildIndex() - - return isSuccess - def clearProject(self): """Clear the data for the current project, and set them to default values. @@ -379,15 +206,15 @@ class NWProject(): self.spellCheck = False self.autoOutline = True self.statusItems = NWStatus() - self.statusItems.addEntry("New", (100,100,100)) - self.statusItems.addEntry("Note", (200, 50, 0)) - self.statusItems.addEntry("Draft", (200,150, 0)) - self.statusItems.addEntry("Finished",( 50,200, 0)) + self.statusItems.addEntry("New", (100, 100, 100)) + self.statusItems.addEntry("Note", (200, 50, 0)) + self.statusItems.addEntry("Draft", (200, 150, 0)) + self.statusItems.addEntry("Finished",( 50, 200, 0)) self.importItems = NWStatus() - self.importItems.addEntry("New", (100,100,100)) - self.importItems.addEntry("Minor", (200, 50, 0)) - self.importItems.addEntry("Major", (200,150, 0)) - self.importItems.addEntry("Main", ( 50,200, 0)) + self.importItems.addEntry("New", (100, 100, 100)) + self.importItems.addEntry("Minor", (200, 50, 0)) + self.importItems.addEntry("Major", (200, 150, 0)) + self.importItems.addEntry("Main", ( 50, 200, 0)) self.lastEdited = None self.lastViewed = None self.lastWCount = 0 @@ -397,6 +224,104 @@ class NWProject(): return + def newProject(self, projData={}): + """Create a new project by populating the project tree with a + few starter items. + """ + popMinimal = projData.get("popMinimal", True) + popCustom = projData.get("popCustom", False) + popSample = projData.get("popSample", False) + + # Check if we're extracting the sample project. This is handled + # differently as it isn't actually a new project, so we forward + # this to another function and return here. + if popSample: + return self.extractSampleProject(projData) + + # Project Settings + projPath = projData.get("projPath", None) + projName = projData.get("projName", "New Project") + projTitle = projData.get("projTitle", "") + projAuthors = projData.get("projAuthors", "") + + if projPath is None: + logger.error("No project path set for the new project") + return False + + if not self.setProjectPath(projPath, newProject=True): + return False + + self.setProjectName(projName) + self.setBookTitle(projTitle) + self.setBookAuthors(projAuthors) + + if popMinimal: + # Creating a minimal project with a few root folders and a + # single chapter folder with a single file. + nHandle = self.newRoot("Novel", nwItemClass.NOVEL) + xHandle = self.newRoot("Plot", nwItemClass.PLOT) + xHandle = self.newRoot("Characters", nwItemClass.CHARACTER) + xHandle = self.newRoot("World", nwItemClass.WORLD) + tHandle = self.newFile("Title Page", nwItemClass.NOVEL, nHandle) + cHandle = self.newFolder("New Chapter", nwItemClass.NOVEL, nHandle) + fHandle = self.newFile("New Chapter", nwItemClass.NOVEL, cHandle) + XHandle = self.newFile("New Scene", nwItemClass.NOVEL, cHandle) + self.projTree.setFileItemLayout(tHandle, nwItemLayout.TITLE) + self.projTree.setFileItemLayout(fHandle, nwItemLayout.CHAPTER) + + elif popCustom: + # Create a project structure based on selected root folders + # and a number of chapters and scenes selected in the + # wizard's custom page. + + # Create root folders + nHandle = self.newRoot("Novel", nwItemClass.NOVEL) + for newRoot in projData.get("addRoots", []): + if newRoot in nwItemClass: + self.newRoot(nwLabels.CLASS_NAME[newRoot], newRoot) + + # Create a title page + tHandle = self.newFile("Title Page", nwItemClass.NOVEL, nHandle) + self.projTree.setFileItemLayout(tHandle, nwItemLayout.TITLE) + + # Create chapters and scenes + numChapters = projData.get("numChapters", 0) + numScenes = projData.get("numScenes", 0) + chFolders = projData.get("chFolders", False) + + # Create chapters + if numChapters > 0: + for ch in range(numChapters): + chTitle = "Chapter %d" % (ch+1) + pHandle = nHandle + if chFolders: + pHandle = self.newFolder(chTitle, nwItemClass.NOVEL, nHandle) + cHandle = self.newFile(chTitle, nwItemClass.NOVEL, pHandle) + self.projTree.setFileItemLayout(cHandle, nwItemLayout.CHAPTER) + + # Create chapter scenes + if numScenes > 0: + for sc in range(numScenes): + scTitle = "Scene %d.%d" % (ch+1, sc+1) + sHandle = self.newFile(scTitle, nwItemClass.NOVEL, pHandle) + + # Create scenes (no chapters) + elif numScenes > 0: + for sc in range(numScenes): + scTitle = "Scene %d" % (sc+1) + sHandle = self.newFile(scTitle, nwItemClass.NOVEL, nHandle) + + else: + # Fallback just in case. We shouldn't reach here. + self.newRoot("Novel", nwItemClass.NOVEL) + + # Finalise + self.projOpened = time() + self.setProjectChanged(True) + self.saveProject(autoSave=True) + + return True + def openProject(self, fileName, overrideLock=False): """Open the project file provided, or if doesn't exist, assume it is a folder, and look for the file within it. If successful, @@ -716,9 +641,9 @@ class NWProject(): if len(aKey) > 0: self._packProjectValue(xTitleFmt, aKey, aValue) - xStatus = etree.SubElement(xSettings,"status") + xStatus = etree.SubElement(xSettings, "status") self.statusItems.packEntries(xStatus) - xStatus = etree.SubElement(xSettings,"importance") + xStatus = etree.SubElement(xSettings, "importance") self.importItems.packEntries(xStatus) # Save Tree Content @@ -733,8 +658,8 @@ class NWProject(): with open(tempFile, mode="wb") as outFile: outFile.write(etree.tostring( nwXML, - pretty_print = True, - encoding = "utf-8", + pretty_print = True, + encoding = "utf-8", xml_declaration = True )) except Exception as e: @@ -794,7 +719,7 @@ class NWProject(): return True ## - # Backup Project + # Zip/Unzip Project ## def zipIt(self, doNotify): @@ -870,6 +795,68 @@ class NWProject(): return True + def extractSampleProject(self, projData): + """Make a copy of the sample project. + First, try to copy the content of the sample folder to the new + project path, or if the folder doesn't exist, look for the zip + file in the assets folder. + """ + projName = projData.get("projName", "Sample Project") + projPath = projData.get("projPath", None) + if projPath is None: + logger.error("No project path set for the example project") + return False + + srcSample = path.abspath(path.join(self.mainConf.appRoot, "sample")) + pkgSample = path.join(self.mainConf.assetPath, "sample.zip") + + isSuccess = False + if path.isfile(pkgSample): + + self.setProjectPath(projPath, newProject=True) + try: + unpack_archive(pkgSample, projPath) + isSuccess = True + except Exception as e: + self.makeAlert( + ["Failed to create a new example project.", str(e)], nwAlert.ERROR + ) + + elif path.isdir(srcSample): + + self.setProjectPath(projPath, newProject=True) + try: + srcProj = path.join(srcSample, nwFiles.PROJ_FILE) + dstProj = path.join(projPath, nwFiles.PROJ_FILE) + copyfile(srcProj, dstProj) + + srcContent = path.join(srcSample, "content") + dstContent = path.join(projPath, "content") + for srcFile in listdir(srcContent): + srcDoc = path.join(srcContent, srcFile) + dstDoc = path.join(dstContent, srcFile) + copyfile(srcDoc, dstDoc) + + isSuccess = True + + except Exception as e: + self.makeAlert( + ["Failed to create a new example project.", str(e)], nwAlert.ERROR + ) + + else: + self.makeAlert(( + "Failed to create a new example project. " + "Could not find the necessary files." + ), nwAlert.ERROR) + + if isSuccess: + self.clearProject() + self.theParent.openProject(projPath) + self.theParent.rebuildIndex() + + return isSuccess + ## # Setters ## diff --git a/nw/core/tree.py b/nw/core/tree.py index e786a727..bafb5306 100644 --- a/nw/core/tree.py +++ b/nw/core/tree.py @@ -314,6 +314,22 @@ class NWTree(): self._handleSeed = theSeed return + def setFileItemLayout(self, tHandle, itemLayout): + """Set the nwItemLayout for a specific file. + """ + tItem = self.__getitem__(tHandle) + if tItem is None: + return False + if tItem.itemType != nwItemType.FILE: + logger.error("Item '%s' is not a file" % tHandle) + return False + if not isinstance(itemLayout, nwItemLayout): + return False + + tItem.setLayout(itemLayout) + + return True + ## # Getters ## diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index 124e0a59..ce7bf499 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -192,7 +192,7 @@ class GuiProjectTree(QTreeWidget): nHandle = pHandle pHandle = pItem.parHandle - # If we again has no home, give up + # If we again have no home, give up if pHandle is None: self.makeAlert( "Did not find anywhere to add the file or folder!", nwAlert.ERROR diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index 7460dbe2..40f014ec 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -324,6 +324,7 @@ class ProjWizardCustomPage(QWizardPage): self.addPlot.setChecked(True) self.addChar.setChecked(True) + self.addWorld.setChecked(True) self.rootForm.addWidget(self.lblPlot, 0, 0) self.rootForm.addWidget(self.lblChar, 1, 0) diff --git a/nw/guimain.py b/nw/guimain.py index 0a67ccfb..12a17c30 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -281,10 +281,7 @@ class GuiMain(QMainWindow): if projData is None: return False - projPath = None - if "projPath" in projData: - projPath = projData["projPath"] - + projPath = projData.get("projPath", None) if projPath is None or projData is None: logger.error("No projData or projPath set") return False From 7d803a2eb7c1af58f57b84d411eb47694d477295 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 10 Aug 2020 21:25:45 +0200 Subject: [PATCH 15/22] Updated GUI tests --- tests/reference/gui/0_nwProject.nwx | 38 +++++++++--- tests/reference/gui/1_031b4af5197ec.nwd | 6 ++ tests/reference/gui/1_0e17daca5f3e1.nwd | 25 ++++++-- tests/reference/gui/1_1a6562590ef19.nwd | 8 +-- tests/reference/gui/1_31489056e0916.nwd | 23 ------- tests/reference/gui/1_41cfc0d1f2d12.nwd | 6 ++ tests/reference/gui/1_98010bd9270f9.nwd | 6 -- tests/reference/gui/1_nwProject.nwx | 80 ++++++++++++++++--------- tests/reference/gui/2_nwProject.nwx | 40 ++++++++++--- tests/reference/gui/3_nwProject.nwx | 40 ++++++++++--- tests/test_gui.py | 41 +++++++------ 11 files changed, 206 insertions(+), 107 deletions(-) create mode 100644 tests/reference/gui/1_031b4af5197ec.nwd delete mode 100644 tests/reference/gui/1_31489056e0916.nwd create mode 100644 tests/reference/gui/1_41cfc0d1f2d12.nwd delete mode 100644 tests/reference/gui/1_98010bd9270f9.nwd diff --git a/tests/reference/gui/0_nwProject.nwx b/tests/reference/gui/0_nwProject.nwx index d1a794e7..9ed7722a 100644 --- a/tests/reference/gui/0_nwProject.nwx +++ b/tests/reference/gui/0_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -37,7 +37,7 @@ Main - + Novel ROOT @@ -46,13 +46,37 @@ False + Title Page + FILE + NOVEL + New + True + TITLE + 0 + 0 + 0 + 0 + + New Chapter FOLDER NOVEL New False - + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + New Scene FILE NOVEL @@ -65,16 +89,16 @@ 0 - Characters + Plot ROOT - CHARACTER + PLOT New False - Plot + Characters ROOT - PLOT + CHARACTER New False diff --git a/tests/reference/gui/1_031b4af5197ec.nwd b/tests/reference/gui/1_031b4af5197ec.nwd new file mode 100644 index 00000000..9a330c9c --- /dev/null +++ b/tests/reference/gui/1_031b4af5197ec.nwd @@ -0,0 +1,6 @@ +%%~ 031b4af5197ec:44cb730c42048:PLOT:NOTE:New File +# Main Plot + +@tag: MainPlot + +This is a file detailing the main plot. diff --git a/tests/reference/gui/1_0e17daca5f3e1.nwd b/tests/reference/gui/1_0e17daca5f3e1.nwd index ecfbf8ff..f7723099 100644 --- a/tests/reference/gui/1_0e17daca5f3e1.nwd +++ b/tests/reference/gui/1_0e17daca5f3e1.nwd @@ -1,6 +1,23 @@ -%%~ 0e17daca5f3e1:71ee45a3c0db9:PLOT:NOTE:New File -# Main Plot +%%~ 0e17daca5f3e1:31489056e0916:73475cb40a568:NOVEL:SCENE:New Scene +# Novel -@tag: MainPlot +## Chapter + +@pov: Jane +@plot: MainPlot + +### Scene + +% How about a comment? +@pov: Jane +@plot: MainPlot +@location: Home + +#### Some Section + +@char: Jane + +This is a paragraph of dummy text. + +This is another paragraph of much longer dummy text. It is in fact very very dumb dummy text! We can also try replacing “quotes”, even single’s quotes are replaced. We can hyphen-ate, make dashes – and even longer dashes — if we want. Ellipsis? Not a problem either … -This is a file detailing the main plot. diff --git a/tests/reference/gui/1_1a6562590ef19.nwd b/tests/reference/gui/1_1a6562590ef19.nwd index 7c6d19a7..de40e292 100644 --- a/tests/reference/gui/1_1a6562590ef19.nwd +++ b/tests/reference/gui/1_1a6562590ef19.nwd @@ -1,6 +1,6 @@ -%%~ 1a6562590ef19:811786ad1ae74:WORLD:NOTE:New File -# Main Location +%%~ 1a6562590ef19:71ee45a3c0db9:CHARACTER:NOTE:New File +# Jane Doe -@tag: Home +@tag: Jane -This is a file describing Jane’s home. +This is a file about Jane. diff --git a/tests/reference/gui/1_31489056e0916.nwd b/tests/reference/gui/1_31489056e0916.nwd deleted file mode 100644 index 0a238e45..00000000 --- a/tests/reference/gui/1_31489056e0916.nwd +++ /dev/null @@ -1,23 +0,0 @@ -%%~ 31489056e0916:25fc0e7096fc6:73475cb40a568:NOVEL:SCENE:New Scene -# Novel - -## Chapter - -@pov: Jane -@plot: MainPlot - -### Scene - -% How about a comment? -@pov: Jane -@plot: MainPlot -@location: Home - -#### Some Section - -@char: Jane - -This is a paragraph of dummy text. - -This is another paragraph of much longer dummy text. It is in fact very very dumb dummy text! We can also try replacing “quotes”, even single’s quotes are replaced. We can hyphen-ate, make dashes – and even longer dashes — if we want. Ellipsis? Not a problem either … - diff --git a/tests/reference/gui/1_41cfc0d1f2d12.nwd b/tests/reference/gui/1_41cfc0d1f2d12.nwd new file mode 100644 index 00000000..e80cbd1b --- /dev/null +++ b/tests/reference/gui/1_41cfc0d1f2d12.nwd @@ -0,0 +1,6 @@ +%%~ 41cfc0d1f2d12:811786ad1ae74:WORLD:NOTE:New File +# Main Location + +@tag: Home + +This is a file describing Jane’s home. diff --git a/tests/reference/gui/1_98010bd9270f9.nwd b/tests/reference/gui/1_98010bd9270f9.nwd deleted file mode 100644 index 632f53cd..00000000 --- a/tests/reference/gui/1_98010bd9270f9.nwd +++ /dev/null @@ -1,6 +0,0 @@ -%%~ 98010bd9270f9:44cb730c42048:CHARACTER:NOTE:New File -# Jane Doe - -@tag: Jane - -This is a file about Jane. diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/gui/1_nwProject.nwx index e8a705b1..3dad289d 100644 --- a/tests/reference/gui/1_nwProject.nwx +++ b/tests/reference/gui/1_nwProject.nwx @@ -1,17 +1,17 @@ - + New Project - 5 + 4 1 - 11 + 13 True True True - 31489056e0916 + 0e17daca5f3e1 None 86 59 @@ -37,7 +37,7 @@ Main - + Novel ROOT @@ -46,13 +46,37 @@ True + Title Page + FILE + NOVEL + New + True + TITLE + 0 + 0 + 0 + 0 + + New Chapter FOLDER NOVEL New True - + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + New Scene FILE NOVEL @@ -65,32 +89,13 @@ 465 - Characters - ROOT - CHARACTER - New - True - - - New File - FILE - CHARACTER - New - True - NOTE - 34 - 8 - 1 - 51 - - Plot ROOT PLOT New True - + New File FILE PLOT @@ -102,6 +107,25 @@ 1 69 + + Characters + ROOT + CHARACTER + New + True + + + New File + FILE + CHARACTER + New + True + NOTE + 34 + 8 + 1 + 51 + World ROOT @@ -109,7 +133,7 @@ New True - + New File FILE WORLD @@ -121,7 +145,7 @@ 1 68 - + Trash TRASH TRASH diff --git a/tests/reference/gui/2_nwProject.nwx b/tests/reference/gui/2_nwProject.nwx index 0815f9ad..dbeecf74 100644 --- a/tests/reference/gui/2_nwProject.nwx +++ b/tests/reference/gui/2_nwProject.nwx @@ -1,11 +1,11 @@ - + Project Name Project Title Jane Doe John Doh - 2 + 1 1 1 @@ -41,7 +41,7 @@ Main - + Novel ROOT @@ -50,13 +50,37 @@ False + Title Page + FILE + NOVEL + New + True + TITLE + 0 + 0 + 0 + 0 + + New Chapter FOLDER NOVEL New False - + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + New Scene FILE NOVEL @@ -69,16 +93,16 @@ 0 - Characters + Plot ROOT - CHARACTER + PLOT New False - Plot + Characters ROOT - PLOT + CHARACTER New False diff --git a/tests/reference/gui/3_nwProject.nwx b/tests/reference/gui/3_nwProject.nwx index 8eeb4dca..92369f44 100644 --- a/tests/reference/gui/3_nwProject.nwx +++ b/tests/reference/gui/3_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -11,7 +11,7 @@ True False True - 31489056e0916 + 0e17daca5f3e1 None 59 59 @@ -37,7 +37,7 @@ Main - + Novel ROOT @@ -46,13 +46,37 @@ False + Title Page + FILE + NOVEL + New + True + TITLE + 0 + 0 + 0 + 0 + + New Chapter FOLDER NOVEL New False - + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + Just a Page FILE NOVEL @@ -65,16 +89,16 @@ 0 - Characters + Plot ROOT - CHARACTER + PLOT New False - Plot + Characters ROOT - PLOT + CHARACTER New False diff --git a/tests/test_gui.py b/tests/test_gui.py index d8e6628b..e282504a 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -55,8 +55,8 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): qtbot.wait(stepDelay) # Check that we loaded the data - assert len(nwGUI.theProject.projTree) == 6 - assert len(nwGUI.theProject.projTree._treeOrder) == 6 + assert len(nwGUI.theProject.projTree) == 8 + assert len(nwGUI.theProject.projTree._treeOrder) == 8 assert len(nwGUI.theProject.projTree._treeRoots) == 4 assert nwGUI.theProject.projTree.trashRoot() is None assert nwGUI.theProject.projPath == nwTempGUI @@ -71,6 +71,8 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): assert nwGUI.treeView._getTreeItem("73475cb40a568") is not None assert nwGUI.treeView._getTreeItem("25fc0e7096fc6") is not None assert nwGUI.treeView._getTreeItem("31489056e0916") is not None + assert nwGUI.treeView._getTreeItem("98010bd9270f9") is not None + assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None assert nwGUI.treeView._getTreeItem("44cb730c42048") is not None assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") is not None assert nwGUI.treeView._getTreeItem("811786ad1ae74") is not None @@ -81,7 +83,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Add a Character File nwGUI.setFocus(1) nwGUI.treeView.clearSelection() - nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True) + nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) assert nwGUI.openSelectedItem() @@ -102,7 +104,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Add a Plot File nwGUI.setFocus(1) nwGUI.treeView.clearSelection() - nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True) + nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True) nwGUI.treeView.newTreeItem(nwItemType.FILE, None) assert nwGUI.openSelectedItem() @@ -150,8 +152,8 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): nwGUI.setFocus(1) nwGUI.treeView.clearSelection() nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True) - nwGUI.treeView._getTreeItem("25fc0e7096fc6").setExpanded(True) - nwGUI.treeView._getTreeItem("31489056e0916").setSelected(True) + nwGUI.treeView._getTreeItem("31489056e0916").setExpanded(True) + nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True) assert nwGUI.openSelectedItem() # Type something into the document @@ -233,8 +235,8 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Open and view the edited document nwGUI.setFocus(3) - assert nwGUI.openDocument("31489056e0916") - assert nwGUI.viewDocument("31489056e0916") + assert nwGUI.openDocument("0e17daca5f3e1") + assert nwGUI.viewDocument("0e17daca5f3e1") qtbot.wait(stepDelay) assert nwGUI.saveProject() assert nwGUI.closeDocViewer() @@ -243,23 +245,24 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Check a Quick Create and Delete assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None) newHandle = nwGUI.treeView.getSelectedHandle() - assert nwGUI.theProject.projTree["031b4af5197ec"] is not None + assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None assert nwGUI.treeView.deleteItem() assert nwGUI.treeView.setSelectedHandle(newHandle) assert nwGUI.treeView.deleteItem() + assert nwGUI.theProject.projTree["2fca346db6561"] is not None # Trash assert nwGUI.saveProject() # Check the files refFile = path.join(nwTempGUI, "nwProject.nwx") assert cmpFiles(refFile, path.join(nwRef, "gui", "1_nwProject.nwx"), [2, 6, 7, 8]) - refFile = path.join(nwTempGUI, "content", "0e17daca5f3e1.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "1_0e17daca5f3e1.nwd")) - refFile = path.join(nwTempGUI, "content", "98010bd9270f9.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "1_98010bd9270f9.nwd")) - refFile = path.join(nwTempGUI, "content", "31489056e0916.nwd") - assert cmpFiles(refFile, path.join(nwRef, "gui", "1_31489056e0916.nwd")) + refFile = path.join(nwTempGUI, "content", "031b4af5197ec.nwd") + assert cmpFiles(refFile, path.join(nwRef, "gui", "1_031b4af5197ec.nwd")) refFile = path.join(nwTempGUI, "content", "1a6562590ef19.nwd") assert cmpFiles(refFile, path.join(nwRef, "gui", "1_1a6562590ef19.nwd")) + refFile = path.join(nwTempGUI, "content", "0e17daca5f3e1.nwd") + assert cmpFiles(refFile, path.join(nwRef, "gui", "1_0e17daca5f3e1.nwd")) + refFile = path.join(nwTempGUI, "content", "41cfc0d1f2d12.nwd") + assert cmpFiles(refFile, path.join(nwRef, "gui", "1_41cfc0d1f2d12.nwd")) nwGUI.closeMain() # qtbot.stopForInteraction() @@ -364,9 +367,9 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): # Create new, save, open project nwGUI.theProject.projTree.setSeed(42) assert nwGUI.newProject({"projPath": nwTempGUI}, True) - assert nwGUI.openDocument("31489056e0916") + assert nwGUI.openDocument("0e17daca5f3e1") - itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916") + itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1") qtbot.addWidget(itemEdit) assert itemEdit.editName.text() == "New Scene" @@ -383,7 +386,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): assert not itemEdit.editExport.isChecked() itemEdit._doSave() - itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916") + itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1") qtbot.addWidget(itemEdit) assert itemEdit.editName.text() == "Just a Page" assert itemEdit.editStatus.currentData() == "Note" @@ -391,7 +394,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): itemEdit._doClose() # Check that the header is updated - nwGUI.docEditor.updateDocInfo("31489056e0916") + nwGUI.docEditor.updateDocInfo("0e17daca5f3e1") assert nwGUI.docEditor.docHeader.theTitle.text() == "Novel › New Chapter › Just a Page" assert not nwGUI.docEditor.setCursorLine("where?") assert nwGUI.docEditor.setCursorLine(2) From 7bc62db5e369064a135b7d2dc00c6b25cc6499e4 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 10 Aug 2020 21:33:53 +0200 Subject: [PATCH 16/22] Updated project tests --- tests/reference/proj/1_nwProject.nwx | 40 ++++++++++++++++----- tests/reference/proj/2_nwProject.nwx | 48 ++++++++++++++++++------- tests/reference/proj/3_nwProject.nwx | 52 ++++++++++++++++++++-------- tests/test_project.py | 18 +++++----- 4 files changed, 115 insertions(+), 43 deletions(-) diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/proj/1_nwProject.nwx index 12697dbe..0c891d8b 100644 --- a/tests/reference/proj/1_nwProject.nwx +++ b/tests/reference/proj/1_nwProject.nwx @@ -1,10 +1,10 @@ - + New Project 1 - 0 + 1 0 @@ -37,7 +37,7 @@ Main - + Novel ROOT @@ -46,16 +46,16 @@ False - Characters + Plot ROOT - CHARACTER + PLOT New False - Plot + Characters ROOT - PLOT + CHARACTER New False @@ -67,13 +67,37 @@ False + Title Page + FILE + NOVEL + New + True + TITLE + 0 + 0 + 0 + 0 + + New Chapter FOLDER NOVEL New False - + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + New Scene FILE NOVEL diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/proj/2_nwProject.nwx index ed6bbbe4..fd73b637 100644 --- a/tests/reference/proj/2_nwProject.nwx +++ b/tests/reference/proj/2_nwProject.nwx @@ -1,10 +1,10 @@ - + New Project 4 - 0 + 1 0 @@ -37,7 +37,7 @@ Main - + Novel ROOT @@ -46,16 +46,16 @@ False - Characters + Plot ROOT - CHARACTER + PLOT New False - Plot + Characters ROOT - PLOT + CHARACTER New False @@ -67,13 +67,37 @@ False + Title Page + FILE + NOVEL + New + True + TITLE + 0 + 0 + 0 + 0 + + New Chapter FOLDER NOVEL New False - + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + New Scene FILE NOVEL @@ -85,28 +109,28 @@ 0 0 - + Timeline ROOT TIMELINE New False - + Object ROOT OBJECT New False - + Custom1 ROOT CUSTOM New False - + Custom2 ROOT CUSTOM diff --git a/tests/reference/proj/3_nwProject.nwx b/tests/reference/proj/3_nwProject.nwx index 58bef95d..0c396343 100644 --- a/tests/reference/proj/3_nwProject.nwx +++ b/tests/reference/proj/3_nwProject.nwx @@ -1,10 +1,10 @@ - + New Project 5 - 0 + 1 0 @@ -37,7 +37,7 @@ Main - + Novel ROOT @@ -46,16 +46,16 @@ False - Characters + Plot ROOT - CHARACTER + PLOT New False - Plot + Characters ROOT - PLOT + CHARACTER New False @@ -67,13 +67,37 @@ False + Title Page + FILE + NOVEL + New + True + TITLE + 0 + 0 + 0 + 0 + + New Chapter FOLDER NOVEL New False - + + New Chapter + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + New Scene FILE NOVEL @@ -85,35 +109,35 @@ 0 0 - + Timeline ROOT TIMELINE New False - + Object ROOT OBJECT New False - + Custom1 ROOT CUSTOM New False - + Custom2 ROOT CUSTOM New False - + Hello FILE NOVEL @@ -125,7 +149,7 @@ 0 0 - + Jane FILE CHARACTER diff --git a/tests/test_project.py b/tests/test_project.py index 9baa2c4b..8f35e35f 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -82,7 +82,7 @@ def testProjectNewFile(nwTempProj,nwRef): refFile = path.join(nwRef,"proj","3_nwProject.nwx") assert theProject.openProject(projFile) assert isinstance(theProject.newFile("Hello", nwItemClass.NOVEL, "73475cb40a568"), str) - assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "44cb730c42048"), str) + assert isinstance(theProject.newFile("Jane", nwItemClass.CHARACTER, "71ee45a3c0db9"), str) assert theProject.projChanged assert theProject.saveProject() assert theProject.closeProject() @@ -142,9 +142,9 @@ def testIndexCheckThese(nwTempProj): assert theProject.openProject(projFile) theIndex = NWIndex(theProject, theMain) - nHandle = "41cfc0d1f2d12" + nHandle = "0e17daca5f3e1" nItem = theProject.projTree[nHandle] - cHandle = "2858dcd1057d3" + cHandle = "02d20bbd7e394" cItem = theProject.projTree[cHandle] assert theIndex.scanText(cHandle, ( @@ -155,7 +155,7 @@ def testIndexCheckThese(nwTempProj): "# Hello World!\n" "@pov: Jane" )) - assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER', 'T000001']}" + assert str(theIndex.tagIndex) == "{'Jane': [2, '02d20bbd7e394', 'CHARACTER', 'T000001']}" assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!" assert str(theIndex.checkThese(["@tag", "Jane"], cItem)) == "[True, True]" @@ -175,9 +175,9 @@ def testIndexMeta(nwTempProj): assert theProject.openProject(projFile) theIndex = NWIndex(theProject, theMain) - nHandle = "41cfc0d1f2d12" + nHandle = "0e17daca5f3e1" nItem = theProject.projTree[nHandle] - cHandle = "2858dcd1057d3" + cHandle = "02d20bbd7e394" cItem = theProject.projTree[cHandle] assert theIndex.scanText(cHandle, ( @@ -195,11 +195,11 @@ def testIndexMeta(nwTempProj): "\n" "Well, not really.\n" )) - assert str(theIndex.tagIndex) == "{'Jane': [2, '2858dcd1057d3', 'CHARACTER', 'T000001']}" + assert str(theIndex.tagIndex) == "{'Jane': [2, '02d20bbd7e394', 'CHARACTER', 'T000001']}" assert theIndex.novelIndex[nHandle]["T000001"]["title"] == "Hello World!" # The novel structure should contain the pointer to the novel file header - assert str(theIndex.getNovelStructure()) == "['41cfc0d1f2d12:T000001']" + assert str(theIndex.getNovelStructure()) == "['0e17daca5f3e1:T000001']" # The novel file should have the correct counts cC, wC, pC = theIndex.getCounts(nHandle) @@ -214,6 +214,6 @@ def testIndexMeta(nwTempProj): # The character file should have a record of the reference from the novel file theRefs = theIndex.getBackReferenceList(cHandle) - assert str(theRefs) == "{'41cfc0d1f2d12': 'T000001'}" + assert str(theRefs) == "{'0e17daca5f3e1': 'T000001'}" assert theProject.closeProject() From 72e4656a57d4bfbd9f7e1a3cc20ffa7b57f02201 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 10 Aug 2020 22:16:46 +0200 Subject: [PATCH 17/22] Have the new project function also set titles in files added --- nw/core/project.py | 40 ++++++++++++++++++++++++++--- nw/guimain.py | 5 ++-- tests/nwdummy.py | 11 ++++++++ tests/reference/gui/0_nwProject.nwx | 18 ++++++------- tests/reference/gui/1_nwProject.nwx | 18 ++++++------- tests/reference/gui/2_nwProject.nwx | 22 ++++++++-------- tests/reference/gui/3_nwProject.nwx | 20 +++++++-------- tests/test_gui.py | 30 ++++++++++++---------- 8 files changed, 106 insertions(+), 58 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index 36589f3c..3943f50c 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -255,6 +255,8 @@ class NWProject(): self.setBookTitle(projTitle) self.setBookAuthors(projAuthors) + aDoc = NWDoc(self, self.theParent) + if popMinimal: # Creating a minimal project with a few root folders and a # single chapter folder with a single file. @@ -263,11 +265,24 @@ class NWProject(): xHandle = self.newRoot("Characters", nwItemClass.CHARACTER) xHandle = self.newRoot("World", nwItemClass.WORLD) tHandle = self.newFile("Title Page", nwItemClass.NOVEL, nHandle) - cHandle = self.newFolder("New Chapter", nwItemClass.NOVEL, nHandle) - fHandle = self.newFile("New Chapter", nwItemClass.NOVEL, cHandle) - XHandle = self.newFile("New Scene", nwItemClass.NOVEL, cHandle) + dHandle = self.newFolder("New Chapter", nwItemClass.NOVEL, nHandle) + cHandle = self.newFile("New Chapter", nwItemClass.NOVEL, dHandle) + sHandle = self.newFile("New Scene", nwItemClass.NOVEL, dHandle) + self.projTree.setFileItemLayout(tHandle, nwItemLayout.TITLE) - self.projTree.setFileItemLayout(fHandle, nwItemLayout.CHAPTER) + self.projTree.setFileItemLayout(cHandle, nwItemLayout.CHAPTER) + + aDoc.openDocument(tHandle, showStatus=False) + aDoc.saveDocument("# %s\n\n" % projName) + aDoc.clearDocument() + + aDoc.openDocument(cHandle, showStatus=False) + aDoc.saveDocument("## New Chapter\n\n") + aDoc.clearDocument() + + aDoc.openDocument(sHandle, showStatus=False) + aDoc.saveDocument("### New Scene\n\n") + aDoc.clearDocument() elif popCustom: # Create a project structure based on selected root folders @@ -284,6 +299,10 @@ class NWProject(): tHandle = self.newFile("Title Page", nwItemClass.NOVEL, nHandle) self.projTree.setFileItemLayout(tHandle, nwItemLayout.TITLE) + aDoc.openDocument(tHandle, showStatus=False) + aDoc.saveDocument("# %s\n\n" % projName) + aDoc.clearDocument() + # Create chapters and scenes numChapters = projData.get("numChapters", 0) numScenes = projData.get("numScenes", 0) @@ -296,21 +315,34 @@ class NWProject(): pHandle = nHandle if chFolders: pHandle = self.newFolder(chTitle, nwItemClass.NOVEL, nHandle) + cHandle = self.newFile(chTitle, nwItemClass.NOVEL, pHandle) self.projTree.setFileItemLayout(cHandle, nwItemLayout.CHAPTER) + aDoc.openDocument(cHandle, showStatus=False) + aDoc.saveDocument("## %s\n\n" % chTitle) + aDoc.clearDocument() + # Create chapter scenes if numScenes > 0: for sc in range(numScenes): scTitle = "Scene %d.%d" % (ch+1, sc+1) sHandle = self.newFile(scTitle, nwItemClass.NOVEL, pHandle) + aDoc.openDocument(sHandle, showStatus=False) + aDoc.saveDocument("### %s\n\n" % scTitle) + aDoc.clearDocument() + # Create scenes (no chapters) elif numScenes > 0: for sc in range(numScenes): scTitle = "Scene %d" % (sc+1) sHandle = self.newFile(scTitle, nwItemClass.NOVEL, nHandle) + aDoc.openDocument(sHandle, showStatus=False) + aDoc.saveDocument("### %s\n\n" % scTitle) + aDoc.clearDocument() + else: # Fallback just in case. We shouldn't reach here. self.newRoot("Novel", nwItemClass.NOVEL) diff --git a/nw/guimain.py b/nw/guimain.py index 12a17c30..175b29c1 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -300,6 +300,7 @@ class GuiMain(QMainWindow): self.saveProject() self.hasProject = True self.statusBar.setRefTime(self.theProject.projOpened) + self.rebuildIndex(beQuiet=True) else: self.theProject.clearProject() return False @@ -700,7 +701,7 @@ class GuiMain(QMainWindow): self.treeView.buildTree() return - def rebuildIndex(self): + def rebuildIndex(self, beQuiet=False): """Rebuild the entire index. """ if not self.hasProject: @@ -743,7 +744,7 @@ class GuiMain(QMainWindow): qApp.restoreOverrideCursor() - if self.mainConf.showGUI: + if self.mainConf.showGUI and not beQuiet: self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO) return True diff --git a/tests/nwdummy.py b/tests/nwdummy.py index 2c54a92a..880a54eb 100644 --- a/tests/nwdummy.py +++ b/tests/nwdummy.py @@ -8,6 +8,7 @@ class DummyMain(): def __init__(self): self.mainConf = None + self.statusBar = StatusBar() return def makeAlert(self, theMessage, theLevel): @@ -33,3 +34,13 @@ class DummyMain(): return # END Class GuiMain + +class StatusBar(): + + def __init__(self): + return + + def setStatus(self, theText): + return + +# END Class StatusBar diff --git a/tests/reference/gui/0_nwProject.nwx b/tests/reference/gui/0_nwProject.nwx index 9ed7722a..4bd15d6f 100644 --- a/tests/reference/gui/0_nwProject.nwx +++ b/tests/reference/gui/0_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -13,8 +13,8 @@ True None None - 0 - 0 + 6 + 6 0 @@ -52,8 +52,8 @@ New True TITLE - 0 - 0 + 11 + 2 0 0 @@ -71,8 +71,8 @@ New True CHAPTER - 0 - 0 + 11 + 2 0 0 @@ -83,8 +83,8 @@ New True SCENE - 0 - 0 + 9 + 2 0 0 diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/gui/1_nwProject.nwx index 3dad289d..43dcf523 100644 --- a/tests/reference/gui/1_nwProject.nwx +++ b/tests/reference/gui/1_nwProject.nwx @@ -1,11 +1,11 @@ - + New Project - 4 + 5 1 - 13 + 14 True @@ -13,8 +13,8 @@ True 0e17daca5f3e1 None - 86 - 59 + 90 + 63 27 @@ -52,8 +52,8 @@ New True TITLE - 0 - 0 + 11 + 2 0 0 @@ -71,8 +71,8 @@ New True CHAPTER - 0 - 0 + 11 + 2 0 0 diff --git a/tests/reference/gui/2_nwProject.nwx b/tests/reference/gui/2_nwProject.nwx index dbeecf74..1e3a2ca3 100644 --- a/tests/reference/gui/2_nwProject.nwx +++ b/tests/reference/gui/2_nwProject.nwx @@ -1,13 +1,13 @@ - + Project Name Project Title Jane Doe John Doh - 1 + 2 1 - 1 + 0 True @@ -15,8 +15,8 @@ True None None - 0 - 0 + 6 + 6 0 With This Stuff @@ -56,8 +56,8 @@ New True TITLE - 0 - 0 + 11 + 2 0 0 @@ -75,8 +75,8 @@ New True CHAPTER - 0 - 0 + 11 + 2 0 0 @@ -87,8 +87,8 @@ New True SCENE - 0 - 0 + 9 + 2 0 0 diff --git a/tests/reference/gui/3_nwProject.nwx b/tests/reference/gui/3_nwProject.nwx index 92369f44..11be0c01 100644 --- a/tests/reference/gui/3_nwProject.nwx +++ b/tests/reference/gui/3_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project @@ -13,8 +13,8 @@ True 0e17daca5f3e1 None - 59 - 59 + 6 + 6 0 @@ -52,8 +52,8 @@ New True TITLE - 0 - 0 + 11 + 2 0 0 @@ -71,8 +71,8 @@ New True CHAPTER - 0 - 0 + 11 + 2 0 0 @@ -83,9 +83,9 @@ Note False PAGE - 331 - 59 - 2 + 9 + 2 + 0 0 diff --git a/tests/test_gui.py b/tests/test_gui.py index e282504a..821d96c1 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -14,8 +14,8 @@ from nw.gui import ( ) from nw.constants import * -keyDelay = 5 -stepDelay = 50 +keyDelay = 2 +stepDelay = 20 @pytest.mark.gui def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): @@ -89,6 +89,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Type something into the document nwGUI.setFocus(2) + qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) for c in "# Jane Doe": qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay) qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) @@ -110,6 +111,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Type something into the document nwGUI.setFocus(2) + qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) for c in "# Main Plot": qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay) qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) @@ -136,6 +138,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Type something into the document nwGUI.setFocus(2) + qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) for c in "# Main Location": qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay) qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) @@ -158,6 +161,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp): # Type something into the document nwGUI.setFocus(2) + qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay) for c in "# Novel": qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay) qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay) @@ -399,7 +403,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp): assert not nwGUI.docEditor.setCursorLine("where?") assert nwGUI.docEditor.setCursorLine(2) qtbot.wait(stepDelay) - assert nwGUI.docEditor.getCursorPosition() == 9 + assert nwGUI.docEditor.getCursorPosition() == 15 qtbot.wait(stepDelay) assert nwGUI.saveProject() @@ -438,10 +442,10 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): jsonData = json.loads(inFile.read()) assert len(jsonData) == 3 - assert jsonData[0]["length"] > 0 - assert jsonData[0]["newWords"] == 86 - assert jsonData[0]["novelWords"] == 59 - assert jsonData[0]["noteWords"] == 27 + assert jsonData[1]["length"] > 0 + assert jsonData[1]["newWords"] == 84 + assert jsonData[1]["novelWords"] == 63 + assert jsonData[1]["noteWords"] == 27 # No Novel Files qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton) @@ -456,7 +460,7 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): assert len(jsonData) == 2 assert jsonData[0]["length"] > 0 assert jsonData[0]["newWords"] == 27 - assert jsonData[0]["novelWords"] == 59 + assert jsonData[0]["novelWords"] == 63 assert jsonData[0]["noteWords"] == 27 # No Note Files @@ -471,10 +475,10 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): jsonData = json.loads(inFile.read()) assert len(jsonData) == 3 - assert jsonData[0]["length"] > 0 - assert jsonData[0]["newWords"] == 59 - assert jsonData[0]["novelWords"] == 59 - assert jsonData[0]["noteWords"] == 27 + assert jsonData[1]["length"] > 0 + assert jsonData[1]["newWords"] == 57 + assert jsonData[1]["novelWords"] == 63 + assert jsonData[1]["noteWords"] == 27 # No Negative Entries qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton) @@ -487,7 +491,7 @@ def testWritingStatsExport(qtbot, nwTempGUI, nwRef, nwTemp): with open(jsonStats, mode="r", encoding="utf-8") as inFile: jsonData = json.loads(inFile.read()) - assert len(jsonData) == 1 + assert len(jsonData) == 2 # Un-hide Zero Entries qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton) From 3acabdf74eeddb930698cf7931ded7be7633bdc6 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 10 Aug 2020 22:26:29 +0200 Subject: [PATCH 18/22] Better title page --- nw/core/project.py | 9 +++++++-- sample/nwProject.nwx | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index 3943f50c..63050a34 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -255,6 +255,11 @@ class NWProject(): self.setBookTitle(projTitle) self.setBookAuthors(projAuthors) + titlePage = "# %s\n\n" % (self.bookTitle if self.bookTitle else self.projName) + if self.bookAuthors: + titlePage = "%sBy %s\n" % (titlePage, ", ".join(self.bookAuthors)) + + # Document object for writing files aDoc = NWDoc(self, self.theParent) if popMinimal: @@ -273,7 +278,7 @@ class NWProject(): self.projTree.setFileItemLayout(cHandle, nwItemLayout.CHAPTER) aDoc.openDocument(tHandle, showStatus=False) - aDoc.saveDocument("# %s\n\n" % projName) + aDoc.saveDocument(titlePage) aDoc.clearDocument() aDoc.openDocument(cHandle, showStatus=False) @@ -300,7 +305,7 @@ class NWProject(): self.projTree.setFileItemLayout(tHandle, nwItemLayout.TITLE) aDoc.openDocument(tHandle, showStatus=False) - aDoc.saveDocument("# %s\n\n" % projName) + aDoc.saveDocument(titlePage) aDoc.clearDocument() # Create chapters and scenes diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 5be0eab5..d52d4f27 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 675 + 676 122 - 32866 + 32870 False From a46803232c39a4a4bd86a7998b01effa5f0b850a Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 11 Aug 2020 00:02:02 +0200 Subject: [PATCH 19/22] Added tests for new sample and new custom project --- tests/conftest.py | 14 ++ tests/nwdummy.py | 6 + tests/reference/proj/4_nwProject.nwx | 270 +++++++++++++++++++++++++++ tests/test_project.py | 57 +++++- 4 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 tests/reference/proj/4_nwProject.nwx diff --git a/tests/conftest.py b/tests/conftest.py index c064c1d8..09031f4f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,6 +38,20 @@ def nwTempBuild(nwTemp): mkdir(buildDir) return buildDir +@pytest.fixture(scope="session") +def nwTempCustom(nwTemp): + customDir = path.join(nwTemp, "custom") + if not path.isdir(customDir): + mkdir(customDir) + return customDir + +@pytest.fixture(scope="session") +def nwTempSample(nwTemp): + sampleDir = path.join(nwTemp, "sample") + if not path.isdir(sampleDir): + mkdir(sampleDir) + return sampleDir + @pytest.fixture(scope="session") def nwRef(): testDir = path.dirname(__file__) diff --git a/tests/nwdummy.py b/tests/nwdummy.py index 880a54eb..2e46352c 100644 --- a/tests/nwdummy.py +++ b/tests/nwdummy.py @@ -33,6 +33,12 @@ class DummyMain(): def setProjectStatus(self, isChanged): return + def openProject(self, projPath): + return + + def rebuildIndex(self): + return + # END Class GuiMain class StatusBar(): diff --git a/tests/reference/proj/4_nwProject.nwx b/tests/reference/proj/4_nwProject.nwx new file mode 100644 index 00000000..3dd716cc --- /dev/null +++ b/tests/reference/proj/4_nwProject.nwx @@ -0,0 +1,270 @@ + + + + Test Custom + Test Novel + Jane Doe + John Doh + 1 + 1 + 0 + + + True + False + True + None + None + 0 + 0 + 0 + + + %title% + Chapter %ch%: %title% + %title% + * * * +
+
+ + New + Note + Draft + Finished + + + New + Minor + Major + Main + +
+ + + Novel + ROOT + NOVEL + New + False + + + Plot + ROOT + PLOT + New + False + + + Characters + ROOT + CHARACTER + New + False + + + Locations + ROOT + WORLD + New + False + + + Timeline + ROOT + TIMELINE + New + False + + + Objects + ROOT + OBJECT + New + False + + + Entity + ROOT + ENTITY + New + False + + + Title Page + FILE + NOVEL + New + True + TITLE + 0 + 0 + 0 + 0 + + + Chapter 1 + FOLDER + NOVEL + New + False + + + Chapter 1 + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + + Scene 1.1 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 1.2 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 1.3 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Chapter 2 + FOLDER + NOVEL + New + False + + + Chapter 2 + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + + Scene 2.1 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 2.2 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 2.3 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Chapter 3 + FOLDER + NOVEL + New + False + + + Chapter 3 + FILE + NOVEL + New + True + CHAPTER + 0 + 0 + 0 + 0 + + + Scene 3.1 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 3.2 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + + Scene 3.3 + FILE + NOVEL + New + True + SCENE + 0 + 0 + 0 + 0 + + +
diff --git a/tests/test_project.py b/tests/test_project.py index 8f35e35f..e27df3c4 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -22,9 +22,9 @@ theProject = NWProject(theMain) theProject.projTree.setSeed(42) @pytest.mark.project -def testProjectNew(nwTempProj,nwRef,nwTemp): +def testProjectNewMinimal(nwTempProj, nwRef, nwTemp): projFile = path.join(nwTempProj,"nwProject.nwx") - refFile = path.join(nwRef,"proj","1_nwProject.nwx") + refFile = path.join(nwRef,"proj", "1_nwProject.nwx") assert theConf.initConfig(nwRef, nwTemp) assert theProject.newProject({"projPath": nwTempProj}) assert theProject.setProjectPath(nwTempProj) @@ -217,3 +217,56 @@ def testIndexMeta(nwTempProj): assert str(theRefs) == "{'0e17daca5f3e1': 'T000001'}" assert theProject.closeProject() + +# The two following tests must be at the end as they mess up the config object +# and the handle seed. They go into their own folders, but use the same project +# object as the test above. + +@pytest.mark.project +def testProjectNewCustom(nwTempCustom, nwRef, nwTemp): + projData = { + "projName": "Test Custom", + "projTitle": "Test Novel", + "projAuthors": "Jane Doe\nJohn Doh\n", + "projPath": nwTempCustom, + "popSample": False, + "popMinimal": False, + "popCustom": True, + "addRoots": [ + nwItemClass.PLOT, + nwItemClass.CHARACTER, + nwItemClass.WORLD, + nwItemClass.TIMELINE, + nwItemClass.OBJECT, + nwItemClass.ENTITY, + ], + "numChapters": 3, + "numScenes": 3, + "chFolders": True, + } + theProject.mainConf = theConf + theProject.projTree.setSeed(42) + assert theProject.newProject(projData) + assert theProject.saveProject() + assert theProject.closeProject() + projFile = path.join(nwTempCustom, "nwProject.nwx") + refFile = path.join(nwRef, "proj", "4_nwProject.nwx") + assert cmpFiles(projFile, refFile, [2, 6, 7, 8]) + +@pytest.mark.project +def testProjectNewSample(nwTempSample, nwLipsum, nwRef, nwTemp): + projData = { + "projName": "Test Sample", + "projTitle": "Test Novel", + "projAuthors": "Jane Doe\nJohn Doh\n", + "projPath": nwTempSample, + "popSample": True, + "popMinimal": False, + "popCustom": False, + } + theProject.mainConf = theConf + assert theProject.newProject(projData) + assert theProject.openProject(nwTempSample) + assert theProject.projName == "Sample Project" + assert theProject.saveProject() + assert theProject.closeProject() From dbdef444a1770cac5d2ae338e936b1903c8b13aa Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 11 Aug 2020 22:14:48 +0200 Subject: [PATCH 20/22] Added project wizard gui test --- nw/gui/projwizard.py | 2 +- nw/guimain.py | 76 +++++++++++++------------ tests/nwtools.py | 7 +++ tests/test_gui.py | 132 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 180 insertions(+), 37 deletions(-) diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index 40f014ec..5c402d25 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -56,7 +56,7 @@ class GuiProjectWizard(QWizard): QWizard.__init__(self, theParent) logger.debug("Initialising GuiProjectWizard ...") - + self.setObjectName("GuiProjectWizard") self.mainConf = nw.CONFIG self.theParent = theParent self.theTheme = theParent.theTheme diff --git a/nw/guimain.py b/nw/guimain.py index 175b29c1..73c7eef0 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -780,42 +780,10 @@ class GuiMain(QMainWindow): newProj = GuiProjectWizard(self) newProj.exec_() - if newProj.result() == QDialog.Rejected: - return None + if newProj.result() == QDialog.Accepted: + return self._assembleProjectWizardData(newProj) - projData = { - "projName": newProj.field("projName"), - "projTitle": newProj.field("projTitle"), - "projAuthors": newProj.field("projAuthors"), - "projPath": newProj.field("projPath"), - "popSample": newProj.field("popSample"), - "popMinimal": newProj.field("popMinimal"), - "popCustom": newProj.field("popCustom"), - "addRoots": [], - "numChapters": 0, - "numScenes": 0, - "chFolders": False, - } - 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) - if newProj.field("addTime"): - addRoots.append(nwItemClass.TIMELINE) - if newProj.field("addObject"): - addRoots.append(nwItemClass.OBJECT) - if newProj.field("addEntity"): - addRoots.append(nwItemClass.ENTITY) - projData["addRoots"] = addRoots - projData["numChapters"] = newProj.field("numChapters") - projData["numScenes"] = newProj.field("numScenes") - projData["chFolders"] = newProj.field("chFolders") - - return projData + return None def editConfigDialog(self): """Open the preferences dialog. @@ -1156,6 +1124,44 @@ class GuiMain(QMainWindow): self.importIcons[sLabel] = QIcon(theIcon) return + def _assembleProjectWizardData(self, newProj): + """Extract the user choices from the New Project Wizard and + store them in a dictionary. + """ + projData = { + "projName": newProj.field("projName"), + "projTitle": newProj.field("projTitle"), + "projAuthors": newProj.field("projAuthors"), + "projPath": newProj.field("projPath"), + "popSample": newProj.field("popSample"), + "popMinimal": newProj.field("popMinimal"), + "popCustom": newProj.field("popCustom"), + "addRoots": [], + "numChapters": 0, + "numScenes": 0, + "chFolders": False, + } + 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) + if newProj.field("addTime"): + addRoots.append(nwItemClass.TIMELINE) + if newProj.field("addObject"): + addRoots.append(nwItemClass.OBJECT) + if newProj.field("addEntity"): + addRoots.append(nwItemClass.ENTITY) + projData["addRoots"] = addRoots + projData["numChapters"] = newProj.field("numChapters") + projData["numScenes"] = newProj.field("numScenes") + projData["chFolders"] = newProj.field("chFolders") + + return projData + ## # Events ## diff --git a/tests/nwtools.py b/tests/nwtools.py index fecb1df9..9baefe2d 100644 --- a/tests/nwtools.py +++ b/tests/nwtools.py @@ -5,6 +5,8 @@ from os import path, mkdir from itertools import chain +from PyQt5.QtWidgets import qApp + def ensureDir(theDir): if not path.isdir(theDir): mkdir(theDir) @@ -60,3 +62,8 @@ def cmpList(listOne, listTwo): if flatOne[i] != flatTwo[i]: return False return True + +def getGuiItem(theName): + for qWidget in qApp.topLevelWidgets(): + if qWidget.objectName() == theName: + return qWidget diff --git a/tests/test_gui.py b/tests/test_gui.py index 821d96c1..a5713ec5 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -7,10 +7,11 @@ from nwtools import * from os import path, unlink from PyQt5.QtCore import Qt +# from PyQt5.QtWidgets import qApp from nw.gui import ( GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel, - GuiDocMerge, GuiDocSplit, GuiWritingStats + GuiDocMerge, GuiDocSplit, GuiWritingStats, GuiProjectWizard ) from nw.constants import * @@ -740,6 +741,135 @@ def testSplitTool(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): # qtbot.stopForInteraction() nwGUI.closeMain() +@pytest.mark.gui +def testNewProjectWizard(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): + + from PyQt5.QtWidgets import QWizard + from nw.gui.projwizard import ( + ProjWizardIntroPage, ProjWizardFolderPage, ProjWizardPopulatePage, + ProjWizardCustomPage, ProjWizardFinalPage + ) + + nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp]) + qtbot.addWidget(nwGUI) + nwGUI.show() + qtbot.waitForWindowShown(nwGUI) + qtbot.wait(stepDelay) + + for wStep in range(3): + + # The Wizard + nwWiz = GuiProjectWizard(nwGUI) + nwWiz.show() + qtbot.waitForWindowShown(nwWiz) + + # Intro Page + introPage = nwWiz.currentPage() + assert isinstance(introPage, ProjWizardIntroPage) + assert not nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + for c in "Test Minimal": + qtbot.keyClick(introPage.projName, c, delay=keyDelay) + + qtbot.wait(stepDelay) + for c in "Minimal Novel": + qtbot.keyClick(introPage.projTitle, c, delay=keyDelay) + + qtbot.wait(stepDelay) + for c in "Jane Doe": + qtbot.keyClick(introPage.projAuthors, c, delay=keyDelay) + + # Setting projName should activate the button + assert nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) + + # Folder Page + storagePage = nwWiz.currentPage() + assert isinstance(storagePage, ProjWizardFolderPage) + assert not nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + projPath = path.join(nwTemp, "dummy") + for c in projPath: + qtbot.keyClick(storagePage.projPath, c, delay=keyDelay) + + # Setting projPath should activate the button + assert nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) + + # Populate Page + popPage = nwWiz.currentPage() + assert isinstance(popPage, ProjWizardPopulatePage) + assert nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + if wStep == 0: + popPage.popMinimal.setChecked(True) + elif wStep == 1: + popPage.popCustom.setChecked(True) + elif wStep == 2: + popPage.popSample.setChecked(True) + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) + + # Custom Page + if wStep == 1: + customPage = nwWiz.currentPage() + assert isinstance(customPage, ProjWizardCustomPage) + assert nwWiz.button(QWizard.NextButton).isEnabled() + + customPage.addPlot.setChecked(True) + customPage.addChar.setChecked(True) + customPage.addWorld.setChecked(True) + customPage.addTime.setChecked(True) + customPage.addObject.setChecked(True) + customPage.addEntity.setChecked(True) + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) + + # Final Page + finalPage = nwWiz.currentPage() + assert isinstance(finalPage, ProjWizardFinalPage) + assert nwWiz.button(QWizard.FinishButton).isEnabled() + qtbot.mouseClick(nwWiz.button(QWizard.FinishButton), Qt.LeftButton) + + # Check Data + projData = nwGUI._assembleProjectWizardData(nwWiz) + assert projData["projName"] == "Test Minimal" + assert projData["projTitle"] == "Minimal Novel" + assert projData["projAuthors"] == "Jane Doe" + assert projData["projPath"] == projPath + assert projData["popMinimal"] == (wStep == 0) + assert projData["popCustom"] == (wStep == 1) + assert projData["popSample"] == (wStep == 2) + if wStep == 1: + assert projData["addRoots"] == [ + nwItemClass.PLOT, + nwItemClass.CHARACTER, + nwItemClass.WORLD, + nwItemClass.TIMELINE, + nwItemClass.OBJECT, + nwItemClass.ENTITY, + ] + assert projData["numChapters"] == 5 + assert projData["numScenes"] == 5 + assert projData["chFolders"] == True + else: + assert projData["addRoots"] == [] + assert projData["numChapters"] == 0 + assert projData["numScenes"] == 0 + assert projData["chFolders"] == False + + # qtbot.stopForInteraction() + nwGUI.closeMain() + @pytest.mark.gui def testDocAction(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp): From e05aada76a90d6cb47b6b2dc32c3cbf80725a352 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 11 Aug 2020 22:29:46 +0200 Subject: [PATCH 21/22] Turning on codecov patch feature again --- .codecov.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.codecov.yml b/.codecov.yml index 45ebc6c7..9166a2e1 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -10,7 +10,9 @@ coverage: project: default: threshold: 1% - patch: no + patch: + default: + threshold: 1% changes: no parsers: From 033c3e63c247697827bf27634c6db718d8a9f6a9 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Tue, 11 Aug 2020 22:55:38 +0200 Subject: [PATCH 22/22] Some final tweaks --- nw/core/project.py | 4 ++-- nw/gui/projwizard.py | 12 +++++------- tests/test_gui.py | 1 - 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/nw/core/project.py b/nw/core/project.py index 63050a34..3404370e 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -883,8 +883,8 @@ class NWProject(): else: self.makeAlert(( - "Failed to create a new example project. " - "Could not find the necessary files." + "Failed to create a new example project. Could not find the " + "necessary files. They seem to be missing from this installation." ), nwAlert.ERROR) if isSuccess: diff --git a/nw/gui/projwizard.py b/nw/gui/projwizard.py index 5c402d25..e95f0a26 100644 --- a/nw/gui/projwizard.py +++ b/nw/gui/projwizard.py @@ -31,7 +31,6 @@ import nw from os import path from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPixmap, QColor from PyQt5.QtWidgets import ( QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit, QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout, @@ -56,7 +55,7 @@ class GuiProjectWizard(QWizard): QWizard.__init__(self, theParent) logger.debug("Initialising GuiProjectWizard ...") - self.setObjectName("GuiProjectWizard") + self.mainConf = nw.CONFIG self.theParent = theParent self.theTheme = theParent.theTheme @@ -240,7 +239,7 @@ class ProjWizardPopulatePage(QWizardPage): self.setTitle("Populate Project") self.theText = QLabel( "Choose how to pre-fill the project. Either with a minimal set of " - "starter items, a sample project explaining and showing many 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) @@ -249,19 +248,19 @@ class ProjWizardPopulatePage(QWizardPage): fS = self.mainConf.pxInt(4) self.popMinimal = QRadioButton("Fill the project with a minimal set of items") - self.popCustom = QRadioButton("Show detailed options for filling the project") self.popSample = QRadioButton("Fill the project with example files") + self.popCustom = QRadioButton("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.popCustom) self.popBox.addWidget(self.popSample) + self.popBox.addWidget(self.popCustom) self.registerField("popMinimal", self.popMinimal) - self.registerField("popCustom", self.popCustom) self.registerField("popSample", self.popSample) + self.registerField("popCustom", self.popCustom) # Assemble self.outerBox = QVBoxLayout() @@ -301,7 +300,6 @@ class ProjWizardCustomPage(QWizardPage): self.theText.setWordWrap(True) vS = self.mainConf.pxInt(12) - fS = self.mainConf.pxInt(4) # Root Folders self.rootGroup = QGroupBox("Additional Root Folders") diff --git a/tests/test_gui.py b/tests/test_gui.py index a5713ec5..ebafbab5 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -7,7 +7,6 @@ from nwtools import * from os import path, unlink from PyQt5.QtCore import Qt -# from PyQt5.QtWidgets import qApp from nw.gui import ( GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel,