Merge pull request #21 from vkbo/improvements

Improvements
This commit is contained in:
Veronica K. Berglyd Olsen
2019-05-26 17:14:37 +02:00
committed by GitHub
21 changed files with 397 additions and 305 deletions
+94 -102
View File
@@ -25,6 +25,11 @@ class Config:
WIN_WIDTH = 0
WIN_HEIGHT = 1
CNF_STR = 0
CNF_INT = 1
CNF_BOOL = 2
CNF_LIST = 3
def __init__(self):
# Set Application Variables
@@ -38,6 +43,7 @@ class Config:
self.confFile = None
self.homePath = None
self.appPath = None
self.appRoot = None
self.guiPath = None
self.themePath = None
@@ -60,6 +66,7 @@ class Config:
self.textWidth = 600
self.textMargin = [40, 40]
self.textSize = 13
self.tabWidth = 40
self.doJustify = True
self.autoSelect = True
self.doReplace = True
@@ -68,7 +75,7 @@ class Config:
self.doReplaceDash = True
self.doReplaceDots = True
self.wordCountTimer = 5.0
self.fmtDoubleQuotes = ["",""]
self.fmtSingleQuotes = ["",""]
self.fmtApostrophe = ""
@@ -95,6 +102,7 @@ class Config:
self.confFile = self.appHandle+".conf"
self.homePath = path.expanduser("~")
self.appPath = path.dirname(__file__)
self.appRoot = path.join(self.appPath,path.pardir)
self.guiPath = path.join(self.appPath,"gui")
self.themePath = path.join(self.appPath,"themes")
@@ -117,140 +125,105 @@ class Config:
def loadConfig(self):
logger.debug("Loading config file")
confParser = configparser.ConfigParser()
cnfParse = configparser.ConfigParser()
try:
confParser.read_file(open(path.join(self.confPath,self.confFile)))
cnfParse.read_file(open(path.join(self.confPath,self.confFile)))
except Exception as e:
logger.error("Could not load config file")
return False
# Get options
## Main
cnfSec = "Main"
if confParser.has_section(cnfSec):
if confParser.has_option(cnfSec,"theme"):
self.guiTheme = confParser.get(cnfSec,"theme")
self.guiTheme = self._parseLine(cnfParse, cnfSec, "theme", self.CNF_STR, self.guiTheme)
## Sizes
cnfSec = "Sizes"
if confParser.has_section(cnfSec):
if confParser.has_option(cnfSec,"geometry"):
self.winGeometry = self.unpackList(
confParser.get(cnfSec,"geometry"), 2, self.winGeometry
)
if confParser.has_option(cnfSec,"treecols"):
self.treeColWidth = self.unpackList(
confParser.get(cnfSec,"treecols"), 3, self.treeColWidth
)
if confParser.has_option(cnfSec,"mainpane"):
self.mainPanePos = self.unpackList(
confParser.get(cnfSec,"mainpane"), 2, self.mainPanePos
)
if confParser.has_option(cnfSec,"docpane"):
self.docPanePos = self.unpackList(
confParser.get(cnfSec,"docpane"), 2, self.docPanePos
)
self.winGeometry = self._parseLine(cnfParse, cnfSec, "geometry", self.CNF_LIST, self.winGeometry)
self.treeColWidth = self._parseLine(cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth)
self.mainPanePos = self._parseLine(cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos)
self.docPanePos = self._parseLine(cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos)
## Project
cnfSec = "Project"
if confParser.has_section(cnfSec):
if confParser.has_option(cnfSec,"autosaveproject"):
self.autoSaveProj = confParser.getint(cnfSec,"autosaveproject")
if confParser.has_option(cnfSec,"autosavedoc"):
self.autoSaveDoc = confParser.getint(cnfSec,"autosavedoc")
self.autoSaveProj = self._parseLine(cnfParse, cnfSec, "autosaveproject", self.CNF_INT, self.autoSaveProj)
self.autoSaveDoc = self._parseLine(cnfParse, cnfSec, "autosavedoc", self.CNF_INT, self.autoSaveDoc)
## Editor
cnfSec = "Editor"
if confParser.has_section(cnfSec):
if confParser.has_option(cnfSec,"fixedwidth"):
self.textFixedW = confParser.getboolean(cnfSec,"fixedwidth")
if confParser.has_option(cnfSec,"width"):
self.textWidth = confParser.getint(cnfSec,"width")
if confParser.has_option(cnfSec,"margins"):
self.textMargin = self.unpackList(
confParser.get(cnfSec,"margins"), 2, self.textMargin
)
if confParser.has_option(cnfSec,"textsize"):
self.textSize = confParser.getint(cnfSec,"textsize")
if confParser.has_option(cnfSec,"justify"):
self.doJustify = confParser.getboolean(cnfSec,"justify")
if confParser.has_option(cnfSec,"autoselect"):
self.autoSelect = confParser.getboolean(cnfSec,"autoselect")
if confParser.has_option(cnfSec,"autoreplace"):
self.doReplace = confParser.getboolean(cnfSec,"autoreplace")
if confParser.has_option(cnfSec,"repsquotes"):
self.doReplaceSQuote = confParser.getboolean(cnfSec,"repsquotes")
if confParser.has_option(cnfSec,"repdquotes"):
self.doReplaceDQuote = confParser.getboolean(cnfSec,"repdquotes")
if confParser.has_option(cnfSec,"repdash"):
self.doReplaceDash = confParser.getboolean(cnfSec,"repdash")
if confParser.has_option(cnfSec,"repdots"):
self.doReplaceDots = confParser.getboolean(cnfSec,"repdots")
if confParser.has_option(cnfSec,"spellcheck"):
self.spellLanguage = confParser.get(cnfSec,"spellcheck")
self.textFixedW = self._parseLine(cnfParse, cnfSec, "fixedwidth", self.CNF_BOOL, self.textFixedW)
self.textWidth = self._parseLine(cnfParse, cnfSec, "width", self.CNF_INT, self.textWidth)
self.textMargin = self._parseLine(cnfParse, cnfSec, "margins", self.CNF_LIST, self.textMargin)
self.textSize = self._parseLine(cnfParse, cnfSec, "textsize", self.CNF_INT, self.textSize)
self.tabWidth = self._parseLine(cnfParse, cnfSec, "tabwidth", self.CNF_INT, self.tabWidth)
self.doJustify = self._parseLine(cnfParse, cnfSec, "justify", self.CNF_BOOL, self.doJustify)
self.autoSelect = self._parseLine(cnfParse, cnfSec, "autoselect", self.CNF_BOOL, self.autoSelect)
self.doReplace = self._parseLine(cnfParse, cnfSec, "autoreplace", self.CNF_BOOL, self.doReplace)
self.doReplaceSQuote = self._parseLine(cnfParse, cnfSec, "repsquotes", self.CNF_BOOL, self.doReplaceSQuote)
self.doReplaceDQuote = self._parseLine(cnfParse, cnfSec, "repdquotes", self.CNF_BOOL, self.doReplaceDQuote)
self.doReplaceDash = self._parseLine(cnfParse, cnfSec, "repdash", self.CNF_BOOL, self.doReplaceDash)
self.doReplaceDots = self._parseLine(cnfParse, cnfSec, "repdots", self.CNF_BOOL, self.doReplaceDots)
self.spellLanguage = self._parseLine(cnfParse, cnfSec, "spellcheck", self.CNF_STR, self.spellLanguage)
## Path
cnfSec = "Path"
if confParser.has_section(cnfSec):
for i in range(10):
if confParser.has_option(cnfSec,"recent%d" % i):
self.recentList[i] = confParser.get(cnfSec,"recent%d" % i)
for i in range(10):
self.recentList[i] = self._parseLine(cnfParse, cnfSec, "recent%d" % i,self.CNF_STR, self.recentList[i])
return True
def saveConfig(self):
logger.debug("Saving config file")
confParser = configparser.ConfigParser()
cnfParse = configparser.ConfigParser()
# Set options
## Main
cnfSec = "Main"
confParser.add_section(cnfSec)
confParser.set(cnfSec,"timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
confParser.set(cnfSec,"theme", str(self.guiTheme))
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
cnfParse.set(cnfSec,"theme", str(self.guiTheme))
## Sizes
cnfSec = "Sizes"
confParser.add_section(cnfSec)
confParser.set(cnfSec,"geometry", self.packList(self.winGeometry))
confParser.set(cnfSec,"treecols", self.packList(self.treeColWidth))
confParser.set(cnfSec,"mainpane", self.packList(self.mainPanePos))
confParser.set(cnfSec,"docpane", self.packList(self.docPanePos))
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"geometry", self._packList(self.winGeometry))
cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth))
cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos))
cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos))
## Project
cnfSec = "Project"
confParser.add_section(cnfSec)
confParser.set(cnfSec,"autosaveproject", str(self.autoSaveProj))
confParser.set(cnfSec,"autosavedoc", str(self.autoSaveDoc))
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"autosaveproject", str(self.autoSaveProj))
cnfParse.set(cnfSec,"autosavedoc", str(self.autoSaveDoc))
## Editor
cnfSec = "Editor"
confParser.add_section(cnfSec)
confParser.set(cnfSec,"fixedwidth", str(self.textFixedW))
confParser.set(cnfSec,"width", str(self.textWidth))
confParser.set(cnfSec,"margins", self.packList(self.textMargin))
confParser.set(cnfSec,"textsize", str(self.textSize))
confParser.set(cnfSec,"justify", str(self.doJustify))
confParser.set(cnfSec,"autoselect", str(self.autoSelect))
confParser.set(cnfSec,"autoreplace",str(self.doReplace))
confParser.set(cnfSec,"repsquotes", str(self.doReplaceSQuote))
confParser.set(cnfSec,"repdquotes", str(self.doReplaceDQuote))
confParser.set(cnfSec,"repdash", str(self.doReplaceDash))
confParser.set(cnfSec,"repdots", str(self.doReplaceDots))
confParser.set(cnfSec,"spellcheck", str(self.spellLanguage))
cnfParse.add_section(cnfSec)
cnfParse.set(cnfSec,"fixedwidth", str(self.textFixedW))
cnfParse.set(cnfSec,"width", str(self.textWidth))
cnfParse.set(cnfSec,"margins", self._packList(self.textMargin))
cnfParse.set(cnfSec,"textsize", str(self.textSize))
cnfParse.set(cnfSec,"tabwidth", str(self.tabWidth))
cnfParse.set(cnfSec,"justify", str(self.doJustify))
cnfParse.set(cnfSec,"autoselect", str(self.autoSelect))
cnfParse.set(cnfSec,"autoreplace",str(self.doReplace))
cnfParse.set(cnfSec,"repsquotes", str(self.doReplaceSQuote))
cnfParse.set(cnfSec,"repdquotes", str(self.doReplaceDQuote))
cnfParse.set(cnfSec,"repdash", str(self.doReplaceDash))
cnfParse.set(cnfSec,"repdots", str(self.doReplaceDots))
cnfParse.set(cnfSec,"spellcheck", str(self.spellLanguage))
## Path
cnfSec = "Path"
confParser.add_section(cnfSec)
cnfParse.add_section(cnfSec)
for i in range(10):
confParser.set(cnfSec,"recent%d" % i, str(self.recentList[i]))
cnfParse.set(cnfSec,"recent%d" % i, str(self.recentList[i]))
# Write config file
try:
confParser.write(open(path.join(self.confPath,self.confFile),"w"))
cnfParse.write(open(path.join(self.confPath,self.confFile),"w"))
self.confChanged = False
except Exception as e:
logger.error("Could not save config file")
@@ -258,19 +231,6 @@ class Config:
return True
def unpackList(self, inStr, listLen, listDefault, castTo=int):
inData = inStr.split(",")
outData = []
for i in range(listLen):
try:
outData.append(castTo(inData[i]))
except:
outData.append(listDefault[i])
return outData
def packList(self, inData):
return ", ".join(str(inVal) for inVal in inData)
##
# Setters
##
@@ -316,4 +276,36 @@ class Config:
self.confChanged = True
return True
##
# Internal Functions
##
def _unpackList(self, inStr, listLen, listDefault, castTo=int):
inData = inStr.split(",")
outData = []
for i in range(listLen):
try:
outData.append(castTo(inData[i]))
except:
outData.append(listDefault[i])
return outData
def _packList(self, inData):
return ", ".join(str(inVal) for inVal in inData)
def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault):
if cnfParse.has_section(cnfSec):
if cnfParse.has_option(cnfSec, cnfName):
if cnfType == self.CNF_STR:
return cnfParse.get(cnfSec, cnfName)
elif cnfType == self.CNF_INT:
return cnfParse.getint(cnfSec, cnfName)
elif cnfType == self.CNF_BOOL:
return cnfParse.getboolean(cnfSec, cnfName)
elif cnfType == self.CNF_LIST:
return self._unpackList(
cnfParse.get(cnfSec, cnfName), len(cnfDefault), cnfDefault
)
return cnfDefault
# End Class Config
+10 -2
View File
@@ -12,6 +12,15 @@
from nw.enum import nwItemClass, nwItemLayout
class nwFiles():
APP_ICON = "novelWriter.svg"
PROJ_FILE = "nwProject.nwx"
PROJ_DICT = "wordlist.txt"
SESS_INFO = "sessionInfo.log"
# END Class nwFiles
class nwLabels():
CLASS_NAME = {
@@ -59,5 +68,4 @@ class nwLabels():
nwItemLayout.NOTE : "Nt",
}
# END nwLabels
# END Class nwLabels
-77
View File
@@ -1,77 +0,0 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI About View
novelWriter GUI About View
==============================
Class holding the tab viewing the about information
File History:
Created: 2018-10-02 [0.0.1]
"""
import logging
import nw
from os import path
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFont
logger = logging.getLogger(__name__)
class GuiAboutView(QWidget):
def __init__(self):
QWidget.__init__(self)
logger.debug("Initialising AboutView ...")
self.mainConf = nw.CONFIG
self.innerBox = QHBoxLayout()
self.outerBox = QVBoxLayout()
logoPath = path.abspath(path.join(self.mainConf.appPath,"..","novelWriter.svg"))
logger.verbose("Loading image: %s" % logoPath)
nwLogo = QSvgWidget(logoPath)
nwLogo.setFixedSize(QSize(300,300))
nwName = QLabel()
nwName.setText(nw.__package__)
nwName.setAlignment(Qt.AlignCenter)
fnName = QFont()
fnName.setPointSize(22)
fnName.setBold(True)
nwName.setFont(fnName)
nwVersion = QLabel()
nwVersion.setText("Version %s" % nw.__version__)
nwVersion.setAlignment(Qt.AlignCenter)
nwCredits = QLabel()
nwCredits.setText("Created By: %s" % ",".join(nw.__credits__))
nwCredits.setAlignment(Qt.AlignCenter)
nwWebsite = QLabel()
nwWebsite.setText("<a href='%s'>%s</a>" % (nw.__website__,nw.__website__))
nwWebsite.setOpenExternalLinks(True)
nwWebsite.setAlignment(Qt.AlignCenter)
self.outerBox.addStretch(1)
self.innerBox.addStretch(1)
self.innerBox.addWidget(nwLogo)
self.innerBox.addStretch(1)
self.outerBox.addLayout(self.innerBox)
self.outerBox.addWidget(nwName)
self.outerBox.addWidget(nwVersion)
self.outerBox.addWidget(nwCredits)
self.outerBox.addWidget(nwWebsite)
self.outerBox.addStretch(1)
self.setLayout(self.outerBox)
logger.debug("AboutView initialisation complete")
return
# END Class GuiAboutView
+6 -1
View File
@@ -104,8 +104,12 @@ class GuiDocEditor(QTextEdit):
mTB = self.mainConf.textMargin[0]
mLR = self.mainConf.textMargin[1]
self.setViewportMargins(mLR,mTB,mLR,mTB)
theOpt = QTextOption()
if self.mainConf.tabWidth is not None:
theOpt.setTabStopDistance(self.mainConf.tabWidth)
if self.mainConf.doJustify:
self.theDoc.setDefaultTextOption(QTextOption(Qt.AlignJustify))
theOpt.setAlignment(Qt.AlignJustify)
self.theDoc.setDefaultTextOption(theOpt)
return True
##
@@ -376,6 +380,7 @@ class GuiDocEditor(QTextEdit):
self.paraCount = self.wCounter.paraCount
self.theParent.statusBar.setCounts(self.charCount,self.wordCount,self.paraCount)
self.theParent.treeView.propagateCount(tHandle, self.wordCount)
self.theParent.treeView.projectWordCount()
return
+2 -1
View File
@@ -194,13 +194,14 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.setCurrentBlockState(0)
if self.theDict is None or not self.spellCheck:
if self.theDict is None or not self.spellCheck or theText.startswith("@"):
return
rxSpell = self.spellRx.globalMatch(theText.replace("_"," "), 0)
while rxSpell.hasNext():
rxMatch = rxSpell.next()
if not self.theDict.check(rxMatch.captured(0)):
if rxMatch.captured(0) == rxMatch.captured(0).upper(): continue
xPos = rxMatch.capturedStart(0)
xLen = rxMatch.capturedLength(0)
spFmt = self.format(xPos)
+13 -1
View File
@@ -50,7 +50,7 @@ class GuiDocTree(QTreeWidget):
self.setExpandsOnDoubleClick(True)
self.setIndentation(13)
self.setColumnCount(4)
self.setHeaderLabels(["Name","Count","Flags","Handle"])
self.setHeaderLabels(["Name","Words","Flags","Handle"])
if not self.debugGUI:
self.hideColumn(self.C_HANDLE)
@@ -286,6 +286,18 @@ class GuiDocTree(QTreeWidget):
self.propagateCount(pHandle, pCount, nDepth+1)
return
def projectWordCount(self):
nWords = 0
for n in range(self.topLevelItemCount()):
tItem = self.topLevelItem(n)
if tItem == self.orphRoot:
continue
nWords += int(tItem.text(self.C_COUNT))
self.theProject.setProjectWordCount(nWords)
sWords = self.theProject.getSessionWordCount()
self.theParent.statusBar.setStats(nWords,sWords)
return
def buildTree(self):
self.clear()
for nwItem in self.theProject.getProjectItems():
+7
View File
@@ -253,6 +253,13 @@ class GuiMainMenu(QMenuBar):
menuItem.triggered.connect(self.theParent.saveDocument)
self.docuMenu.addAction(menuItem)
# Document > Close
menuItem = QAction(QIcon.fromTheme("document-revert"), "Close Document", self)
menuItem.setStatusTip("Close Current Document")
menuItem.setShortcut("Ctrl+W")
menuItem.triggered.connect(self.theParent.closeDocEditor)
self.docuMenu.addAction(menuItem)
# Document > Separator
self.docuMenu.addSeparator()
+83 -20
View File
@@ -13,6 +13,9 @@
import logging
import nw
from time import time
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QColor, QPixmap
from PyQt5.QtWidgets import QStatusBar, QLabel, QFrame
logger = logging.getLogger(__name__)
@@ -25,36 +28,74 @@ class GuiMainStatus(QStatusBar):
logger.debug("Initialising GuiMainStatus ...")
self.mainConf = nw.CONFIG
self.refTime = None
self.iconGrey = QPixmap(16,16)
self.iconGrey.fill(QColor(120,120,120))
self.iconYellow = QPixmap(16,16)
self.iconYellow.fill(QColor(120,120, 40))
self.iconGreen = QPixmap(16,16)
self.iconGreen.fill(QColor( 40,120, 0))
self.boxStats = QLabel()
self.boxStats.setToolTip("Project Word Count | Session Word Count")
self.boxTime = QLabel("")
self.boxTime.setToolTip("Session Time")
self.boxTime.setAlignment(Qt.AlignRight)
self.boxTime.setMinimumWidth(80)
self.boxCounts = QLabel()
self.boxCounts.setToolTip("Character, Word, Paragraph Count")
self.boxCounts.setFrameStyle(QFrame.Panel | QFrame.Sunken);
self.addPermanentWidget(self.boxCounts)
self.boxCounts.setToolTip("Document Character | Word | Paragraph Count")
self.projChanged = QLabel("P")
self.projChanged = QLabel("")
self.projChanged.setFixedHeight(16)
self.projChanged.setFixedWidth(16)
self.projChanged.setToolTip("Project Changes Saved")
self.projChanged.setFrameStyle(QFrame.Panel | QFrame.Sunken);
self.addPermanentWidget(self.projChanged)
self.docChanged = QLabel("D")
self.docChanged = QLabel("")
self.docChanged.setFixedHeight(16)
self.docChanged.setFixedWidth(16)
self.docChanged.setToolTip("Document Changes Saved")
self.docChanged.setFrameStyle(QFrame.Panel | QFrame.Sunken);
self.addPermanentWidget(self.docChanged)
self.boxDocHandle = QLabel()
self.boxDocHandle.setFrameStyle(QFrame.Panel | QFrame.Sunken);
# Add Them
self.addPermanentWidget(self.docChanged)
self.addPermanentWidget(self.boxCounts)
self.addPermanentWidget(QLabel(" "))
self.addPermanentWidget(self.projChanged)
self.addPermanentWidget(self.boxStats)
self.addPermanentWidget(self.boxTime)
if self.mainConf.debugGUI:
self.addPermanentWidget(self.boxDocHandle)
self.setSizeGripEnabled(True)
self.sessionTimer = QTimer()
self.sessionTimer.setInterval(1000)
self.sessionTimer.timeout.connect(self._updateTime)
self.sessionTimer.start()
logger.debug("GuiMainStatus initialisation complete")
self.clearStatus()
return
def clearStatus(self):
self.setRefTime(None)
self.setStats(0,0)
self.setCounts(0,0,0)
self.setDocHandleCount(None)
self.setProjectStatus(None)
self.setDocumentStatus(None)
self._updateTime()
return True
self.setSizeGripEnabled(True)
def setRefTime(self, theTime):
self.refTime = theTime
return
def setStatus(self, theMessage, timeOut=10.0):
@@ -63,28 +104,32 @@ class GuiMainStatus(QStatusBar):
def setProjectStatus(self, isChanged):
if isChanged is None:
self.projChanged.setStyleSheet("QLabel {background-color: rgba(120,120,120,1.0);}")
self.projChanged.setPixmap(self.iconGrey)
elif isChanged == True:
self.projChanged.setStyleSheet("QLabel {background-color: rgba(120,120,40,1.0);}")
self.projChanged.setPixmap(self.iconYellow)
elif isChanged == False:
self.projChanged.setStyleSheet("QLabel {background-color: rgba(40,120,0,1.0);}")
self.projChanged.setPixmap(self.iconGreen)
else:
self.projChanged.setStyleSheet("QLabel {background-color: rgba(120,120,120,1.0);}")
self.projChanged.setPixmap(self.iconGrey)
return
def setDocumentStatus(self, isChanged):
if isChanged is None:
self.docChanged.setStyleSheet("QLabel {background-color: rgba(120,120,120,1.0);}")
self.docChanged.setPixmap(self.iconGrey)
elif isChanged == True:
self.docChanged.setStyleSheet("QLabel {background-color: rgba(120,120,40,1.0);}")
self.docChanged.setPixmap(self.iconYellow)
elif isChanged == False:
self.docChanged.setStyleSheet("QLabel {background-color: rgba(40,120,0,1.0);}")
self.docChanged.setPixmap(self.iconGreen)
else:
self.docChanged.setStyleSheet("QLabel {background-color: rgba(120,120,120,1.0);}")
self.docChanged.setPixmap(self.iconGrey)
return
def setStats(self, pWC, sWC):
self.boxStats.setText("<b>Project:</b> {:d} : {:d}".format(pWC,sWC))
return
def setCounts(self, cC, wC, pC):
self.boxCounts.setText("<b>C:</b> {:n}&nbsp;&nbsp;<b>W:</b> {:n}&nbsp;&nbsp;<b>P:</b> {:n}".format(cC,wC,pC))
self.boxCounts.setText("<b>Document:</b> {:d} : {:d} : {:d}".format(cC,wC,pC))
return
def setDocHandleCount(self, theHandle):
@@ -94,4 +139,22 @@ class GuiMainStatus(QStatusBar):
self.boxDocHandle.setText("%13s" % theHandle)
return
##
# Internal Functions
##
def _updateTime(self):
if self.refTime is None:
theTime = "00:00:00"
else:
# This is much faster than using datetime format
tS = int(time() - self.refTime)
tM = int(tS/60)
tH = int(tM/60)
tM = tM - tH*60
tS = tS - tM*60 - tH*3600
theTime = "%02d:%02d:%02d" % (tH,tM,tS)
self.boxTime.setText(theTime)
return
# END Class GuiMainStatus
+85 -75
View File
@@ -36,6 +36,7 @@ from nw.project.item import NWItem
from nw.convert.tokenizer import Tokenizer
from nw.convert.tohtml import ToHtml
from nw.enum import nwItemType, nwAlert
from nw.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -53,7 +54,7 @@ class GuiMain(QMainWindow):
self.resize(*self.mainConf.winGeometry)
self._setWindowTitle()
self.setWindowIcon(QIcon(path.join(self.mainConf.appPath,"..","novelWriter.svg")))
self.setWindowIcon(QIcon(path.join(self.mainConf.appRoot, nwFiles.APP_ICON)))
self.theTheme.loadTheme()
# Main GUI Elements
@@ -139,43 +140,11 @@ class GuiMain(QMainWindow):
return
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
"""Alert both the user and the logger at the same time. Message can be either a string or an
array of strings. Severity level is 0 = info, 1 = warning, and 2 = error.
"""
if isinstance(theMessage, list):
popMsg = "<br>".join(theMessage)
logMsg = theMessage
else:
popMsg = theMessage
logMsg = [theMessage]
msgBox = QMessageBox()
if theLevel == nwAlert.INFO:
for msgLine in logMsg:
logger.info(msgLine)
msgBox.information(self, "Information", popMsg)
elif theLevel == nwAlert.WARN:
for msgLine in logMsg:
logger.warning(msgLine)
msgBox.warning(self, "Warning", popMsg)
elif theLevel == nwAlert.ERROR:
for msgLine in logMsg:
logger.error(msgLine)
msgBox.critical(self, "Error", popMsg)
elif theLevel == nwAlert.BUG:
for msgLine in logMsg:
logger.error(msgLine)
popMsg += "<br>This is a bug!"
msgBox.critical(self, "Internal Error", popMsg)
return
def clearGUI(self):
self.treeView.clearTree()
self.docEditor.clearEditor()
self.closeDocViewer()
self.statusBar.clearStatus()
return True
##
@@ -211,19 +180,22 @@ class GuiMain(QMainWindow):
self.rebuildTree()
self.saveProject()
self.hasProject = True
self.statusBar.setRefTime(self.theProject.projOpened)
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.
"""
if not self.hasProject:
# There is no project loaded, everything OK
return True
if not isYes:
if self.mainConf.showGUI and not isYes:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Close Project",
"Close current project?<br>Unsaved changes will be saved."
self, "Close Project", "Save changes and close current project?"
)
if msgRes != QMessageBox.Yes:
return False
@@ -235,29 +207,38 @@ class GuiMain(QMainWindow):
saveOK = True
if saveOK:
self.theProject.clearProject()
self.theProject.closeProject()
self.clearGUI()
self.hasProject = False
return saveOK
def openProject(self, projFile=None):
"""Open a project.
projFile is passed from the open recent projects menu, so can be set. If not, we pop the dialog.
"""
if projFile is None:
projFile = self.openProjectDialog()
if projFile is None:
return False
# Make sure any open project is cleared out first before we load another one
if not self.closeProject():
return False
self.theProject.openProject(projFile)
# Try to open the project
if not self.theProject.openProject(projFile):
return False
# Update GUI
self._setWindowTitle(self.theProject.projName)
self.rebuildTree()
self.docEditor.setPwl(path.join(self.theProject.projMeta,"wordlist.txt"))
self.docEditor.setPwl(path.join(self.theProject.projMeta, nwFiles.PROJ_DICT))
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:
self.openDocument(self.theProject.lastEdited)
if self.theProject.lastViewed is not None:
@@ -268,7 +249,9 @@ class GuiMain(QMainWindow):
return True
def saveProject(self):
"""Save the current project.
"""
# If the project is new, it may not have a path, so we need one
if self.theProject.projPath is None:
projPath = self.saveProjectDialog()
self.theProject.setProjectPath(projPath)
@@ -286,13 +269,14 @@ class GuiMain(QMainWindow):
##
def closeDocument(self):
self.saveDocument()
if self.docEditor.docChanged:
self.saveDocument()
self.theDocument.clearDocument()
self.docEditor.clearEditor()
return True
def openDocument(self, tHandle):
if self.docEditor.docChanged:
self.saveDocument()
self.closeDocument()
self.docEditor.setText(self.theDocument.openDocument(tHandle))
self.docEditor.setReadOnly(False)
self.docEditor.setCursorPosition(self.theDocument.theItem.cursorPos)
@@ -303,13 +287,13 @@ class GuiMain(QMainWindow):
def saveDocument(self):
if self.theDocument.theItem is not None:
docHtml = self.docEditor.getText()
docText = self.docEditor.getText()
cursPos = self.docEditor.getCursorPosition()
self.theDocument.theItem.setCharCount(self.docEditor.charCount)
self.theDocument.theItem.setWordCount(self.docEditor.wordCount)
self.theDocument.theItem.setParaCount(self.docEditor.paraCount)
self.theDocument.theItem.setCursorPos(cursPos)
self.theDocument.saveDocument(docHtml)
self.theDocument.saveDocument(docText)
self.docEditor.setDocumentChanged(False)
return True
@@ -393,7 +377,9 @@ class GuiMain(QMainWindow):
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
projFile, _ = QFileDialog.getOpenFileName(
self,"Open novelWriter Project","","novelWriter Project File (nwProject.nwx);;All Files (*)",options=dlgOpt
self, "Open novelWriter Project", "",
"novelWriter Project File (%s);;All Files (*)" % nwFiles.PROJ_FILE,
options=dlgOpt
)
if projFile:
return projFile
@@ -427,26 +413,56 @@ class GuiMain(QMainWindow):
self._setWindowTitle(self.theProject.projName)
return True
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
"""Alert both the user and the logger at the same time. Message can be either a string or an
array of strings. Severity level is 0 = info, 1 = warning, and 2 = error.
"""
if isinstance(theMessage, list):
popMsg = "<br>".join(theMessage)
logMsg = theMessage
else:
popMsg = theMessage
logMsg = [theMessage]
msgBox = QMessageBox()
if theLevel == nwAlert.INFO:
for msgLine in logMsg:
logger.info(msgLine)
msgBox.information(self, "Information", popMsg)
elif theLevel == nwAlert.WARN:
for msgLine in logMsg:
logger.warning(msgLine)
msgBox.warning(self, "Warning", popMsg)
elif theLevel == nwAlert.ERROR:
for msgLine in logMsg:
logger.error(msgLine)
msgBox.critical(self, "Error", popMsg)
elif theLevel == nwAlert.BUG:
for msgLine in logMsg:
logger.error(msgLine)
popMsg += "<br>This is a bug!"
msgBox.critical(self, "Internal Error", popMsg)
return
##
# Main Window Actions
##
def closeMain(self, isYes=False):
def closeMain(self):
if not isYes:
if self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Exit",
"Do you want to exit %s?" % nw.__package__
self, "Exit", "Do you want to save changes and exit?"
)
if msgRes != QMessageBox.Yes:
return False
logger.info("Exiting %s" % nw.__package__)
if self._takeDocumentAction():
self.saveDocument()
if self._takeProjectAction():
self.saveProject()
self.closeProject(True)
self.mainConf.setWinSize(self.width(), self.height())
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
self.mainConf.setMainPanePos(self.splitMain.sizes())
@@ -466,6 +482,11 @@ class GuiMain(QMainWindow):
self.docViewer.setFocus()
return
def closeDocEditor(self):
self.closeDocument()
self.theProject.setLastEdited(None)
return
def closeDocViewer(self):
self.docViewer.clearViewer()
self.theProject.setLastViewed(None)
@@ -488,31 +509,17 @@ class GuiMain(QMainWindow):
return True
def _autoSaveProject(self):
if self._takeProjectAction():
if self.hasProject and self.theProject.projChanged and self.theProject.projPath is not None:
logger.debug("Autosaving project")
self.saveProject()
return
def _autoSaveDocument(self):
if self._takeDocumentAction():
if self.hasProject and self.docEditor.docChanged and self.theDocument.theItem is not None:
logger.debug("Autosaving document")
self.saveDocument()
return
def _takeProjectAction(self):
if self.theProject.projPath is None:
return False
if not self.theProject.projChanged:
return False
return True
def _takeDocumentAction(self):
if self.theDocument.theItem is None:
return False
if not self.docEditor.docChanged:
return False
return True
def _makeStatusIcons(self):
self.statusIcons = {}
for sLabel, sCol, _ in self.theProject.statusItems:
@@ -541,7 +548,10 @@ class GuiMain(QMainWindow):
return
def closeEvent(self, theEvent):
self.closeMain()
if self.closeMain():
theEvent.accept()
else:
theEvent.ignore()
return
##
+53 -9
View File
@@ -19,10 +19,11 @@ from hashlib import sha256
from datetime import datetime
from time import time
from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from nw.common import checkString, checkBool
from nw.project.item import NWItem
from nw.project.status import NWStatus
from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from nw.common import checkString, checkBool, checkInt
from nw.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -34,6 +35,7 @@ class NWProject():
self.theParent = theParent
self.mainConf = self.theParent.mainConf
self.projChanged = None
self.projOpened = None
# Debug
self.handleSeed = None
@@ -59,6 +61,8 @@ class NWProject():
self.importItems = None
self.lastEdited = None
self.lastViewed = None
self.lastWCount = 0
self.currWCount = 0
# Set Defaults
self.clearProject()
@@ -119,19 +123,20 @@ class NWProject():
##
def newProject(self):
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)
return True
def clearProject(self):
self.projChanged = None
self.projOpened = None
# Project Settings
self.projTree = {}
@@ -141,7 +146,7 @@ class NWProject():
self.projPath = None
self.projMeta = None
self.projCache = None
self.projFile = "nwProject.nwx"
self.projFile = nwFiles.PROJ_FILE
self.projName = ""
self.bookTitle = ""
self.bookAuthors = []
@@ -156,13 +161,17 @@ class NWProject():
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
self.currWCount = 0
return
def openProject(self, fileName):
if not path.isfile(fileName):
fileName = path.join(fileName, "nwProject.nwx")
fileName = path.join(fileName, nwFiles.PROJ_FILE)
if not path.isfile(fileName):
self.makeAlert("File not found: %s" % fileName, nwAlert.ERROR)
return False
@@ -215,6 +224,8 @@ class NWProject():
self.lastEdited = checkString(xItem.text,None,True)
if xItem.tag == "lastViewed":
self.lastViewed = checkString(xItem.text,None,True)
if xItem.tag == "lastWordCount":
self.lastWCount = checkInt(xItem.text,0,False)
if xItem.tag == "status":
self.statusItems.unpackEntries(xItem)
if xItem.tag == "importance":
@@ -242,6 +253,7 @@ class NWProject():
self._scanProjectFolder()
self.setProjectChanged(False)
self.projOpened = time()
return True
@@ -276,9 +288,10 @@ class NWProject():
# Save Project Settings
xSettings = etree.SubElement(nwXML,"settings")
self._saveProjectValue(xSettings,"spellCheck",self.spellCheck)
self._saveProjectValue(xSettings,"lastEdited",self.lastEdited)
self._saveProjectValue(xSettings,"lastViewed",self.lastViewed)
self._saveProjectValue(xSettings,"spellCheck", self.spellCheck)
self._saveProjectValue(xSettings,"lastEdited", self.lastEdited)
self._saveProjectValue(xSettings,"lastViewed", self.lastViewed)
self._saveProjectValue(xSettings,"lastWordCount",self.currWCount)
xStatus = etree.SubElement(xSettings,"status")
self.statusItems.packEntries(xStatus)
@@ -311,6 +324,11 @@ class NWProject():
return True
def closeProject(self):
self._appendSessionStats()
self.clearProject()
return True
##
# Set Functions
##
@@ -365,6 +383,14 @@ class NWProject():
self.setProjectChanged(True)
return True
def setProjectWordCount(self, theCount):
self.currWCount = theCount
self.setProjectChanged(True)
return True
def getSessionWordCount(self):
return self.currWCount - self.lastWCount
def setStatusColours(self, newCols):
replaceMap = self.statusItems.setNewEntries(newCols)
if self.projTree is not None:
@@ -576,6 +602,24 @@ class NWProject():
return
def _appendSessionStats(self):
if self.projMeta is None:
return False
with open(path.join(self.projMeta, nwFiles.SESS_INFO), mode="a+") as outFile:
print((
"Start: {opened:s} "
"End: {closed:s} "
"Words: {words:8d}"
).format(
opened = datetime.fromtimestamp(self.projOpened).strftime("%Y-%m-%d %H:%M:%S"),
closed = datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
words = self.getSessionWordCount(),
), file=outFile)
return True
def _makeHandle(self, addSeed=""):
if self.handleSeed is None:
newSeed = str(time()) + addSeed
@@ -13,8 +13,12 @@ Some text here would look good as well, and maybe some "dialogue"?
So, this is some __text__ that weve been adding to this document. It is utterly meaningless text, _but_ since this is just dummy text, that doesnt really matter. The text is perfectly happy to live in this document regardless.
This paragraph is also meaningless. At least a bit. Its also very short.
This paragraph is also meaningless. At least a bit. Its also very short. But we could make it less meaningless if we wanted to … but we wont, will we.
This one is a bit longer. “It also has some dialogue in it” she said, before she moved on to check if the spellchecker worked. It did. “Cool,” she concluded.
@ToDo: Stuff that will be done at some point
+15
View File
@@ -0,0 +1,15 @@
Start: 2019-05-26 14:28:28 End: 2019-05-26 14:28:38 Words: -23
Start: 2019-05-26 14:31:12 End: 2019-05-26 14:31:34 Words: 12
Start: 2019-05-26 14:33:27 End: 2019-05-26 14:33:31 Words: 0
Start: 2019-05-26 14:40:29 End: 2019-05-26 14:40:32 Words: 0
Start: 2019-05-26 14:40:43 End: 2019-05-26 14:40:46 Words: 0
Start: 2019-05-26 14:48:40 End: 2019-05-26 14:48:50 Words: 0
Start: 2019-05-26 14:50:18 End: 2019-05-26 14:50:21 Words: 0
Start: 2019-05-26 14:50:29 End: 2019-05-26 14:50:47 Words: 3
Start: 2019-05-26 15:10:52 End: 2019-05-26 15:11:09 Words: 0
Start: 2019-05-26 15:28:56 End: 2019-05-26 15:29:08 Words: 0
Start: 2019-05-26 15:37:34 End: 2019-05-26 15:37:43 Words: -538
Start: 2019-05-26 15:37:47 End: 2019-05-26 15:38:07 Words: 538
Start: 2019-05-26 15:38:11 End: 2019-05-26 15:43:07 Words: 2
Start: 2019-05-26 15:49:18 End: 2019-05-26 15:49:47 Words: 0
Start: 2019-05-26 15:52:39 End: 2019-05-26 15:52:56 Words: 0
+6 -5
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.3" fileVersion="1.0" timeStamp="2019-05-24 00:28:28">
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-26 15:52:53">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -9,7 +9,8 @@
<settings>
<spellCheck>True</spellCheck>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastViewed>None</lastViewed>
<lastWordCount>540</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Notes</entry>
@@ -72,10 +73,10 @@
<status>Notes</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>577</charCount>
<wordCount>104</wordCount>
<charCount>656</charCount>
<wordCount>121</wordCount>
<paraCount>5</paraCount>
<cursorPos>603</cursorPos>
<cursorPos>573</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>New File</name>
+2 -1
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.3" fileVersion="1.0" timeStamp="2019-05-23 23:02:29">
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-25 23:29:02">
<project>
<name></name>
<title></title>
@@ -8,6 +8,7 @@
<spellCheck>False</spellCheck>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
+2 -1
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.3" fileVersion="1.0" timeStamp="2019-05-22 21:14:26">
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-25 23:29:32">
<project>
<name></name>
<title></title>
@@ -8,6 +8,7 @@
<spellCheck>True</spellCheck>
<lastEdited>31489056e0916</lastEdited>
<lastViewed>31489056e0916</lastViewed>
<lastWordCount>69</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
+2 -1
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.3" fileVersion="1.0" timeStamp="2019-05-22 21:35:39">
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-25 23:31:12">
<project>
<name>Project Name</name>
<title>Project Title</title>
@@ -10,6 +10,7 @@
<spellCheck>False</spellCheck>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
+2 -1
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.3" fileVersion="1.0" timeStamp="2019-05-22 21:16:34">
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-25 23:31:45">
<project>
<name></name>
<title></title>
@@ -8,6 +8,7 @@
<spellCheck>False</spellCheck>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
+2 -1
View File
@@ -1,5 +1,5 @@
[Main]
timestamp = 2019-05-18 15:06:44
timestamp = 2019-05-25 23:33:23
theme = default
[Sizes]
@@ -17,6 +17,7 @@ fixedwidth = True
width = 600
margins = 40, 40
textsize = 13
tabwidth = 40
justify = True
autoselect = True
autoreplace = True
+2 -1
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.3" fileVersion="1.0" timeStamp="2019-05-22 20:48:51">
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-25 23:25:27">
<project>
<name></name>
<title></title>
@@ -8,6 +8,7 @@
<spellCheck>False</spellCheck>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
+2 -1
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.1.3" fileVersion="1.0" timeStamp="2019-05-22 20:52:32">
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-25 23:26:15">
<project>
<name></name>
<title></title>
@@ -8,6 +8,7 @@
<spellCheck>False</spellCheck>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
+4 -4
View File
@@ -27,7 +27,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
nwGUI.theProject.handleSeed = 42
assert nwGUI.newProject(nwTempGUI, True)
assert nwGUI.saveProject()
assert nwGUI.closeProject(True)
assert nwGUI.closeProject()
assert len(nwGUI.theProject.projTree) == 0
assert len(nwGUI.theProject.treeOrder) == 0
@@ -155,7 +155,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
sceneFile = path.join(nwTempGUI,"data_3","1489056e0916_main.nwd")
assert cmpFiles(sceneFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
nwGUI.closeMain(True)
nwGUI.closeMain()
# qtbot.stopForInteraction()
@pytest.mark.gui
@@ -217,7 +217,7 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef):
projFile = path.join(nwTempGUI,"nwProject.nwx")
assert cmpFiles(projFile, path.join(nwRef,"gui","2_nwProject.nwx"), [2])
nwGUI.closeMain(True)
nwGUI.closeMain()
# qtbot.stopForInteraction()
@pytest.mark.gui
@@ -263,5 +263,5 @@ def testItemEditor(qtbot, nwTempGUI, nwRef):
projFile = path.join(nwTempGUI,"nwProject.nwx")
assert cmpFiles(projFile, path.join(nwRef,"gui","3_nwProject.nwx"), [2])
nwGUI.closeMain(True)
nwGUI.closeMain()
# qtbot.stopForInteraction()