Merge pull request #366 from vkbo/new_project_wizard

New Project Wizard
This commit is contained in:
Veronica K. Berglyd Olsen
2020-08-11 23:00:48 +02:00
committed by GitHub
35 changed files with 1672 additions and 279 deletions
+3 -1
View File
@@ -10,7 +10,9 @@ coverage:
project:
default:
threshold: 1%
patch: no
patch:
default:
threshold: 1%
changes: no
parsers:
+1
View File
@@ -15,6 +15,7 @@ novelWriter.qhc
__pycache__
# Sample Project
/nw/assets/sample.zip
/sample/cache
/sample/meta
*.bak
Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

+9
View File
@@ -233,3 +233,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
+239 -49
View File
@@ -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
@@ -41,9 +41,11 @@ 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
nwFiles, nwItemType, nwItemClass, nwItemLayout, nwLabels, nwAlert
)
logger = logging.getLogger(__name__)
@@ -169,22 +171,6 @@ class NWProject():
# Project Methods
##
def newProject(self):
"""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)
self.projOpened = time()
self.setProjectChanged(True)
self.saveProject(autoSave=True)
return True
def clearProject(self):
"""Clear the data for the current project, and set them to
default values.
@@ -220,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
@@ -238,6 +224,141 @@ 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)
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:
# 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)
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(cHandle, nwItemLayout.CHAPTER)
aDoc.openDocument(tHandle, showStatus=False)
aDoc.saveDocument(titlePage)
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
# 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)
aDoc.openDocument(tHandle, showStatus=False)
aDoc.saveDocument(titlePage)
aDoc.clearDocument()
# 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)
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)
# 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,
@@ -557,9 +678,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
@@ -574,8 +695,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:
@@ -635,7 +756,7 @@ class NWProject():
return True
##
# Backup Project
# Zip/Unzip Project
##
def zipIt(self, doNotify):
@@ -665,7 +786,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:
@@ -711,6 +832,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. They seem to be missing from this installation."
), nwAlert.ERROR)
if isSuccess:
self.clearProject()
self.theParent.openProject(projPath)
self.theParent.rebuildIndex()
return isSuccess
##
# Setters
##
@@ -726,13 +909,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)
@@ -757,13 +951,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):
@@ -889,15 +1088,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.
"""
+16
View File
@@ -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
##
+2
View File
@@ -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",
+4 -3
View File
@@ -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("<b>%s</b>" % self.mainConf.appName)
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)
+2 -2
View File
@@ -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 (
@@ -622,7 +622,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)
+4 -4
View File
@@ -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)
@@ -173,8 +174,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:
+1 -1
View File
@@ -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
+418
View File
@@ -0,0 +1,418 @@
# -*- 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 <https://www.gnu.org/licenses/>.
"""
import logging
import nw
from os import path
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit,
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__)
PAGE_INTRO = 0
PAGE_STORE = 1
PAGE_POP = 2
PAGE_CUSTOM = 3
PAGE_FINAL = 4
class GuiProjectWizard(QWizard):
def __init__(self, theParent):
QWizard.__init__(self, theParent)
logger.debug("Initialising GuiProjectWizard ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
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)
self.storagePage = ProjWizardFolderPage(self)
self.popPage = ProjWizardPopulatePage(self)
self.customPage = ProjWizardCustomPage(self)
self.finalPage = ProjWizardFinalPage(self)
self.setPage(PAGE_INTRO, self.introPage)
self.setPage(PAGE_STORE, self.storagePage)
self.setPage(PAGE_POP, self.popPage)
self.setPage(PAGE_CUSTOM, self.customPage)
self.setPage(PAGE_FINAL, self.finalPage)
self.setOption(QWizard.NoBackButtonOnStartPage, True)
logger.debug("GuiProjectWizard initialisation complete")
return
# END Class GuiProjectWizard
class ProjWizardIntroPage(QWizardPage):
def __init__(self, theWizard):
QWizardPage.__init__(self)
self.mainConf = nw.CONFIG
self.theWizard = theWizard
self.theTheme = theWizard.theTheme
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)
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)
fS = self.mainConf.pxInt(4)
# The Page Form
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)
self.registerField("projName*", self.projName)
self.registerField("projTitle", self.projTitle)
self.registerField("projAuthors", self.projAuthors, "plainText")
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.setSpacing(vS)
self.outerBox.addWidget(self.theText)
self.outerBox.addLayout(self.mainForm)
self.outerBox.addStretch(1)
self.outerBox.addWidget(self.imgCredit)
self.setLayout(self.outerBox)
return
# END Class ProjWizardIntroPage
class ProjWizardFolderPage(QWizardPage):
def __init__(self, theWizard):
QWizardPage.__init__(self)
self.mainConf = nw.CONFIG
self.theWizard = theWizard
self.theTheme = theWizard.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)
vS = self.mainConf.pxInt(12)
fS = self.mainConf.pxInt(8)
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 = 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)
# 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
class ProjWizardPopulatePage(QWizardPage):
def __init__(self, theWizard):
QWizardPage.__init__(self)
self.mainConf = nw.CONFIG
self.theWizard = theWizard
self.setTitle("Populate Project")
self.theText = QLabel(
"Choose how to pre-fill the project. Either with a minimal set of "
"starter items, an example project explaining and showing many of "
"the features, or show further custom options on the next page."
)
self.theText.setWordWrap(True)
vS = self.mainConf.pxInt(12)
fS = self.mainConf.pxInt(4)
self.popMinimal = QRadioButton("Fill the project with a minimal set of items")
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.popSample)
self.popBox.addWidget(self.popCustom)
self.registerField("popMinimal", self.popMinimal)
self.registerField("popSample", self.popSample)
self.registerField("popCustom", self.popCustom)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.setSpacing(vS)
self.outerBox.addWidget(self.theText)
self.outerBox.addLayout(self.popBox)
self.outerBox.addStretch(1)
self.setLayout(self.outerBox)
return
def nextId(self):
"""Overload the nextID function to skip further pages if custom
is not selected.
"""
if self.popCustom.isChecked():
return PAGE_CUSTOM
else:
return PAGE_FINAL
# END Class ProjWizardPopulatePage
class ProjWizardCustomPage(QWizardPage):
def __init__(self, theWizard):
QWizardPage.__init__(self)
self.mainConf = nw.CONFIG
self.theWizard = theWizard
self.setTitle("Custom Project Options")
self.theText = QLabel(
"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)
# 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.addWorld.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.innerBox)
self.outerBox.addStretch(1)
self.setLayout(self.outerBox)
return
# END Class ProjWizardCustomPage
class ProjWizardFinalPage(QWizardPage):
def __init__(self, theWizard):
QWizardPage.__init__(self)
self.mainConf = nw.CONFIG
self.theWizard = theWizard
self.setTitle("Finished")
self.theText = QLabel((
"<p>All done.</p>"
"<p>Press '{finish}' to create the new project.</p>"
).format(
finish = "Done" if self.mainConf.osDarwin else "Finish"
))
self.theText.setWordWrap(True)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.setSpacing(self.mainConf.pxInt(12))
self.outerBox.addWidget(self.theText)
self.outerBox.addStretch(1)
self.setLayout(self.outerBox)
return
# END Class ProjWizardFinalPage
+22 -13
View File
@@ -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 (
@@ -501,6 +501,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"),
@@ -556,7 +557,7 @@ class GuiIcons:
}
DECO_MAP = {
"nwicon" : ["icons", "novelwriter.svg"],
"wiz-back" : "wizard-back.jpg",
}
def __init__(self, theParent):
@@ -649,7 +650,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.
"""
@@ -657,20 +658,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,
@@ -738,6 +741,12 @@ class GuiIcons:
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])
+64 -19
View File
@@ -43,10 +43,11 @@ from nw.gui import (
GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails,
GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus,
GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, GuiTheme,
GuiProjectSettings, GuiProjectTree, GuiWritingStats, GuiAbout
GuiProjectSettings, GuiProjectTree, GuiWritingStats, GuiProjectWizard,
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__)
@@ -262,8 +263,9 @@ 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.
"""
if self.hasProject:
msgBox = QMessageBox()
@@ -273,9 +275,15 @@ class GuiMain(QMainWindow):
)
return False
if projPath is None:
projPath = self.newProjectDialog()
if projPath is None:
if projData is None and self.mainConf.showGUI:
projData = self.newProjectDialog()
if projData is None:
return False
projPath = projData.get("projPath", None)
if projPath is None or projData is None:
logger.error("No projData or projPath set")
return False
if path.isfile(path.join(projPath,self.theProject.projFile)) and not forceNew:
@@ -287,13 +295,14 @@ class GuiMain(QMainWindow):
return False
logger.info("Creating new project")
if self.theProject.setProjectPath(projPath, newProject=True):
self.theProject.newProject()
if self.theProject.newProject(projData):
self.rebuildTree()
self.saveProject()
self.hasProject = True
self.statusBar.setRefTime(self.theProject.projOpened)
self.rebuildIndex(beQuiet=True)
else:
self.theProject.clearProject()
return False
return True
@@ -692,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:
@@ -735,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
@@ -766,16 +775,14 @@ class GuiMain(QMainWindow):
return None
def newProjectDialog(self):
"""Select where to save new project.
"""Open the wizard and assemble the project options dict.
"""
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_()
if newProj.result() == QDialog.Accepted:
return self._assembleProjectWizardData(newProj)
return None
def editConfigDialog(self):
@@ -1117,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
##
+3 -3
View File
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-09 15:42:40">
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 20:03:56">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>675</saveCount>
<saveCount>676</saveCount>
<autoCount>122</autoCount>
<editTime>32866</editTime>
<editTime>32870</editTime>
</project>
<settings>
<doBackup>False</doBackup>
+35
View File
@@ -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)
+14
View File
@@ -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__)
+17
View File
@@ -8,6 +8,7 @@ class DummyMain():
def __init__(self):
self.mainConf = None
self.statusBar = StatusBar()
return
def makeAlert(self, theMessage, theLevel):
@@ -32,4 +33,20 @@ class DummyMain():
def setProjectStatus(self, isChanged):
return
def openProject(self, projPath):
return
def rebuildIndex(self):
return
# END Class GuiMain
class StatusBar():
def __init__(self):
return
def setStatus(self, theText):
return
# END Class StatusBar
+7
View File
@@ -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
+35 -11
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:05:07">
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 21:55:04">
<project>
<name>New Project</name>
<title></title>
@@ -13,8 +13,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<lastWordCount>6</lastWordCount>
<novelWordCount>6</novelWordCount>
<notesWordCount>0</notesWordCount>
<autoReplace/>
<titleFormat>
@@ -37,7 +37,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="6">
<content count="8">
<item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -46,35 +46,59 @@
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="31489056e0916" order="1" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="0" parent="25fc0e7096fc6">
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="1" parent="31489056e0916">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<charCount>9</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="44cb730c42048" order="1" parent="None">
<name>Characters</name>
<name>Plot</name>
<type>ROOT</type>
<class>CHARACTER</class>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="2" parent="None">
<name>Plot</name>
<name>Characters</name>
<type>ROOT</type>
<class>PLOT</class>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
+6
View File
@@ -0,0 +1,6 @@
%%~ 031b4af5197ec:44cb730c42048:PLOT:NOTE:New File
# Main Plot
@tag: MainPlot
This is a file detailing the main plot.
+21 -4
View File
@@ -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 singles 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.
+4 -4
View File
@@ -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 Janes home.
This is a file about Jane.
-23
View File
@@ -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 singles quotes are replaced. We can hyphen-ate, make dashes and even longer dashes — if we want. Ellipsis? Not a problem either …
+6
View File
@@ -0,0 +1,6 @@
%%~ 41cfc0d1f2d12:811786ad1ae74:WORLD:NOTE:New File
# Main Location
@tag: Home
This is a file describing Janes home.
-6
View File
@@ -1,6 +0,0 @@
%%~ 98010bd9270f9:44cb730c42048:CHARACTER:NOTE:New File
# Jane Doe
@tag: Jane
This is a file about Jane.
+53 -29
View File
@@ -1,20 +1,20 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:06:00">
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 22:00:16">
<project>
<name>New Project</name>
<title></title>
<saveCount>5</saveCount>
<autoCount>1</autoCount>
<editTime>11</editTime>
<editTime>14</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<spellCheck>True</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>31489056e0916</lastEdited>
<lastEdited>0e17daca5f3e1</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>86</lastWordCount>
<novelWordCount>59</novelWordCount>
<lastWordCount>90</lastWordCount>
<novelWordCount>63</novelWordCount>
<notesWordCount>27</notesWordCount>
<autoReplace/>
<titleFormat>
@@ -37,7 +37,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="10">
<content count="12">
<item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -46,13 +46,37 @@
<expanded>True</expanded>
</item>
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="31489056e0916" order="1" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="31489056e0916" order="0" parent="25fc0e7096fc6">
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="1" parent="31489056e0916">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
@@ -65,32 +89,13 @@
<cursorPos>465</cursorPos>
</item>
<item handle="44cb730c42048" order="1" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="98010bd9270f9" order="0" parent="44cb730c42048">
<name>New File</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>New</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>34</charCount>
<wordCount>8</wordCount>
<paraCount>1</paraCount>
<cursorPos>51</cursorPos>
</item>
<item handle="71ee45a3c0db9" order="2" parent="None">
<name>Plot</name>
<type>ROOT</type>
<class>PLOT</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="0e17daca5f3e1" order="0" parent="71ee45a3c0db9">
<item handle="031b4af5197ec" order="0" parent="44cb730c42048">
<name>New File</name>
<type>FILE</type>
<class>PLOT</class>
@@ -102,6 +107,25 @@
<paraCount>1</paraCount>
<cursorPos>69</cursorPos>
</item>
<item handle="71ee45a3c0db9" order="2" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="1a6562590ef19" order="0" parent="71ee45a3c0db9">
<name>New File</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>New</status>
<exported>True</exported>
<layout>NOTE</layout>
<charCount>34</charCount>
<wordCount>8</wordCount>
<paraCount>1</paraCount>
<cursorPos>51</cursorPos>
</item>
<item handle="811786ad1ae74" order="3" parent="None">
<name>World</name>
<type>ROOT</type>
@@ -109,7 +133,7 @@
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="1a6562590ef19" order="0" parent="811786ad1ae74">
<item handle="41cfc0d1f2d12" order="0" parent="811786ad1ae74">
<name>New File</name>
<type>FILE</type>
<class>WORLD</class>
@@ -121,7 +145,7 @@
<paraCount>1</paraCount>
<cursorPos>68</cursorPos>
</item>
<item handle="41cfc0d1f2d12" order="4" parent="None">
<item handle="2fca346db6561" order="4" parent="None">
<name>Trash</name>
<type>TRASH</type>
<class>TRASH</class>
+36 -12
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.10.0rc1" hexVersion="0x001000c1" fileVersion="1.1" timeStamp="2020-06-26 19:32:47">
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 22:01:52">
<project>
<name>Project Name</name>
<title>Project Title</title>
@@ -7,7 +7,7 @@
<author>John Doh</author>
<saveCount>2</saveCount>
<autoCount>1</autoCount>
<editTime>1</editTime>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
@@ -15,8 +15,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<lastWordCount>6</lastWordCount>
<novelWordCount>6</novelWordCount>
<notesWordCount>0</notesWordCount>
<autoReplace>
<entry key="This">With This Stuff </entry>
@@ -41,7 +41,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="6">
<content count="8">
<item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -50,35 +50,59 @@
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="31489056e0916" order="1" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="0" parent="25fc0e7096fc6">
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="1" parent="31489056e0916">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<charCount>9</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="44cb730c42048" order="1" parent="None">
<name>Characters</name>
<name>Plot</name>
<type>ROOT</type>
<class>CHARACTER</class>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="2" parent="None">
<name>Plot</name>
<name>Characters</name>
<type>ROOT</type>
<class>PLOT</class>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
+37 -13
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.9.0rc1" hexVersion="0x000900c1" fileVersion="1.1" timeStamp="2020-06-18 22:17:32">
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 22:03:19">
<project>
<name>New Project</name>
<title></title>
@@ -11,10 +11,10 @@
<doBackup>True</doBackup>
<spellCheck>False</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>31489056e0916</lastEdited>
<lastEdited>0e17daca5f3e1</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>59</lastWordCount>
<novelWordCount>59</novelWordCount>
<lastWordCount>6</lastWordCount>
<novelWordCount>6</novelWordCount>
<notesWordCount>0</notesWordCount>
<autoReplace/>
<titleFormat>
@@ -37,7 +37,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="6">
<content count="8">
<item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -46,35 +46,59 @@
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="0" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="31489056e0916" order="1" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="0" parent="25fc0e7096fc6">
<item handle="98010bd9270f9" order="0" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>11</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="1" parent="31489056e0916">
<name>Just a Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>Note</status>
<exported>False</exported>
<layout>PAGE</layout>
<charCount>331</charCount>
<wordCount>59</wordCount>
<paraCount>2</paraCount>
<charCount>9</charCount>
<wordCount>2</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="44cb730c42048" order="1" parent="None">
<name>Characters</name>
<name>Plot</name>
<type>ROOT</type>
<class>CHARACTER</class>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="2" parent="None">
<name>Plot</name>
<name>Characters</name>
<type>ROOT</type>
<class>PLOT</class>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
+32 -8
View File
@@ -1,10 +1,10 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 20:58:52">
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 21:26:47">
<project>
<name>New Project</name>
<title></title>
<saveCount>1</saveCount>
<autoCount>0</autoCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
@@ -37,7 +37,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="6">
<content count="8">
<item handle="73475cb40a568" order="None" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -46,16 +46,16 @@
<expanded>False</expanded>
</item>
<item handle="44cb730c42048" order="None" parent="None">
<name>Characters</name>
<name>Plot</name>
<type>ROOT</type>
<class>CHARACTER</class>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="None" parent="None">
<name>Plot</name>
<name>Characters</name>
<type>ROOT</type>
<class>PLOT</class>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
@@ -67,13 +67,37 @@
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="None" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="31489056e0916" order="None" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="None" parent="25fc0e7096fc6">
<item handle="98010bd9270f9" order="None" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="None" parent="31489056e0916">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
+36 -12
View File
@@ -1,10 +1,10 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:03:18">
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 21:28:02">
<project>
<name>New Project</name>
<title></title>
<saveCount>4</saveCount>
<autoCount>0</autoCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
@@ -37,7 +37,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="10">
<content count="12">
<item handle="73475cb40a568" order="None" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -46,16 +46,16 @@
<expanded>False</expanded>
</item>
<item handle="44cb730c42048" order="None" parent="None">
<name>Characters</name>
<name>Plot</name>
<type>ROOT</type>
<class>CHARACTER</class>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="None" parent="None">
<name>Plot</name>
<name>Characters</name>
<type>ROOT</type>
<class>PLOT</class>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
@@ -67,13 +67,37 @@
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="None" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="31489056e0916" order="None" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="None" parent="25fc0e7096fc6">
<item handle="98010bd9270f9" order="None" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="None" parent="31489056e0916">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
@@ -85,28 +109,28 @@
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="98010bd9270f9" order="None" parent="None">
<item handle="1a6562590ef19" order="None" parent="None">
<name>Timeline</name>
<type>ROOT</type>
<class>TIMELINE</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="0e17daca5f3e1" order="None" parent="None">
<item handle="031b4af5197ec" order="None" parent="None">
<name>Object</name>
<type>ROOT</type>
<class>OBJECT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="1a6562590ef19" order="None" parent="None">
<item handle="41cfc0d1f2d12" order="None" parent="None">
<name>Custom1</name>
<type>ROOT</type>
<class>CUSTOM</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="031b4af5197ec" order="None" parent="None">
<item handle="2858dcd1057d3" order="None" parent="None">
<name>Custom2</name>
<type>ROOT</type>
<class>CUSTOM</class>
+38 -14
View File
@@ -1,10 +1,10 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:04:10">
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 21:29:43">
<project>
<name>New Project</name>
<title></title>
<saveCount>5</saveCount>
<autoCount>0</autoCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
@@ -37,7 +37,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="12">
<content count="14">
<item handle="73475cb40a568" order="None" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -46,16 +46,16 @@
<expanded>False</expanded>
</item>
<item handle="44cb730c42048" order="None" parent="None">
<name>Characters</name>
<name>Plot</name>
<type>ROOT</type>
<class>CHARACTER</class>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="None" parent="None">
<name>Plot</name>
<name>Characters</name>
<type>ROOT</type>
<class>PLOT</class>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
@@ -67,13 +67,37 @@
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="None" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="31489056e0916" order="None" parent="73475cb40a568">
<name>New Chapter</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="None" parent="25fc0e7096fc6">
<item handle="98010bd9270f9" order="None" parent="31489056e0916">
<name>New Chapter</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="0e17daca5f3e1" order="None" parent="31489056e0916">
<name>New Scene</name>
<type>FILE</type>
<class>NOVEL</class>
@@ -85,35 +109,35 @@
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="98010bd9270f9" order="None" parent="None">
<item handle="1a6562590ef19" order="None" parent="None">
<name>Timeline</name>
<type>ROOT</type>
<class>TIMELINE</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="0e17daca5f3e1" order="None" parent="None">
<item handle="031b4af5197ec" order="None" parent="None">
<name>Object</name>
<type>ROOT</type>
<class>OBJECT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="1a6562590ef19" order="None" parent="None">
<item handle="41cfc0d1f2d12" order="None" parent="None">
<name>Custom1</name>
<type>ROOT</type>
<class>CUSTOM</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="031b4af5197ec" order="None" parent="None">
<item handle="2858dcd1057d3" order="None" parent="None">
<name>Custom2</name>
<type>ROOT</type>
<class>CUSTOM</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="41cfc0d1f2d12" order="None" parent="73475cb40a568">
<item handle="2fca346db6561" order="None" parent="73475cb40a568">
<name>Hello</name>
<type>FILE</type>
<class>NOVEL</class>
@@ -125,7 +149,7 @@
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2858dcd1057d3" order="None" parent="44cb730c42048">
<item handle="02d20bbd7e394" order="None" parent="71ee45a3c0db9">
<name>Jane</name>
<type>FILE</type>
<class>CHARACTER</class>
+270
View File
@@ -0,0 +1,270 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.11.1" hexVersion="0x001101f0" fileVersion="1.2" timeStamp="2020-08-10 23:59:24">
<project>
<name>Test Custom</name>
<title>Test Novel</title>
<author>Jane Doe</author>
<author>John Doh</author>
<saveCount>1</saveCount>
<autoCount>1</autoCount>
<editTime>0</editTime>
</project>
<settings>
<doBackup>True</doBackup>
<spellCheck>False</spellCheck>
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<autoReplace/>
<titleFormat>
<title>%title%</title>
<chapter>Chapter %ch%: %title%</chapter>
<unnumbered>%title%</unnumbered>
<scene>* * *</scene>
<section></section>
</titleFormat>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
<entry blue="0" green="150" red="200">Draft</entry>
<entry blue="0" green="200" red="50">Finished</entry>
</status>
<importance>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Minor</entry>
<entry blue="0" green="150" red="200">Major</entry>
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="23">
<item handle="73475cb40a568" order="None" parent="None">
<name>Novel</name>
<type>ROOT</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="44cb730c42048" order="None" parent="None">
<name>Plot</name>
<type>ROOT</type>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="71ee45a3c0db9" order="None" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="811786ad1ae74" order="None" parent="None">
<name>Locations</name>
<type>ROOT</type>
<class>WORLD</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="25fc0e7096fc6" order="None" parent="None">
<name>Timeline</name>
<type>ROOT</type>
<class>TIMELINE</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="31489056e0916" order="None" parent="None">
<name>Objects</name>
<type>ROOT</type>
<class>OBJECT</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="98010bd9270f9" order="None" parent="None">
<name>Entity</name>
<type>ROOT</type>
<class>ENTITY</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="0e17daca5f3e1" order="None" parent="73475cb40a568">
<name>Title Page</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>TITLE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="1a6562590ef19" order="None" parent="73475cb40a568">
<name>Chapter 1</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="031b4af5197ec" order="None" parent="1a6562590ef19">
<name>Chapter 1</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="41cfc0d1f2d12" order="None" parent="1a6562590ef19">
<name>Scene 1.1</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2858dcd1057d3" order="None" parent="1a6562590ef19">
<name>Scene 1.2</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="2fca346db6561" order="None" parent="1a6562590ef19">
<name>Scene 1.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="02d20bbd7e394" order="None" parent="73475cb40a568">
<name>Chapter 2</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="7688b6ef52555" order="None" parent="02d20bbd7e394">
<name>Chapter 2</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="c837649cce43f" order="None" parent="02d20bbd7e394">
<name>Scene 2.1</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="6208ef0f7750c" order="None" parent="02d20bbd7e394">
<name>Scene 2.2</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="3e1e967e9b793" order="None" parent="02d20bbd7e394">
<name>Scene 2.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="39fa9ec190eee" order="None" parent="73475cb40a568">
<name>Chapter 3</name>
<type>FOLDER</type>
<class>NOVEL</class>
<status>New</status>
<expanded>False</expanded>
</item>
<item handle="d029fa3a95e17" order="None" parent="39fa9ec190eee">
<name>Chapter 3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>CHAPTER</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="81b8a03f97e87" order="None" parent="39fa9ec190eee">
<name>Scene 3.1</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="da4ea2a5506f2" order="None" parent="39fa9ec190eee">
<name>Scene 3.2</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
<item handle="a68b412c42825" order="None" parent="39fa9ec190eee">
<name>Scene 3.3</name>
<type>FILE</type>
<class>NOVEL</class>
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
</item>
</content>
</novelWriterXML>
+172 -36
View File
@@ -10,12 +10,12 @@ from PyQt5.QtCore import Qt
from nw.gui import (
GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel,
GuiDocMerge, GuiDocSplit, GuiWritingStats
GuiDocMerge, GuiDocSplit, GuiWritingStats, GuiProjectWizard
)
from nw.constants import *
keyDelay = 5
stepDelay = 50
keyDelay = 2
stepDelay = 20
@pytest.mark.gui
def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
@@ -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()
@@ -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,12 +83,13 @@ 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()
# 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)
@@ -102,12 +105,13 @@ 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()
# 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)
@@ -134,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)
@@ -150,12 +155,13 @@ 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
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)
@@ -233,8 +239,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 +249,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()
@@ -274,7 +281,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,10 +370,10 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
# Create new, save, open project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject(nwTempGUI, True)
assert nwGUI.openDocument("31489056e0916")
assert nwGUI.newProject({"projPath": nwTempGUI}, True)
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 +390,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,12 +398,12 @@ 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)
qtbot.wait(stepDelay)
assert nwGUI.docEditor.getCursorPosition() == 9
assert nwGUI.docEditor.getCursorPosition() == 15
qtbot.wait(stepDelay)
assert nwGUI.saveProject()
@@ -435,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)
@@ -453,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
@@ -468,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)
@@ -484,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)
@@ -733,6 +740,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):
+65 -12
View File
@@ -22,11 +22,11 @@ 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()
assert theProject.newProject({"projPath": nwTempProj})
assert theProject.setProjectPath(nwTempProj)
assert theProject.saveProject()
assert theProject.closeProject()
@@ -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,59 @@ 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()
# 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()