Merge pull request #177 from vkbo/splash_screen

Project Load Screen
This commit is contained in:
Veronica K. Berglyd Olsen
2020-02-26 20:30:41 +01:00
committed by GitHub
15 changed files with 447 additions and 131 deletions
+6 -1
View File
@@ -87,6 +87,7 @@ def main(sysArgs=None):
"logfile=",
"version",
"config=",
"data=",
"testmode",
"style=",
]
@@ -105,6 +106,7 @@ def main(sysArgs=None):
" -l, --logfile= Specify log file.\n"
" --style= Set Qt5 style flag. Defaults to 'Fusion'.\n"
" --config= Alternative config file.\n"
" --data= Alternative user data path.\n"
" --headless Do not display GUI. Useful for testing scripts.\n"
).format(
appname = __package__,
@@ -120,6 +122,7 @@ def main(sysArgs=None):
toFile = False
toStd = True
confPath = None
dataPath = None
testMode = False
qtStyle = "Fusion"
cmdOpen = None
@@ -157,6 +160,8 @@ def main(sysArgs=None):
qtStyle = inArg
elif inOpt in ("--config"):
confPath = inArg
elif inOpt in ("--data"):
dataPath = inArg
elif inOpt in ("--testmode"):
testMode = True
@@ -187,7 +192,7 @@ def main(sysArgs=None):
logger.setLevel(debugLevel)
CONFIG.initConfig(confPath)
CONFIG.initConfig(confPath, dataPath)
if testMode:
nwGUI = GuiMain()
+19
View File
@@ -88,6 +88,25 @@ def colRange(rgbStart, rgbEnd, nStep):
return retCol
def formatInt(theInt):
"""Formats an integer with k, M, G etc.
"""
postFix = ["k","M","G","T","P","E"]
theVal = float(theInt)
if theVal > 1000.0:
for pF in postFix:
theVal /= 1000.0
if theVal < 1000.0:
if theVal < 10.0:
return "%4.2f%s" % (theVal,pF)
elif theVal < 100.0:
return "%4.1f%s" % (theVal,pF)
else:
return "%3.0f%s" % (theVal,pF)
return "%d" % theInt
def splitVersionNumber(vString):
""" Splits a version string on the form aa.bb.cc into major, minor
and patch, and computes an integer value aabbcc.
+126 -45
View File
@@ -12,10 +12,11 @@
import logging
import configparser
import json
import sys
import nw
from os import path, mkdir
from os import path, mkdir, unlink, rename
from datetime import datetime
from PyQt5.Qt import PYQT_VERSION_STR
@@ -116,9 +117,6 @@ class Config:
self.showRefPanel = True
self.viewComments = True
## Path
self.recentList = [""]*10
# Check Qt5 Versions
verQt = splitVersionNumber(QT_VERSION_STR)
self.verQtString = QT_VERSION_STR
@@ -162,13 +160,19 @@ class Config:
self.hasEnchant = False
self.hasSymSpell = False
# Recent Cache
self.recentProj = {}
return
##
# Actions
##
def initConfig(self, confPath=None):
def initConfig(self, confPath=None, dataPath=None):
"""Initialise the config class. The manual setting of confPath
and dataPath is mainly intended for the test suite.
"""
if confPath is None:
confRoot = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation)
@@ -177,11 +181,15 @@ class Config:
logger.info("Setting config from alternative path: %s" % confPath)
self.confPath = confPath
if self.verQtValue >= 50400:
dataRoot = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)
if dataPath is None:
if self.verQtValue >= 50400:
dataRoot = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)
else:
dataRoot = QStandardPaths.writableLocation(QStandardPaths.DataLocation)
self.dataPath = path.join(path.abspath(dataRoot), self.appHandle)
else:
dataRoot = QStandardPaths.writableLocation(QStandardPaths.DataLocation)
self.dataPath = path.join(path.abspath(dataRoot), self.appHandle)
logger.info("Setting data path from alternative path: %s" % dataPath)
self.dataPath = dataPath
logger.verbose("Config path: %s" % self.confPath)
logger.verbose("Data path: %s" % self.dataPath)
@@ -212,25 +220,30 @@ class Config:
self.confPath = None
# Check if config file exists
if path.isfile(path.join(self.confPath,self.confFile)):
# If it exists, load it
self.loadConfig()
else:
# If it does not exist, save a copy of the default values
self.saveConfig()
if self.confPath is not None:
if path.isfile(path.join(self.confPath,self.confFile)):
# If it exists, load it
self.loadConfig()
else:
# If it does not exist, save a copy of the default values
self.saveConfig()
# If data folder does not exist, make it.
# This assumes that the os data folder itself exists.
if not path.isdir(self.dataPath):
try:
mkdir(self.dataPath)
except Exception as e:
logger.error("Could not create folder: %s" % self.dataPath)
logger.error(str(e))
self.hasError = True
self.errData.append("Could not create folder: %s" % self.dataPath)
self.errData.append(str(e))
self.dataPath = None
if self.dataPath is not None:
if not path.isdir(self.dataPath):
try:
mkdir(self.dataPath)
except Exception as e:
logger.error("Could not create folder: %s" % self.dataPath)
logger.error(str(e))
self.hasError = True
self.errData.append("Could not create folder: %s" % self.dataPath)
self.errData.append(str(e))
self.dataPath = None
# Load recent projects cache
self.loadRecentCache()
# Check the availability of optional packages
self._checkOptionalPackages()
@@ -389,10 +402,6 @@ class Config:
self.lastPath = self._parseLine(
cnfParse, cnfSec, "lastpath", self.CNF_STR, self.lastPath
)
for i in range(10):
self.recentList[i] = self._parseLine(
cnfParse, cnfSec, "recent%d" % i,self.CNF_STR, self.recentList[i]
)
# Check Certain Values for None
self.spellLanguage = self._checkNone(self.spellLanguage)
@@ -471,8 +480,6 @@ class Config:
cnfSec = "Path"
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"lastpath", str(self.lastPath))
for i in range(10):
cnfParse.set(cnfSec,"recent%d" % i, str(self.recentList[i]))
# Write config file
try:
@@ -488,21 +495,86 @@ class Config:
return True
def loadRecentCache(self):
"""Load the cache file for recent projects.
"""
if self.dataPath is None:
return False
cacheFile = path.join(self.dataPath, nwFiles.RECENT_FILE)
self.recentProj = {}
if path.isfile(cacheFile):
try:
with open(cacheFile, mode="r", encoding="utf8") as inFile:
theJson = inFile.read()
theData = json.loads(theJson)
for projPath in theData.keys():
theEntry = theData[projPath]
theTitle = ""
lastTime = 0
wordCount = 0
if "title" in theEntry.keys():
theTitle = theEntry["title"]
if "time" in theEntry.keys():
lastTime = int(theEntry["time"])
if "words" in theEntry.keys():
wordCount = int(theEntry["words"])
self.recentProj[projPath] = {
"title" : theTitle,
"time" : lastTime,
"words" : wordCount,
}
except Exception as e:
self.hasError = True
self.errData.append("Could not load recent project cache")
self.errData.append(str(e))
return False
return True
def saveRecentCache(self):
"""Save the cache dictionary of recent projects.
"""
if self.dataPath is None:
return False
cacheFile = path.join(self.dataPath, nwFiles.RECENT_FILE)
cacheTemp = path.join(self.dataPath, nwFiles.RECENT_FILE+"~")
try:
with open(cacheTemp, mode="w+", encoding="utf8") as outFile:
outFile.write(json.dumps(self.recentProj, indent=2))
except Exception as e:
self.hasError = True
self.errData.append("Could not save recent project cache")
self.errData.append(str(e))
return False
if path.isfile(cacheFile):
unlink(cacheFile)
rename(cacheTemp, cacheFile)
return True
def updateRecentCache(self, projPath, projTitle, wordCount, saveTime):
"""Add or update recent cache information o9n a given project.
"""
self.recentProj[path.abspath(projPath)] = {
"title" : projTitle,
"time" : int(saveTime),
"words" : int(wordCount),
}
return True
##
# Setters and Getters
##
def setRecent(self, recentPath):
if recentPath == "": return
if recentPath in self.recentList[0:10]:
self.recentList.remove(recentPath)
self.recentList.insert(0,recentPath)
return
def clearRecent(self):
self.recentList = [""]*10
return
def setConfPath(self, newPath):
if newPath is None:
return True
@@ -513,6 +585,15 @@ class Config:
self.confFile = path.basename(newPath)
return True
def setDataPath(self, newPath):
if newPath is None:
return True
if not path.isdir(newPath):
logger.error("Config: Path not found. Using default data path instead.")
return False
self.dataPath = path.abspath(newPath)
return True
def setLastPath(self, lastPath):
if lastPath is None or lastPath == "":
self.lastPath = ""
@@ -547,12 +628,12 @@ class Config:
def setShowRefPanel(self, checkState):
self.showRefPanel = checkState
self.confChanged = True
return
return self.showRefPanel
def setViewComments(self, checkState):
self.viewComments = checkState
self.confChanged = True
return
return self.viewComments
def getErrData(self):
errMessage = "<br>".join(self.errData)
@@ -596,7 +677,7 @@ class Config:
if checkVal is None:
return None
if isinstance(checkVal, str):
if checkVal.lower == "none":
if checkVal.lower() == "none":
return None
return checkVal
+7 -6
View File
@@ -20,12 +20,13 @@ class nwConst():
class nwFiles():
APP_ICON = "novelWriter.svg"
PROJ_FILE = "nwProject.nwx"
PROJ_DICT = "wordlist.txt"
SESS_INFO = "sessionInfo.log"
INDEX_FILE = "tagsIndex.json"
OPTS_FILE = "guiOptions.json"
APP_ICON = "novelWriter.svg"
PROJ_FILE = "nwProject.nwx"
PROJ_DICT = "wordlist.txt"
SESS_INFO = "sessionInfo.log"
INDEX_FILE = "tagsIndex.json"
OPTS_FILE = "guiOptions.json"
RECENT_FILE = "recentProjects.json"
# END Class nwFiles
+2
View File
@@ -13,6 +13,7 @@ from nw.gui.dialogs.docsplit import GuiDocSplit
from nw.gui.dialogs.export import GuiExport
from nw.gui.dialogs.itemeditor import GuiItemEditor
from nw.gui.dialogs.projecteditor import GuiProjectEditor
from nw.gui.dialogs.projectload import GuiProjectLoad
from nw.gui.dialogs.sessionlog import GuiSessionLogView
from nw.gui.dialogs.timelineview import GuiTimeLineView
@@ -40,6 +41,7 @@ __all__ = [
"GuiExport",
"GuiItemEditor",
"GuiProjectEditor",
"GuiProjectLoad",
"GuiSessionLogView",
"GuiTimeLineView",
"GuiDocDetails",
+2
View File
@@ -6,6 +6,7 @@ from nw.gui.dialogs.docsplit import GuiDocSplit
from nw.gui.dialogs.export import GuiExport
from nw.gui.dialogs.itemeditor import GuiItemEditor
from nw.gui.dialogs.projecteditor import GuiProjectEditor
from nw.gui.dialogs.projectload import GuiProjectLoad
from nw.gui.dialogs.sessionlog import GuiSessionLogView
from nw.gui.dialogs.timelineview import GuiTimeLineView
@@ -16,6 +17,7 @@ __all__ = [
"GuiExport",
"GuiItemEditor",
"GuiProjectEditor",
"GuiProjectLoad",
"GuiSessionLogView",
"GuiTimeLineView",
]
+169
View File
@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Open Project
novelWriter GUI Open Project
================================
New and open project dialog
File History:
Created: 2020-02-26 [0.4.5]
"""
import logging
import nw
from datetime import datetime
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QPushButton, QTreeWidget,
QAbstractItemView, QTreeWidgetItem
)
from nw.common import formatInt
logger = logging.getLogger(__name__)
class GuiProjectLoad(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiProjectLoad ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.sourceItem = None
self.openPath = None
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
self.setWindowTitle("Open Project")
self.setLayout(self.outerBox)
self.guiDeco = self.theParent.theTheme.loadDecoration("nwicon", (128, 128))
self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
self.outerBox.addLayout(self.innerBox)
self.projectForm = QGridLayout()
self.projectForm.setContentsMargins(0, 0, 0, 0)
self.listBox = QTreeWidget()
self.listBox.setSelectionMode(QAbstractItemView.SingleSelection)
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
self.listBox.setColumnCount(4)
self.listBox.setHeaderLabels(["Working Title","Words","Accessed","Path"])
self.listBox.setRootIsDecorated(False)
treeHead = self.listBox.headerItem()
treeHead.setTextAlignment(1, Qt.AlignRight)
self.recentButton = QPushButton("Open")
self.recentButton.clicked.connect(self._doOpenRecent)
self.browseButton = QPushButton("Browse")
self.browseButton.clicked.connect(self._doBrowse)
self.closeButton = QPushButton("Close")
self.closeButton.clicked.connect(self._doClose)
self.projectForm.addWidget(self.listBox, 0, 0, 1, 4)
self.projectForm.addWidget(self.recentButton, 1, 1)
self.projectForm.addWidget(self.browseButton, 1, 2)
self.projectForm.addWidget(self.closeButton, 1, 3)
self.projectForm.setColumnStretch(0, 1)
self.innerBox.addLayout(self.projectForm)
self.rejected.connect(self._doClose)
self.setModal(True)
self.setMinimumWidth(750)
self.setMinimumHeight(450)
self.show()
self._populateList()
logger.debug("GuiProjectLoad initialisation complete")
return
##
# Buttons
##
def _doOpenRecent(self):
"""Close the dialog window with a recent project selected.
"""
logger.verbose("GuiProjectLoad open button clicked")
selItems = self.listBox.selectedItems()
if selItems:
self.openPath = selItems[0].text(3)
self.accept()
else:
self.openPath = None
return
def _doBrowse(self):
"""Close the dialog window with no selected path, triggering the
project browser dialog.
"""
logger.verbose("GuiProjectLoad browse button clicked")
self.openPath = None
self.accept()
return
def _doClose(self):
"""Close the dialog window without doing anything.
"""
logger.verbose("GuiProjectLoad close button clicked")
self.close()
return
##
# Internal Functions
##
def _populateList(self):
"""Populate the list box with recent project data.
"""
listOrder = []
listData = {}
for projPath in self.mainConf.recentProj.keys():
theEntry = self.mainConf.recentProj[projPath]
theTitle = ""
theTime = 0
theWords = 0
if "title" in theEntry.keys():
theTitle = theEntry["title"]
if "time" in theEntry.keys():
theTime = theEntry["time"]
if "words" in theEntry.keys():
theWords = theEntry["words"]
if theTime > 0:
listOrder.append(theTime)
listData[theTime] = [theTitle, theWords, projPath]
self.listBox.clear()
hasSelection = False
for timeStamp in sorted(listOrder, reverse=True):
newItem = QTreeWidgetItem([""]*4)
newItem.setText(0, listData[timeStamp][0])
newItem.setText(1, formatInt(listData[timeStamp][1]))
newItem.setText(2, datetime.fromtimestamp(timeStamp).strftime("%x %X"))
newItem.setText(3, listData[timeStamp][2])
newItem.setTextAlignment(1, Qt.AlignRight)
self.listBox.addTopLevelItem(newItem)
if not hasSelection:
newItem.setSelected(True)
hasSelection = True
self.listBox.resizeColumnToContents(0)
self.listBox.resizeColumnToContents(1)
self.listBox.resizeColumnToContents(2)
return
# END Class GuiProjectLoad
+27 -5
View File
@@ -45,10 +45,11 @@ class GuiIcons:
}
DECO_MAP = {
"export" : "export.svg",
"merge" : "merge.svg",
"settings" : "gear.svg",
"split" : "split.svg",
"nwicon" : ["icons", "novelWriter.svg"],
"export" : ["graphics", "export.svg"],
"merge" : ["graphics", "merge.svg"],
"settings" : ["graphics", "gear.svg"],
"split" : ["graphics", "split.svg"],
}
def __init__(self, theParent):
@@ -67,6 +68,9 @@ class GuiIcons:
return
def initIcons(self, priPath):
"""Load all icons listed in the icon map. Can be overridden by
the selected theme.
"""
self.priPath = priPath
self.secPath = self.mainConf.iconPath
@@ -78,12 +82,19 @@ class GuiIcons:
return
def loadDecoration(self, decoKey, decoSize=None):
"""Load graphical decoration element based on the decoration
map. This function always returns a QSwgWidget.
"""
if decoKey not in self.DECO_MAP:
logger.error("Decoration with name '%s' does not exist" % decoKey)
return QSvgWidget()
svgPath = path.join(self.mainConf.graphPath, self.DECO_MAP[decoKey])
svgPath = path.join(
self.mainConf.assetPath,
self.DECO_MAP[decoKey][0],
self.DECO_MAP[decoKey][1]
)
if not path.isfile(svgPath):
logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey])
return QSvgWidget()
@@ -95,11 +106,17 @@ class GuiIcons:
return svgDeco
def getIcon(self, iconKey, iconSize=None):
"""Return an icon from the icon buffer. If it doesn't exist,
return an empty icon.
"""
if iconKey in self.qIcons:
return self.qIcons[iconKey]
return QIcon()
def getPixmap(self, iconKey, iconSize):
"""Return an icon from the icon buffer as a QPixmap. If it
doesn't exist, return an empty QPixmap.
"""
if iconKey in self.qIcons:
return self.qIcons[iconKey].pixmap(iconSize[0], iconSize[1], QIcon.Normal)
return QPixmap()
@@ -109,6 +126,11 @@ class GuiIcons:
##
def _loadIcon(self, iconKey):
"""Load an icon from the assets or theme folder, with a
preference for dark/light icons depending on theme type, if such
an icon exists. Prefer svg files over png files. Always returns
a QIcon.
"""
if iconKey not in self.ICON_MAP:
logger.error("Icon with name '%s' does not exist" % iconKey)
+1 -39
View File
@@ -48,11 +48,6 @@ class GuiMainMenu(QMenuBar):
return
def openRecentProject(self, menuItem, recentItem):
logger.verbose("User requested opening recent project #%d" % recentItem)
self.theParent.openProject(self.mainConf.recentList[recentItem])
return True
def setAvailableRoot(self):
for itemClass in nwItemClass:
if itemClass == nwItemClass.NO_CLASS: continue
@@ -66,30 +61,6 @@ class GuiMainMenu(QMenuBar):
# Update Menu on Settings Changed
##
def updateMenu(self):
self.updateRecentProjects()
return
def updateRecentProjects(self):
self.recentMenu.clear()
for n in range(len(self.mainConf.recentList)):
recentProject = self.mainConf.recentList[n]
if recentProject == "": continue
menuItem = QAction("%s" % recentProject, self.projMenu)
menuItem.triggered.connect(
lambda a1=menuItem, a2=n : self.openRecentProject(a1, a2)
)
self.recentMenu.addAction(menuItem)
self.recentMenu.addSeparator()
menuItem = QAction("Clear Recent Projects", self)
menuItem.setStatusTip("Clear the list of recent projects")
menuItem.triggered.connect(self._clearRecentProjects)
self.recentMenu.addAction(menuItem)
return
def setSpellCheck(self, theMode):
"""Set the spell check check box to theMode. This is controlled
by the document editor class, which holds the master spell check
@@ -166,11 +137,6 @@ class GuiMainMenu(QMenuBar):
QDesktopServices.openUrl(QUrl(nw.__docurl__))
return True
def _clearRecentProjects(self):
self.mainConf.clearRecent()
self.updateRecentProjects()
return True
def _showDocumentLocation(self):
self.theParent.docEditor.revealLocation()
return True
@@ -194,7 +160,7 @@ class GuiMainMenu(QMenuBar):
self.aOpenProject = QAction("Open Project", self)
self.aOpenProject.setStatusTip("Open project")
self.aOpenProject.setShortcut("Ctrl+Shift+O")
self.aOpenProject.triggered.connect(lambda : self.theParent.openProject(None))
self.aOpenProject.triggered.connect(self.theParent.manageProjects)
self.projMenu.addAction(self.aOpenProject)
# Project > Save Project
@@ -211,10 +177,6 @@ class GuiMainMenu(QMenuBar):
self.aCloseProject.triggered.connect(lambda : self.theParent.closeProject(False))
self.projMenu.addAction(self.aCloseProject)
# Project > Recent Projects
self.recentMenu = self.projMenu.addMenu("Recent Projects")
self.updateRecentProjects()
# Project > Project Settings
self.aProjectSettings = QAction("Project Settings", self)
self.aProjectSettings.setStatusTip("Project settings")
+26 -9
View File
@@ -27,7 +27,7 @@ from nw.gui import (
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor, GuiExport,
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
GuiConfigEditor, GuiProjectEditor, GuiItemEditor, GuiTimeLineView,
GuiSessionLogView, GuiDocMerge, GuiDocSplit
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad
)
from nw.project import NWProject, NWDoc, NWItem, NWIndex, NWBackup
from nw.tools import countWords
@@ -64,7 +64,7 @@ class GuiMain(QMainWindow):
self.resize(*self.mainConf.winGeometry)
self._setWindowTitle()
self.setWindowIcon(QIcon(path.join(self.mainConf.appIcon)))
self.setWindowIcon(QIcon(self.mainConf.appIcon))
# Main GUI Elements
self.statusBar = GuiMainStatus(self)
@@ -183,6 +183,8 @@ class GuiMain(QMainWindow):
if self.mainConf.cmdOpen is not None:
logger.debug("Opening project from additional command line option")
self.openProject(self.mainConf.cmdOpen)
else:
self.manageProjects()
return
@@ -204,13 +206,30 @@ class GuiMain(QMainWindow):
# Project Actions
##
def manageProjects(self):
"""Opens the projects dialog for selecting either existing
projects from a cache of recently opened projects, or provide a
browse button for projects not yet cached.
"""
if not self.mainConf.showGUI:
return False
dlgProj = GuiProjectLoad(self)
dlgProj.exec_()
if dlgProj.result() == QDialog.Accepted:
self.openProject(dlgProj.openPath)
return True
def newProject(self, projPath=None, forceNew=False):
"""Create new project with a few default files and folders.
"""
if self.hasProject:
msgBox = QMessageBox()
msgRes = msgBox.warning(
self, "New Project",
"Please close the current project<br>before making a new one."
"Please close the current project before making a new one."
)
return False
@@ -223,7 +242,7 @@ class GuiMain(QMainWindow):
msgBox = QMessageBox()
msgRes = msgBox.critical(
self, "New Project",
"A project already exists in that location.<br>Please choose another folder."
"A project already exists in that location. Please choose another folder."
)
return False
@@ -238,9 +257,9 @@ class GuiMain(QMainWindow):
return True
def closeProject(self, isYes=False):
"""Closes the project if one is open.
isYes is passed on from the close application event so the user
doesn't get prompted twice.
"""Closes the project if one is open. isYes is passed on from
the close application event so the user doesn't get prompted
twice.
"""
if not self.hasProject:
# There is no project loaded, everything OK
@@ -314,7 +333,6 @@ class GuiMain(QMainWindow):
self.docEditor.setDictionaries()
self.docEditor.setSpellCheck(self.theProject.spellCheck)
self.statusBar.setRefTime(self.theProject.projOpened)
self.mainMenu.updateMenu()
# Restore previously open documents, if any
if self.theProject.lastEdited is not None:
@@ -344,7 +362,6 @@ class GuiMain(QMainWindow):
self.treeView.saveTreeOrder()
self.theProject.saveProject()
self.theIndex.saveIndex()
self.mainMenu.updateRecentProjects()
return True
+13 -3
View File
@@ -297,7 +297,11 @@ class NWProject():
self._appendItem(tHandle,pHandle,nwItem)
self.optState.loadSettings()
self.mainConf.setRecent(self.projPath)
# Update recent projects
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
self.mainConf.saveRecentCache()
self.theParent.setStatus("Opened Project: %s" % self.projName)
self._scanProjectFolder()
@@ -319,6 +323,7 @@ class NWProject():
return False
self.projMeta = path.join(self.projPath,"meta")
saveTime = time()
if not self._checkFolder(self.projPath): return
if not self._checkFolder(self.projMeta): return
@@ -330,7 +335,7 @@ class NWProject():
nwXML = etree.Element("novelWriterXML",attrib={
"appVersion" : str(nw.__version__),
"fileVersion" : "1.0",
"timeStamp" : datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"timeStamp" : datetime.fromtimestamp(saveTime).strftime("%Y-%m-%d %H:%M:%S"),
})
# Save Project Meta
@@ -386,8 +391,13 @@ class NWProject():
rename(saveFile, backFile)
rename(tempFile, saveFile)
# Save project GUI options
self.optState.saveSettings()
self.mainConf.setRecent(self.projPath)
# Update recent projects
self.mainConf.updateRecentCache(self.projPath, self.projName, self.currWCount, saveTime)
self.mainConf.saveRecentCache()
self.theParent.setStatus("Saved Project: %s" % self.projName)
self.setProjectChanged(False)
+1 -11
View File
@@ -1,5 +1,5 @@
[Main]
timestamp = 2019-11-19 21:49:29
timestamp = 2020-02-26 18:10:35
theme = default
syntax = default_light
guidark = False
@@ -49,14 +49,4 @@ viewcomments = True
[Path]
lastpath =
recent0 =
recent1 =
recent2 =
recent3 =
recent4 =
recent5 =
recent6 =
recent7 =
recent8 =
recent9 =
+38 -2
View File
@@ -13,7 +13,7 @@ theConf = Config()
def testConfigInit(nwTemp,nwRef):
tmpConf = path.join(nwTemp,"novelwriter.conf")
refConf = path.join(nwRef, "novelwriter.conf")
assert theConf.initConfig(nwTemp)
assert theConf.initConfig(nwTemp, nwTemp)
assert theConf.setLastPath("")
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
@@ -37,6 +37,14 @@ def testConfigSetConfPath(nwTemp):
assert theConf.confFile == "novelwriter.conf"
assert not theConf.confChanged
@pytest.mark.core
def testConfigSetDataPath(nwTemp):
assert theConf.setDataPath(None)
assert not theConf.setDataPath(path.join("somewhere","over","the","rainbow"))
assert theConf.setDataPath(nwTemp)
assert theConf.dataPath == nwTemp
assert not theConf.confChanged
@pytest.mark.core
def testConfigLoad():
assert theConf.loadConfig()
@@ -67,7 +75,7 @@ def testConfigSetTreeColWidths(nwTemp,nwRef):
assert not theConf.confChanged
@pytest.mark.core
def testConfigSetMainPanePos(nwTemp,nwRef):
def testConfigSetPanePos(nwTemp,nwRef):
tmpConf = path.join(nwTemp,"novelwriter.conf")
refConf = path.join(nwRef, "novelwriter.conf")
assert theConf.setMainPanePos([0, 0])
@@ -77,3 +85,31 @@ def testConfigSetMainPanePos(nwTemp,nwRef):
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
assert not theConf.confChanged
@pytest.mark.core
def testConfigFlags(nwTemp,nwRef):
tmpConf = path.join(nwTemp,"novelwriter.conf")
refConf = path.join(nwRef, "novelwriter.conf")
assert not theConf.setShowRefPanel(False)
assert theConf.setShowRefPanel(True)
assert not theConf.setViewComments(False)
assert theConf.setViewComments(True)
assert theConf.confChanged
assert theConf.saveConfig()
assert cmpFiles(tmpConf, refConf, [2])
assert not theConf.confChanged
@pytest.mark.core
def testConfigErrors(nwTemp):
nonPath = path.join("somewhere","over","the","rainbow")
assert theConf.initConfig(nonPath, nonPath)
assert theConf.hasError
assert not theConf.loadConfig()
assert not theConf.saveConfig()
assert not theConf.loadRecentCache()
assert len(theConf.getErrData()) > 0
@pytest.mark.core
def testConfigInternals():
assert theConf._checkNone(None) is None
assert theConf._checkNone("None") is None
+8 -8
View File
@@ -18,8 +18,8 @@ keyDelay = 10
stepDelay = 50
@pytest.mark.gui
def testMainWindows(qtbot, nwTempGUI, nwRef):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI])
def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -254,8 +254,8 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
# qtbot.stopForInteraction()
@pytest.mark.gui
def testTimeLineView(qtbot, nwTempGUI, nwRef):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI])
def testTimeLineView(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -276,8 +276,8 @@ def testTimeLineView(qtbot, nwTempGUI, nwRef):
nwGUI.closeMain()
@pytest.mark.gui
def testProjectEditor(qtbot, nwTempGUI, nwRef):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI])
def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
@@ -363,8 +363,8 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef):
# qtbot.stopForInteraction()
@pytest.mark.gui
def testItemEditor(qtbot, nwTempGUI, nwRef):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI])
def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
+2 -2
View File
@@ -22,10 +22,10 @@ theProject = NWProject(theMain)
theProject.handleSeed = 42
@pytest.mark.project
def testProjectNew(nwTempProj,nwRef):
def testProjectNew(nwTempProj,nwRef,nwTemp):
projFile = path.join(nwTempProj,"nwProject.nwx")
refFile = path.join(nwRef,"proj","1_nwProject.nwx")
assert theConf.initConfig(nwRef)
assert theConf.initConfig(nwRef, nwTemp)
assert theProject.newProject()
assert theProject.setProjectPath(nwTempProj)
assert theProject.saveProject()