Merge pull request #22 from vkbo/indexer
Tags Indexer and Simple TimeLine
This commit is contained in:
@@ -9,6 +9,7 @@ __pycache__
|
|||||||
sample/**/cache
|
sample/**/cache
|
||||||
sample/**/wordlist.txt
|
sample/**/wordlist.txt
|
||||||
sample/**/*.bak
|
sample/**/*.bak
|
||||||
|
sample/**/*.json
|
||||||
|
|
||||||
# PyTest
|
# PyTest
|
||||||
tests/temp
|
tests/temp
|
||||||
|
|||||||
+3
-2
@@ -151,8 +151,9 @@ def main(sysArgs):
|
|||||||
debugGUI = True
|
debugGUI = True
|
||||||
|
|
||||||
# Set Config Options
|
# Set Config Options
|
||||||
CONFIG.showGUI = not testMode
|
CONFIG.showGUI = not testMode
|
||||||
CONFIG.debugGUI = debugGUI
|
CONFIG.debugGUI = debugGUI
|
||||||
|
CONFIG.debugInfo = debugLevel < logging.INFO
|
||||||
|
|
||||||
# Set Logging
|
# Set Logging
|
||||||
if showTime: debugStr = timeStr+debugStr
|
if showTime: debugStr = timeStr+debugStr
|
||||||
|
|||||||
+19
-7
@@ -22,9 +22,6 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
|
||||||
WIN_WIDTH = 0
|
|
||||||
WIN_HEIGHT = 1
|
|
||||||
|
|
||||||
CNF_STR = 0
|
CNF_STR = 0
|
||||||
CNF_INT = 1
|
CNF_INT = 1
|
||||||
CNF_BOOL = 2
|
CNF_BOOL = 2
|
||||||
@@ -37,6 +34,7 @@ class Config:
|
|||||||
self.appHandle = nw.__package__.lower()
|
self.appHandle = nw.__package__.lower()
|
||||||
self.showGUI = True
|
self.showGUI = True
|
||||||
self.debugGUI = False
|
self.debugGUI = False
|
||||||
|
self.debugInfo = False
|
||||||
|
|
||||||
# Set Paths
|
# Set Paths
|
||||||
self.confPath = None
|
self.confPath = None
|
||||||
@@ -57,6 +55,9 @@ class Config:
|
|||||||
self.mainPanePos = [300, 800]
|
self.mainPanePos = [300, 800]
|
||||||
self.docPanePos = [400, 400]
|
self.docPanePos = [400, 400]
|
||||||
|
|
||||||
|
## Dialogs
|
||||||
|
self.dlgTimeLine = [600, 400]
|
||||||
|
|
||||||
## Project
|
## Project
|
||||||
self.autoSaveProj = 60
|
self.autoSaveProj = 60
|
||||||
self.autoSaveDoc = 30
|
self.autoSaveDoc = 30
|
||||||
@@ -142,6 +143,7 @@ class Config:
|
|||||||
self.treeColWidth = self._parseLine(cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth)
|
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.mainPanePos = self._parseLine(cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos)
|
||||||
self.docPanePos = self._parseLine(cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos)
|
self.docPanePos = self._parseLine(cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos)
|
||||||
|
self.dlgTimeLine = self._parseLine(cnfParse, cnfSec, "timeline", self.CNF_LIST, self.dlgTimeLine)
|
||||||
|
|
||||||
## Project
|
## Project
|
||||||
cnfSec = "Project"
|
cnfSec = "Project"
|
||||||
@@ -191,6 +193,7 @@ class Config:
|
|||||||
cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth))
|
cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth))
|
||||||
cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos))
|
cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos))
|
||||||
cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos))
|
cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos))
|
||||||
|
cnfParse.set(cnfSec,"timeline", self._packList(self.dlgTimeLine))
|
||||||
|
|
||||||
## Project
|
## Project
|
||||||
cnfSec = "Project"
|
cnfSec = "Project"
|
||||||
@@ -253,11 +256,20 @@ class Config:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def setWinSize(self, newWidth, newHeight):
|
def setWinSize(self, newWidth, newHeight):
|
||||||
if abs(self.winGeometry[self.WIN_WIDTH] - newWidth) >= 10:
|
if abs(self.winGeometry[0] - newWidth) > 5:
|
||||||
self.winGeometry[self.WIN_WIDTH] = newWidth
|
self.winGeometry[0] = newWidth
|
||||||
self.confChanged = True
|
self.confChanged = True
|
||||||
if abs(self.winGeometry[self.WIN_HEIGHT] - newHeight) >= 10:
|
if abs(self.winGeometry[1] - newHeight) > 5:
|
||||||
self.winGeometry[self.WIN_HEIGHT] = newHeight
|
self.winGeometry[1] = newHeight
|
||||||
|
self.confChanged = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
def setTLineSize(self, newWidth, newHeight):
|
||||||
|
if abs(self.dlgTimeLine[0] - newWidth) > 5:
|
||||||
|
self.dlgTimeLine[0] = newWidth
|
||||||
|
self.confChanged = True
|
||||||
|
if abs(self.dlgTimeLine[1] - newHeight) > 5:
|
||||||
|
self.dlgTimeLine[1] = newHeight
|
||||||
self.confChanged = True
|
self.confChanged = True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -14,10 +14,11 @@ from nw.enum import nwItemClass, nwItemLayout
|
|||||||
|
|
||||||
class nwFiles():
|
class nwFiles():
|
||||||
|
|
||||||
APP_ICON = "novelWriter.svg"
|
APP_ICON = "novelWriter.svg"
|
||||||
PROJ_FILE = "nwProject.nwx"
|
PROJ_FILE = "nwProject.nwx"
|
||||||
PROJ_DICT = "wordlist.txt"
|
PROJ_DICT = "wordlist.txt"
|
||||||
SESS_INFO = "sessionInfo.log"
|
SESS_INFO = "sessionInfo.log"
|
||||||
|
INDEX_FILE = "tagsIndex.json"
|
||||||
|
|
||||||
# END Class nwFiles
|
# END Class nwFiles
|
||||||
|
|
||||||
|
|||||||
+23
-19
@@ -34,11 +34,12 @@ class GuiDocEditor(QTextEdit):
|
|||||||
logger.debug("Initialising DocEditor ...")
|
logger.debug("Initialising DocEditor ...")
|
||||||
|
|
||||||
# Class Variables
|
# Class Variables
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.docChanged = False
|
self.docChanged = False
|
||||||
self.pwlFile = None
|
self.pwlFile = None
|
||||||
self.spellCheck = False
|
self.spellCheck = False
|
||||||
|
self.theDocument = theParent.theDocument
|
||||||
|
|
||||||
# Document Variables
|
# Document Variables
|
||||||
self.charCount = 0
|
self.charCount = 0
|
||||||
@@ -54,9 +55,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.typApos = self.mainConf.fmtApostrophe
|
self.typApos = self.mainConf.fmtApostrophe
|
||||||
|
|
||||||
# Core Elements
|
# Core Elements
|
||||||
self.theDoc = self.document()
|
self.theQDoc = self.document()
|
||||||
self.theDict = enchant.Dict(self.mainConf.spellLanguage)
|
self.theDict = enchant.Dict(self.mainConf.spellLanguage)
|
||||||
self.hLight = GuiDocHighlighter(self.theDoc, self.theParent.theTheme)
|
self.hLight = GuiDocHighlighter(self.theQDoc, self.theParent)
|
||||||
self.hLight.setDict(self.theDict)
|
self.hLight.setDict(self.theDict)
|
||||||
|
|
||||||
# Context Menu
|
# Context Menu
|
||||||
@@ -71,8 +72,8 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.clearEditor()
|
self.clearEditor()
|
||||||
self.initEditor()
|
self.initEditor()
|
||||||
|
|
||||||
self.theDoc.setDocumentMargin(0)
|
self.theQDoc.setDocumentMargin(0)
|
||||||
self.theDoc.contentsChange.connect(self._docChange)
|
self.theQDoc.contentsChange.connect(self._docChange)
|
||||||
|
|
||||||
# Custom Shortcuts
|
# Custom Shortcuts
|
||||||
QShortcut(QKeySequence("Ctrl+."), self, context=Qt.WidgetShortcut, activated=self._openSpellContext)
|
QShortcut(QKeySequence("Ctrl+."), self, context=Qt.WidgetShortcut, activated=self._openSpellContext)
|
||||||
@@ -109,7 +110,18 @@ class GuiDocEditor(QTextEdit):
|
|||||||
theOpt.setTabStopDistance(self.mainConf.tabWidth)
|
theOpt.setTabStopDistance(self.mainConf.tabWidth)
|
||||||
if self.mainConf.doJustify:
|
if self.mainConf.doJustify:
|
||||||
theOpt.setAlignment(Qt.AlignJustify)
|
theOpt.setAlignment(Qt.AlignJustify)
|
||||||
self.theDoc.setDefaultTextOption(theOpt)
|
self.theQDoc.setDefaultTextOption(theOpt)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def loadText(self, tHandle):
|
||||||
|
self.hLight.setHandle(tHandle)
|
||||||
|
self.setPlainText(self.theDocument.openDocument(tHandle))
|
||||||
|
self.setCursorPosition(self.theDocument.theItem.cursorPos)
|
||||||
|
self.lastEdit = time()
|
||||||
|
self._runCounter()
|
||||||
|
self.wcTimer.start()
|
||||||
|
self.setDocumentChanged(False)
|
||||||
|
self.setReadOnly(False)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -121,14 +133,6 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.theParent.statusBar.setDocumentStatus(self.docChanged)
|
self.theParent.statusBar.setDocumentStatus(self.docChanged)
|
||||||
return self.docChanged
|
return self.docChanged
|
||||||
|
|
||||||
def setText(self, theText):
|
|
||||||
self.setPlainText(theText)
|
|
||||||
self.lastEdit = time()
|
|
||||||
self._runCounter()
|
|
||||||
self.wcTimer.start()
|
|
||||||
self.setDocumentChanged(False)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def getText(self):
|
def getText(self):
|
||||||
theText = self.toPlainText()
|
theText = self.toPlainText()
|
||||||
return theText
|
return theText
|
||||||
@@ -296,7 +300,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
if not self.wcTimer.isActive():
|
if not self.wcTimer.isActive():
|
||||||
self.wcTimer.start()
|
self.wcTimer.start()
|
||||||
if self.mainConf.doReplace and not self.hasSelection:
|
if self.mainConf.doReplace and not self.hasSelection:
|
||||||
self._docAutoReplace(self.theDoc.findBlock(thePos))
|
self._docAutoReplace(self.theQDoc.findBlock(thePos))
|
||||||
# logger.verbose("Doc change signal took %.3f µs" % ((time()-self.lastEdit)*1e6))
|
# logger.verbose("Doc change signal took %.3f µs" % ((time()-self.lastEdit)*1e6))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
+85
-38
@@ -20,14 +20,17 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class GuiDocHighlighter(QSyntaxHighlighter):
|
class GuiDocHighlighter(QSyntaxHighlighter):
|
||||||
|
|
||||||
def __init__(self, theDoc, theTheme):
|
def __init__(self, theDoc, theParent):
|
||||||
QSyntaxHighlighter.__init__(self, theDoc)
|
QSyntaxHighlighter.__init__(self, theDoc)
|
||||||
|
|
||||||
logger.debug("Initialising DocHighlighter ...")
|
logger.debug("Initialising DocHighlighter ...")
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
self.theDoc = theDoc
|
self.theDoc = theDoc
|
||||||
self.theTheme = theTheme
|
self.theParent = theParent
|
||||||
|
self.theTheme = theParent.theTheme
|
||||||
|
self.theIndex = theParent.theIndex
|
||||||
self.theDict = None
|
self.theDict = None
|
||||||
|
self.theHandle = None
|
||||||
self.spellCheck = False
|
self.spellCheck = False
|
||||||
self.hRules = []
|
self.hRules = []
|
||||||
|
|
||||||
@@ -41,6 +44,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.colKey = QColor(*self.theTheme.colKey)
|
self.colKey = QColor(*self.theTheme.colKey)
|
||||||
self.colVal = QColor(*self.theTheme.colVal)
|
self.colVal = QColor(*self.theTheme.colVal)
|
||||||
self.colSpell = QColor(*self.theTheme.colSpell)
|
self.colSpell = QColor(*self.theTheme.colSpell)
|
||||||
|
self.colTagErr = QColor(*self.theTheme.colTagErr)
|
||||||
|
|
||||||
self.hStyles = {
|
self.hStyles = {
|
||||||
"header1" : self._makeFormat(self.colHead, "bold",1.8),
|
"header1" : self._makeFormat(self.colHead, "bold",1.8),
|
||||||
@@ -90,12 +94,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
))
|
))
|
||||||
|
|
||||||
# Keyword/Value
|
# Keyword/Value
|
||||||
self.hRules.append((
|
# self.hRules.append((
|
||||||
r"^(@.+?)\s*:\s*(.+?)$", {
|
# r"^(@.+?)\s*:\s*(.+?)$", {
|
||||||
1 : self.hStyles["keyword"],
|
# 1 : self.hStyles["keyword"],
|
||||||
2 : self.hStyles["value"],
|
# 2 : self.hStyles["value"],
|
||||||
}
|
# }
|
||||||
))
|
# ))
|
||||||
|
|
||||||
# Comments
|
# Comments
|
||||||
self.hRules.append((
|
self.hRules.append((
|
||||||
@@ -152,6 +156,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Setters
|
||||||
|
##
|
||||||
|
|
||||||
def setDict(self, theDict):
|
def setDict(self, theDict):
|
||||||
self.theDict = theDict
|
self.theDict = theDict
|
||||||
return True
|
return True
|
||||||
@@ -160,6 +168,75 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.spellCheck = theMode
|
self.spellCheck = theMode
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def setHandle(self, theHandle):
|
||||||
|
self.theHandle = theHandle
|
||||||
|
return True
|
||||||
|
|
||||||
|
##
|
||||||
|
# Highlight Block
|
||||||
|
##
|
||||||
|
|
||||||
|
def highlightBlock(self, theText):
|
||||||
|
|
||||||
|
if self.theHandle is None:
|
||||||
|
self.setCurrentBlockState(0)
|
||||||
|
return
|
||||||
|
|
||||||
|
if theText.startswith("@"):
|
||||||
|
# Highlighting of keywords and commands
|
||||||
|
tItem = self.theParent.theProject.getItem(self.theHandle)
|
||||||
|
isValid, theBits, thePos = self.theIndex.scanThis(theText)
|
||||||
|
isGood = self.theIndex.checkThese(theBits, tItem)
|
||||||
|
if isValid:
|
||||||
|
for n in range(len(theBits)):
|
||||||
|
xPos = thePos[n]
|
||||||
|
xLen = len(theBits[n])
|
||||||
|
if isGood[n]:
|
||||||
|
if n == 0:
|
||||||
|
self.setFormat(xPos, xLen, self.hStyles["keyword"])
|
||||||
|
else:
|
||||||
|
self.setFormat(xPos, xLen, self.hStyles["value"])
|
||||||
|
else:
|
||||||
|
kwFmt = self.format(xPos)
|
||||||
|
kwFmt.setUnderlineColor(self.colTagErr)
|
||||||
|
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||||
|
self.setFormat(xPos, xLen, kwFmt)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Other text just uses regex
|
||||||
|
for rX, xFmt in self.rules:
|
||||||
|
rxItt = rX.globalMatch(theText, 0)
|
||||||
|
while rxItt.hasNext():
|
||||||
|
rxMatch = rxItt.next()
|
||||||
|
for xM in xFmt.keys():
|
||||||
|
xPos = rxMatch.capturedStart(xM)
|
||||||
|
xLen = rxMatch.capturedLength(xM)
|
||||||
|
self.setFormat(xPos, xLen, xFmt[xM])
|
||||||
|
|
||||||
|
self.setCurrentBlockState(0)
|
||||||
|
|
||||||
|
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)
|
||||||
|
spFmt.setUnderlineColor(self.colSpell)
|
||||||
|
spFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||||
|
self.setFormat(xPos, xLen, spFmt)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Internal Functions
|
||||||
|
##
|
||||||
|
|
||||||
def _makeFormat(self, fmtCol=None, fmtStyle=None, fmtSize=None):
|
def _makeFormat(self, fmtCol=None, fmtStyle=None, fmtSize=None):
|
||||||
theFormat = QTextCharFormat()
|
theFormat = QTextCharFormat()
|
||||||
|
|
||||||
@@ -181,34 +258,4 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
|
|
||||||
return theFormat
|
return theFormat
|
||||||
|
|
||||||
def highlightBlock(self, theText):
|
|
||||||
|
|
||||||
for rX, xFmt in self.rules:
|
|
||||||
rxItt = rX.globalMatch(theText, 0)
|
|
||||||
while rxItt.hasNext():
|
|
||||||
rxMatch = rxItt.next()
|
|
||||||
for xM in xFmt.keys():
|
|
||||||
xPos = rxMatch.capturedStart(xM)
|
|
||||||
xLen = rxMatch.capturedLength(xM)
|
|
||||||
self.setFormat(xPos, xLen, xFmt[xM])
|
|
||||||
|
|
||||||
self.setCurrentBlockState(0)
|
|
||||||
|
|
||||||
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)
|
|
||||||
spFmt.setUnderlineColor(self.colSpell)
|
|
||||||
spFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
|
||||||
self.setFormat(xPos, xLen, spFmt)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
# END Class DocHighlighter
|
# END Class DocHighlighter
|
||||||
|
|||||||
+54
-34
@@ -135,27 +135,27 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Project > New Project
|
# Project > New Project
|
||||||
menuItem = QAction(QIcon.fromTheme("folder-new"), "New Project", self)
|
menuItem = QAction(QIcon.fromTheme("folder-new"), "New Project", self)
|
||||||
menuItem.setStatusTip("Create New Project")
|
menuItem.setStatusTip("Create new project")
|
||||||
menuItem.triggered.connect(lambda : self.theParent.newProject(None))
|
menuItem.triggered.connect(lambda : self.theParent.newProject(None))
|
||||||
self.projMenu.addAction(menuItem)
|
self.projMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Project > Open Project
|
# Project > Open Project
|
||||||
menuItem = QAction(QIcon.fromTheme("folder-open"), "Open Project", self)
|
menuItem = QAction(QIcon.fromTheme("folder-open"), "Open Project", self)
|
||||||
menuItem.setStatusTip("Open Project")
|
menuItem.setStatusTip("Open project")
|
||||||
menuItem.setShortcut("Ctrl+Shift+O")
|
menuItem.setShortcut("Ctrl+Shift+O")
|
||||||
menuItem.triggered.connect(lambda : self.theParent.openProject(None))
|
menuItem.triggered.connect(lambda : self.theParent.openProject(None))
|
||||||
self.projMenu.addAction(menuItem)
|
self.projMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Project > Save Project
|
# Project > Save Project
|
||||||
menuItem = QAction(QIcon.fromTheme("document-save"), "Save Project", self)
|
menuItem = QAction(QIcon.fromTheme("document-save"), "Save Project", self)
|
||||||
menuItem.setStatusTip("Save Project")
|
menuItem.setStatusTip("Save project")
|
||||||
menuItem.setShortcut("Ctrl+Shift+S")
|
menuItem.setShortcut("Ctrl+Shift+S")
|
||||||
menuItem.triggered.connect(self.theParent.saveProject)
|
menuItem.triggered.connect(self.theParent.saveProject)
|
||||||
self.projMenu.addAction(menuItem)
|
self.projMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Project > Close Project
|
# Project > Close Project
|
||||||
menuItem = QAction(QIcon.fromTheme("document-revert"), "Close Project", self)
|
menuItem = QAction(QIcon.fromTheme("document-revert"), "Close Project", self)
|
||||||
menuItem.setStatusTip("Close Project")
|
menuItem.setStatusTip("Close project")
|
||||||
menuItem.setShortcut("Ctrl+Shift+W")
|
menuItem.setShortcut("Ctrl+Shift+W")
|
||||||
menuItem.triggered.connect(lambda : self.theParent.closeProject(False))
|
menuItem.triggered.connect(lambda : self.theParent.closeProject(False))
|
||||||
self.projMenu.addAction(menuItem)
|
self.projMenu.addAction(menuItem)
|
||||||
@@ -166,7 +166,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Project > Project Settings
|
# Project > Project Settings
|
||||||
menuItem = QAction(QIcon.fromTheme("document-properties"), "Project Settings", self)
|
menuItem = QAction(QIcon.fromTheme("document-properties"), "Project Settings", self)
|
||||||
menuItem.setStatusTip("Project Settings")
|
menuItem.setStatusTip("Project settings")
|
||||||
menuItem.triggered.connect(self.theParent.editProjectDialog)
|
menuItem.triggered.connect(self.theParent.editProjectDialog)
|
||||||
self.projMenu.addAction(menuItem)
|
self.projMenu.addAction(menuItem)
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Project > New Folder
|
# Project > New Folder
|
||||||
menuItem = QAction(QIcon.fromTheme("folder-new"), "Create Folder", self)
|
menuItem = QAction(QIcon.fromTheme("folder-new"), "Create Folder", self)
|
||||||
menuItem.setStatusTip("Create Folder")
|
menuItem.setStatusTip("Create folder")
|
||||||
menuItem.setShortcut("Ctrl+Shift+N")
|
menuItem.setShortcut("Ctrl+Shift+N")
|
||||||
menuItem.triggered.connect(lambda : self._newTreeItem(nwItemType.FOLDER, None))
|
menuItem.triggered.connect(lambda : self._newTreeItem(nwItemType.FOLDER, None))
|
||||||
self.projMenu.addAction(menuItem)
|
self.projMenu.addAction(menuItem)
|
||||||
@@ -203,14 +203,14 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Project > Edit
|
# Project > Edit
|
||||||
menuItem = QAction(QIcon.fromTheme("document-properties"), "&Edit Item", self)
|
menuItem = QAction(QIcon.fromTheme("document-properties"), "&Edit Item", self)
|
||||||
menuItem.setStatusTip("Change Item Settings")
|
menuItem.setStatusTip("Change item settings")
|
||||||
menuItem.setShortcuts(["Ctrl+E", "F2"])
|
menuItem.setShortcuts(["Ctrl+E", "F2"])
|
||||||
menuItem.triggered.connect(self.theParent.editItem)
|
menuItem.triggered.connect(self.theParent.editItem)
|
||||||
self.projMenu.addAction(menuItem)
|
self.projMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Project > Delete
|
# Project > Delete
|
||||||
menuItem = QAction(QIcon.fromTheme("edit-delete"), "&Delete Item", self)
|
menuItem = QAction(QIcon.fromTheme("edit-delete"), "&Delete Item", self)
|
||||||
menuItem.setStatusTip("Delete Selected Item")
|
menuItem.setStatusTip("Delete selected item")
|
||||||
menuItem.setShortcut("Ctrl+Del")
|
menuItem.setShortcut("Ctrl+Del")
|
||||||
menuItem.triggered.connect(lambda : self.theParent.treeView.deleteItem(None))
|
menuItem.triggered.connect(lambda : self.theParent.treeView.deleteItem(None))
|
||||||
self.projMenu.addAction(menuItem)
|
self.projMenu.addAction(menuItem)
|
||||||
@@ -234,28 +234,28 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Document > New
|
# Document > New
|
||||||
menuItem = QAction(QIcon.fromTheme("document-new"), "&New Document", self)
|
menuItem = QAction(QIcon.fromTheme("document-new"), "&New Document", self)
|
||||||
menuItem.setStatusTip("Create New Document")
|
menuItem.setStatusTip("Create new document")
|
||||||
menuItem.setShortcut("Ctrl+N")
|
menuItem.setShortcut("Ctrl+N")
|
||||||
menuItem.triggered.connect(lambda : self._newTreeItem(nwItemType.FILE, None))
|
menuItem.triggered.connect(lambda : self._newTreeItem(nwItemType.FILE, None))
|
||||||
self.docuMenu.addAction(menuItem)
|
self.docuMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Document > Open
|
# Document > Open
|
||||||
menuItem = QAction(QIcon.fromTheme("document-open"), "&Open Document", self)
|
menuItem = QAction(QIcon.fromTheme("document-open"), "&Open Document", self)
|
||||||
menuItem.setStatusTip("Open Selected Document")
|
menuItem.setStatusTip("Open selected document")
|
||||||
menuItem.setShortcut("Ctrl+O")
|
menuItem.setShortcut("Ctrl+O")
|
||||||
menuItem.triggered.connect(self.theParent.openSelectedItem)
|
menuItem.triggered.connect(self.theParent.openSelectedItem)
|
||||||
self.docuMenu.addAction(menuItem)
|
self.docuMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Document > Save
|
# Document > Save
|
||||||
menuItem = QAction(QIcon.fromTheme("document-save"), "&Save Document", self)
|
menuItem = QAction(QIcon.fromTheme("document-save"), "&Save Document", self)
|
||||||
menuItem.setStatusTip("Save Current Document")
|
menuItem.setStatusTip("Save current document")
|
||||||
menuItem.setShortcut("Ctrl+S")
|
menuItem.setShortcut("Ctrl+S")
|
||||||
menuItem.triggered.connect(self.theParent.saveDocument)
|
menuItem.triggered.connect(self.theParent.saveDocument)
|
||||||
self.docuMenu.addAction(menuItem)
|
self.docuMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Document > Close
|
# Document > Close
|
||||||
menuItem = QAction(QIcon.fromTheme("document-revert"), "Close Document", self)
|
menuItem = QAction(QIcon.fromTheme("document-revert"), "Close Document", self)
|
||||||
menuItem.setStatusTip("Close Current Document")
|
menuItem.setStatusTip("Close current document")
|
||||||
menuItem.setShortcut("Ctrl+W")
|
menuItem.setShortcut("Ctrl+W")
|
||||||
menuItem.triggered.connect(self.theParent.closeDocEditor)
|
menuItem.triggered.connect(self.theParent.closeDocEditor)
|
||||||
self.docuMenu.addAction(menuItem)
|
self.docuMenu.addAction(menuItem)
|
||||||
@@ -265,14 +265,14 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Document > Preview
|
# Document > Preview
|
||||||
menuItem = QAction(QIcon.fromTheme("text-html"), "View Document", self)
|
menuItem = QAction(QIcon.fromTheme("text-html"), "View Document", self)
|
||||||
menuItem.setStatusTip("View Document in HTML")
|
menuItem.setStatusTip("View document as HTML")
|
||||||
menuItem.setShortcut("Ctrl+R")
|
menuItem.setShortcut("Ctrl+R")
|
||||||
menuItem.triggered.connect(lambda : self.theParent.viewDocument(None))
|
menuItem.triggered.connect(lambda : self.theParent.viewDocument(None))
|
||||||
self.docuMenu.addAction(menuItem)
|
self.docuMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Document > Close Preview
|
# Document > Close Preview
|
||||||
menuItem = QAction(QIcon.fromTheme("text-html"), "Close Document View", self)
|
menuItem = QAction(QIcon.fromTheme("text-html"), "Close Document View", self)
|
||||||
menuItem.setStatusTip("Close Document View Pane")
|
menuItem.setStatusTip("Close document view pane")
|
||||||
menuItem.setShortcut("Ctrl+Shift+R")
|
menuItem.setShortcut("Ctrl+Shift+R")
|
||||||
menuItem.triggered.connect(self.theParent.closeDocViewer)
|
menuItem.triggered.connect(self.theParent.closeDocViewer)
|
||||||
self.docuMenu.addAction(menuItem)
|
self.docuMenu.addAction(menuItem)
|
||||||
@@ -299,25 +299,35 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# View > TreeView
|
# View > TreeView
|
||||||
menuItem = QAction(QIcon.fromTheme("go-home"), "TreeView", self)
|
menuItem = QAction(QIcon.fromTheme("go-home"), "TreeView", self)
|
||||||
menuItem.setStatusTip("Move to TreeView Panel")
|
menuItem.setStatusTip("Move focus to project tree")
|
||||||
menuItem.setShortcut("Ctrl+1")
|
menuItem.setShortcut("Ctrl+1")
|
||||||
menuItem.triggered.connect(lambda : self.theParent.setFocus(1))
|
menuItem.triggered.connect(lambda : self.theParent.setFocus(1))
|
||||||
self.viewMenu.addAction(menuItem)
|
self.viewMenu.addAction(menuItem)
|
||||||
|
|
||||||
# View > Document Pane 1
|
# View > Document Pane 1
|
||||||
menuItem = QAction(QIcon.fromTheme("go-first"), "Left Document Pane", self)
|
menuItem = QAction(QIcon.fromTheme("go-first"), "Left Document Pane", self)
|
||||||
menuItem.setStatusTip("Move to Left Document Pane")
|
menuItem.setStatusTip("Move focus to left document pane")
|
||||||
menuItem.setShortcut("Ctrl+2")
|
menuItem.setShortcut("Ctrl+2")
|
||||||
menuItem.triggered.connect(lambda : self.theParent.setFocus(2))
|
menuItem.triggered.connect(lambda : self.theParent.setFocus(2))
|
||||||
self.viewMenu.addAction(menuItem)
|
self.viewMenu.addAction(menuItem)
|
||||||
|
|
||||||
# # View > Document Pane 2
|
# View > Document Pane 2
|
||||||
menuItem = QAction(QIcon.fromTheme("go-last"), "Right Document Pane", self)
|
menuItem = QAction(QIcon.fromTheme("go-last"), "Right Document Pane", self)
|
||||||
menuItem.setStatusTip("Move to Right Document Pane")
|
menuItem.setStatusTip("Move focus to right document pane")
|
||||||
menuItem.setShortcut("Ctrl+3")
|
menuItem.setShortcut("Ctrl+3")
|
||||||
menuItem.triggered.connect(lambda : self.theParent.setFocus(3))
|
menuItem.triggered.connect(lambda : self.theParent.setFocus(3))
|
||||||
self.viewMenu.addAction(menuItem)
|
self.viewMenu.addAction(menuItem)
|
||||||
|
|
||||||
|
# View > Separator
|
||||||
|
self.viewMenu.addSeparator()
|
||||||
|
|
||||||
|
# View > Project Timeline
|
||||||
|
menuItem = QAction(QIcon.fromTheme("x-office-spreadsheet"), "Show Project Timeline", self)
|
||||||
|
menuItem.setStatusTip("Open the project timeline window")
|
||||||
|
menuItem.setShortcut("Ctrl+T")
|
||||||
|
menuItem.triggered.connect(self.theParent.showTimeLineDialog)
|
||||||
|
self.viewMenu.addAction(menuItem)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _buildEditMenu(self):
|
def _buildEditMenu(self):
|
||||||
@@ -327,14 +337,14 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Edit > Undo
|
# Edit > Undo
|
||||||
menuItem = QAction(QIcon.fromTheme("edit-undo"), "Undo", self)
|
menuItem = QAction(QIcon.fromTheme("edit-undo"), "Undo", self)
|
||||||
menuItem.setStatusTip("Undo Last Change")
|
menuItem.setStatusTip("Undo last change")
|
||||||
menuItem.setShortcut("Ctrl+Z")
|
menuItem.setShortcut("Ctrl+Z")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.UNDO))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.UNDO))
|
||||||
self.editMenu.addAction(menuItem)
|
self.editMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Edit > Redo
|
# Edit > Redo
|
||||||
menuItem = QAction(QIcon.fromTheme("edit-redo"), "Redo", self)
|
menuItem = QAction(QIcon.fromTheme("edit-redo"), "Redo", self)
|
||||||
menuItem.setStatusTip("Redo Last Change")
|
menuItem.setStatusTip("Redo last change")
|
||||||
menuItem.setShortcut("Ctrl+Y")
|
menuItem.setShortcut("Ctrl+Y")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.REDO))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.REDO))
|
||||||
self.editMenu.addAction(menuItem)
|
self.editMenu.addAction(menuItem)
|
||||||
@@ -344,21 +354,21 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Edit > Cut
|
# Edit > Cut
|
||||||
menuItem = QAction(QIcon.fromTheme("edit-cut"), "Cut", self)
|
menuItem = QAction(QIcon.fromTheme("edit-cut"), "Cut", self)
|
||||||
menuItem.setStatusTip("Cut Selected Text")
|
menuItem.setStatusTip("Cut selected text")
|
||||||
menuItem.setShortcut("Ctrl+X")
|
menuItem.setShortcut("Ctrl+X")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.CUT))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.CUT))
|
||||||
self.editMenu.addAction(menuItem)
|
self.editMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Edit > Copy
|
# Edit > Copy
|
||||||
menuItem = QAction(QIcon.fromTheme("edit-copy"), "Copy", self)
|
menuItem = QAction(QIcon.fromTheme("edit-copy"), "Copy", self)
|
||||||
menuItem.setStatusTip("Copy Selected Text")
|
menuItem.setStatusTip("Copy selected text")
|
||||||
menuItem.setShortcut("Ctrl+C")
|
menuItem.setShortcut("Ctrl+C")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.COPY))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.COPY))
|
||||||
self.editMenu.addAction(menuItem)
|
self.editMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Edit > Paste
|
# Edit > Paste
|
||||||
menuItem = QAction(QIcon.fromTheme("edit-paste"), "Paste", self)
|
menuItem = QAction(QIcon.fromTheme("edit-paste"), "Paste", self)
|
||||||
menuItem.setStatusTip("Paste Text from Clipboard")
|
menuItem.setStatusTip("Paste text from clipboard")
|
||||||
menuItem.setShortcut("Ctrl+V")
|
menuItem.setShortcut("Ctrl+V")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.PASTE))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.PASTE))
|
||||||
self.editMenu.addAction(menuItem)
|
self.editMenu.addAction(menuItem)
|
||||||
@@ -368,14 +378,14 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Edit > Select All
|
# Edit > Select All
|
||||||
menuItem = QAction(QIcon.fromTheme("edit-select-all"), "Select All", self)
|
menuItem = QAction(QIcon.fromTheme("edit-select-all"), "Select All", self)
|
||||||
menuItem.setStatusTip("Select All Text in Document")
|
menuItem.setStatusTip("Select all text in document")
|
||||||
menuItem.setShortcut("Ctrl+A")
|
menuItem.setShortcut("Ctrl+A")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.SEL_ALL))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.SEL_ALL))
|
||||||
self.editMenu.addAction(menuItem)
|
self.editMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Edit > Select Paragraph
|
# Edit > Select Paragraph
|
||||||
menuItem = QAction(QIcon.fromTheme("edit-select-all"), "Select Paragraph", self)
|
menuItem = QAction(QIcon.fromTheme("edit-select-all"), "Select Paragraph", self)
|
||||||
menuItem.setStatusTip("Select All Text in Paragraph")
|
menuItem.setStatusTip("Select all text in paragraph")
|
||||||
menuItem.setShortcut("Ctrl+Shift+A")
|
menuItem.setShortcut("Ctrl+Shift+A")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.SEL_PARA))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.SEL_PARA))
|
||||||
self.editMenu.addAction(menuItem)
|
self.editMenu.addAction(menuItem)
|
||||||
@@ -389,21 +399,21 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Format > Bold Text
|
# Format > Bold Text
|
||||||
menuItem = QAction(QIcon.fromTheme("format-text-bold"), "Bold Text", self)
|
menuItem = QAction(QIcon.fromTheme("format-text-bold"), "Bold Text", self)
|
||||||
menuItem.setStatusTip("Make Selected Text Bold")
|
menuItem.setStatusTip("Make selected text bold")
|
||||||
menuItem.setShortcut("Ctrl+B")
|
menuItem.setShortcut("Ctrl+B")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.BOLD))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.BOLD))
|
||||||
self.fmtMenu.addAction(menuItem)
|
self.fmtMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Format > Italic Text
|
# Format > Italic Text
|
||||||
menuItem = QAction(QIcon.fromTheme("format-text-italic"), "Italic Text", self)
|
menuItem = QAction(QIcon.fromTheme("format-text-italic"), "Italic Text", self)
|
||||||
menuItem.setStatusTip("Make Selected Text Italic")
|
menuItem.setStatusTip("Make selected text italic")
|
||||||
menuItem.setShortcut("Ctrl+I")
|
menuItem.setShortcut("Ctrl+I")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.ITALIC))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.ITALIC))
|
||||||
self.fmtMenu.addAction(menuItem)
|
self.fmtMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Format > Underline Text
|
# Format > Underline Text
|
||||||
menuItem = QAction(QIcon.fromTheme("format-text-underline"), "Underline Text", self)
|
menuItem = QAction(QIcon.fromTheme("format-text-underline"), "Underline Text", self)
|
||||||
menuItem.setStatusTip("Underline Selected Text")
|
menuItem.setStatusTip("Underline selected text")
|
||||||
menuItem.setShortcut("Ctrl+U")
|
menuItem.setShortcut("Ctrl+U")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.U_LINE))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.U_LINE))
|
||||||
self.fmtMenu.addAction(menuItem)
|
self.fmtMenu.addAction(menuItem)
|
||||||
@@ -413,14 +423,14 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Format > Double Quotes
|
# Format > Double Quotes
|
||||||
menuItem = QAction(QIcon.fromTheme("insert-text"), "Wrap Double Quotes", self)
|
menuItem = QAction(QIcon.fromTheme("insert-text"), "Wrap Double Quotes", self)
|
||||||
menuItem.setStatusTip("Wrap Selected Text in Double Quotes")
|
menuItem.setStatusTip("Wrap selected text in double quotes")
|
||||||
menuItem.setShortcut("Ctrl+D")
|
menuItem.setShortcut("Ctrl+D")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.D_QUOTE))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.D_QUOTE))
|
||||||
self.fmtMenu.addAction(menuItem)
|
self.fmtMenu.addAction(menuItem)
|
||||||
|
|
||||||
# Format > Single Quotes
|
# Format > Single Quotes
|
||||||
menuItem = QAction(QIcon.fromTheme("insert-text"), "Wrap Single Quotes", self)
|
menuItem = QAction(QIcon.fromTheme("insert-text"), "Wrap Single Quotes", self)
|
||||||
menuItem.setStatusTip("Wrap Selected Text in Single Quotes")
|
menuItem.setStatusTip("Wrap selected text in single quotes")
|
||||||
menuItem.setShortcut("Ctrl+Shift+D")
|
menuItem.setShortcut("Ctrl+Shift+D")
|
||||||
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
|
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
|
||||||
self.fmtMenu.addAction(menuItem)
|
self.fmtMenu.addAction(menuItem)
|
||||||
@@ -434,14 +444,14 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Tools > Move Up
|
# Tools > Move Up
|
||||||
self.toolsMoveUp = QAction(QIcon.fromTheme("go-up"), "Move Tree Item Up", self)
|
self.toolsMoveUp = QAction(QIcon.fromTheme("go-up"), "Move Tree Item Up", self)
|
||||||
self.toolsMoveUp.setStatusTip("Move Item Up")
|
self.toolsMoveUp.setStatusTip("Move item up")
|
||||||
self.toolsMoveUp.setShortcut("Ctrl+Shift+Up")
|
self.toolsMoveUp.setShortcut("Ctrl+Shift+Up")
|
||||||
self.toolsMoveUp.triggered.connect(lambda : self._moveTreeItem(-1))
|
self.toolsMoveUp.triggered.connect(lambda : self._moveTreeItem(-1))
|
||||||
self.toolsMenu.addAction(self.toolsMoveUp)
|
self.toolsMenu.addAction(self.toolsMoveUp)
|
||||||
|
|
||||||
# Tools > Move Down
|
# Tools > Move Down
|
||||||
self.toolsMoveDown = QAction(QIcon.fromTheme("go-down"), "Move Tree Item Down", self)
|
self.toolsMoveDown = QAction(QIcon.fromTheme("go-down"), "Move Tree Item Down", self)
|
||||||
self.toolsMoveDown.setStatusTip("Move Item Down")
|
self.toolsMoveDown.setStatusTip("Move item down")
|
||||||
self.toolsMoveDown.setShortcut("Ctrl+Shift+Down")
|
self.toolsMoveDown.setShortcut("Ctrl+Shift+Down")
|
||||||
self.toolsMoveDown.triggered.connect(lambda : self._moveTreeItem(1))
|
self.toolsMoveDown.triggered.connect(lambda : self._moveTreeItem(1))
|
||||||
self.toolsMenu.addAction(self.toolsMoveDown)
|
self.toolsMenu.addAction(self.toolsMoveDown)
|
||||||
@@ -451,7 +461,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Tools > Toggle Spell Check
|
# Tools > Toggle Spell Check
|
||||||
self.toolsSpellCheck = QAction("Check Spelling", self)
|
self.toolsSpellCheck = QAction("Check Spelling", self)
|
||||||
self.toolsSpellCheck.setStatusTip("Toggle Check Spelling")
|
self.toolsSpellCheck.setStatusTip("Toggle check spelling")
|
||||||
self.toolsSpellCheck.setCheckable(True)
|
self.toolsSpellCheck.setCheckable(True)
|
||||||
self.toolsSpellCheck.setChecked(self.theProject.spellCheck)
|
self.toolsSpellCheck.setChecked(self.theProject.spellCheck)
|
||||||
self.toolsSpellCheck.toggled.connect(self._toggleSpellCheck)
|
self.toolsSpellCheck.toggled.connect(self._toggleSpellCheck)
|
||||||
@@ -460,11 +470,21 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
# Tools > Update Spell Check
|
# Tools > Update Spell Check
|
||||||
menuItem = QAction(QIcon.fromTheme("tools-check-spelling"), "Re-Run Spell Check", self)
|
menuItem = QAction(QIcon.fromTheme("tools-check-spelling"), "Re-Run Spell Check", self)
|
||||||
menuItem.setStatusTip("Rus the Spell Checker on Current Document")
|
menuItem.setStatusTip("Run the spell checker on current document")
|
||||||
menuItem.setShortcut("F7")
|
menuItem.setShortcut("F7")
|
||||||
menuItem.triggered.connect(self.theParent.docEditor.updateSpellCheck)
|
menuItem.triggered.connect(self.theParent.docEditor.updateSpellCheck)
|
||||||
self.toolsMenu.addAction(menuItem)
|
self.toolsMenu.addAction(menuItem)
|
||||||
|
|
||||||
|
# Tools > Separator
|
||||||
|
self.toolsMenu.addSeparator()
|
||||||
|
|
||||||
|
# Tools > Rebuild Indices
|
||||||
|
menuItem = QAction(QIcon.fromTheme("edit-redo"), "Rebuild Indices", self)
|
||||||
|
menuItem.setStatusTip("Rebuild the tag indices and word counts")
|
||||||
|
menuItem.setShortcut("F9")
|
||||||
|
menuItem.triggered.connect(self.theParent.rebuildIndex)
|
||||||
|
self.toolsMenu.addAction(menuItem)
|
||||||
|
|
||||||
# # Tools > Settings
|
# # Tools > Settings
|
||||||
# menuItem = QAction(QIcon.fromTheme("preferences-system"), "Preferences", self)
|
# menuItem = QAction(QIcon.fromTheme("preferences-system"), "Preferences", self)
|
||||||
# menuItem.setStatusTip("Preferences")
|
# menuItem.setStatusTip("Preferences")
|
||||||
|
|||||||
+2
-2
@@ -88,7 +88,7 @@ class GuiMainStatus(QStatusBar):
|
|||||||
self.setRefTime(None)
|
self.setRefTime(None)
|
||||||
self.setStats(0,0)
|
self.setStats(0,0)
|
||||||
self.setCounts(0,0,0)
|
self.setCounts(0,0,0)
|
||||||
self.setDocHandleCount(None)
|
self.setDocHandle(None)
|
||||||
self.setProjectStatus(None)
|
self.setProjectStatus(None)
|
||||||
self.setDocumentStatus(None)
|
self.setDocumentStatus(None)
|
||||||
self._updateTime()
|
self._updateTime()
|
||||||
@@ -132,7 +132,7 @@ class GuiMainStatus(QStatusBar):
|
|||||||
self.boxCounts.setText("<b>Document:</b> {:d} : {:d} : {:d}".format(cC,wC,pC))
|
self.boxCounts.setText("<b>Document:</b> {:d} : {:d} : {:d}".format(cC,wC,pC))
|
||||||
return
|
return
|
||||||
|
|
||||||
def setDocHandleCount(self, theHandle):
|
def setDocHandle(self, theHandle):
|
||||||
if theHandle is None:
|
if theHandle is None:
|
||||||
self.boxDocHandle.setText("0000000000000")
|
self.boxDocHandle.setText("0000000000000")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""novelWriter GUI Timeline View
|
||||||
|
|
||||||
|
novelWriter – GUI Timeline View
|
||||||
|
=================================
|
||||||
|
Class holding the timeline view window
|
||||||
|
|
||||||
|
File History:
|
||||||
|
Created: 2019-05-30 [0.1.4]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import nw
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtGui import QIcon, QColor, QPixmap
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
|
||||||
|
QDialogButtonBox, QLabel, QPushButton, QHeaderView
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class GuiTimeLineView(QDialog):
|
||||||
|
|
||||||
|
def __init__(self, theParent, theProject, theIndex):
|
||||||
|
QDialog.__init__(self, theParent)
|
||||||
|
|
||||||
|
logger.debug("Initialising TimeLineView ...")
|
||||||
|
|
||||||
|
self.mainConf = nw.CONFIG
|
||||||
|
self.theProject = theProject
|
||||||
|
self.theParent = theParent
|
||||||
|
self.theIndex = theIndex
|
||||||
|
self.theMatrix = {}
|
||||||
|
self.numRows = 0
|
||||||
|
self.numCols = 0
|
||||||
|
|
||||||
|
self.outerBox = QVBoxLayout()
|
||||||
|
self.bottomBox = QHBoxLayout()
|
||||||
|
|
||||||
|
self.setWindowTitle("Timeline View")
|
||||||
|
self.setMinimumSize(*self.mainConf.dlgTimeLine)
|
||||||
|
|
||||||
|
self.mainTable = QTableWidget()
|
||||||
|
self.mainTable.setGridStyle(Qt.NoPen)
|
||||||
|
|
||||||
|
self.hHeader = self.mainTable.horizontalHeader()
|
||||||
|
self.hHeader.setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||||
|
self.mainTable.setHorizontalHeader(self.hHeader)
|
||||||
|
|
||||||
|
self.vHeader = self.mainTable.verticalHeader()
|
||||||
|
self.vHeader.setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||||
|
self.mainTable.setVerticalHeader(self.vHeader)
|
||||||
|
|
||||||
|
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close)
|
||||||
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
|
|
||||||
|
self.btnRebuild = QPushButton("Rebuild Index")
|
||||||
|
self.btnRebuild.clicked.connect(self.theParent.rebuildIndex)
|
||||||
|
|
||||||
|
self.btnRefresh = QPushButton("Refresh Table")
|
||||||
|
self.btnRefresh.clicked.connect(self._buildNovelList)
|
||||||
|
|
||||||
|
self.setLayout(self.outerBox)
|
||||||
|
self.outerBox.addWidget(self.mainTable)
|
||||||
|
self.outerBox.addLayout(self.bottomBox)
|
||||||
|
self.bottomBox.addWidget(self.btnRebuild)
|
||||||
|
self.bottomBox.addWidget(self.btnRefresh)
|
||||||
|
self.bottomBox.addStretch()
|
||||||
|
self.bottomBox.addWidget(self.buttonBox)
|
||||||
|
|
||||||
|
self._buildNovelList()
|
||||||
|
self.buttonBox.setFocus()
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
logger.debug("TimeLineView initialisation complete")
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _buildNovelList(self):
|
||||||
|
|
||||||
|
self.theIndex.buildNovelList()
|
||||||
|
self.numRows = len(self.theIndex.novelList)
|
||||||
|
self.numCols = len(self.theIndex.tagIndex.keys())
|
||||||
|
|
||||||
|
self.mainTable.clear()
|
||||||
|
self.mainTable.setRowCount(self.numRows)
|
||||||
|
self.mainTable.setColumnCount(self.numCols)
|
||||||
|
|
||||||
|
for n in range(len(self.theIndex.novelList)):
|
||||||
|
iDepth = self.theIndex.novelList[n][1]
|
||||||
|
iTitle = self.theIndex.novelList[n][2]
|
||||||
|
newItem = QTableWidgetItem("%s%s " % (" "*iDepth,iTitle))
|
||||||
|
self.mainTable.setVerticalHeaderItem(n, newItem)
|
||||||
|
|
||||||
|
theMap = self.theIndex.buildTagNovelMap(self.theIndex.tagIndex.keys())
|
||||||
|
nCol = 0
|
||||||
|
for theTag, theCols in theMap.items():
|
||||||
|
newItem = QTableWidgetItem(" %s " % theTag)
|
||||||
|
self.mainTable.setHorizontalHeaderItem(nCol, newItem)
|
||||||
|
for n in range(len(theCols)):
|
||||||
|
if theCols[n] == 1:
|
||||||
|
pxNew = QPixmap(10,10)
|
||||||
|
pxNew.fill(QColor(0,120,0))
|
||||||
|
lblNew = QLabel()
|
||||||
|
lblNew.setPixmap(pxNew)
|
||||||
|
lblNew.setAlignment(Qt.AlignCenter)
|
||||||
|
lblNew.setAttribute(Qt.WA_TranslucentBackground)
|
||||||
|
self.mainTable.setCellWidget(n, nCol, lblNew)
|
||||||
|
elif theCols[n] == 2:
|
||||||
|
pxNew = QPixmap(10,10)
|
||||||
|
pxNew.fill(QColor(0,0,120))
|
||||||
|
lblNew = QLabel()
|
||||||
|
lblNew.setPixmap(pxNew)
|
||||||
|
lblNew.setAlignment(Qt.AlignCenter)
|
||||||
|
lblNew.setAttribute(Qt.WA_TranslucentBackground)
|
||||||
|
self.mainTable.setCellWidget(n, nCol, lblNew)
|
||||||
|
nCol += 1
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _doClose(self):
|
||||||
|
self.mainConf.setTLineSize(self.width(), self.height())
|
||||||
|
self.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class GuiItemEditor
|
||||||
+76
-11
@@ -11,6 +11,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from os import path
|
from os import path
|
||||||
@@ -18,7 +19,7 @@ from PyQt5.QtCore import Qt, QTimer
|
|||||||
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog,
|
qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog,
|
||||||
QShortcut, QMessageBox
|
QShortcut, QMessageBox, QProgressDialog
|
||||||
)
|
)
|
||||||
|
|
||||||
from nw.theme import Theme
|
from nw.theme import Theme
|
||||||
@@ -30,13 +31,16 @@ from nw.gui.mainmenu import GuiMainMenu
|
|||||||
from nw.gui.projecteditor import GuiProjectEditor
|
from nw.gui.projecteditor import GuiProjectEditor
|
||||||
from nw.gui.itemeditor import GuiItemEditor
|
from nw.gui.itemeditor import GuiItemEditor
|
||||||
from nw.gui.statusbar import GuiMainStatus
|
from nw.gui.statusbar import GuiMainStatus
|
||||||
|
from nw.gui.timelineview import GuiTimeLineView
|
||||||
from nw.project.project import NWProject
|
from nw.project.project import NWProject
|
||||||
from nw.project.document import NWDoc
|
from nw.project.document import NWDoc
|
||||||
from nw.project.item import NWItem
|
from nw.project.item import NWItem
|
||||||
|
from nw.project.index import NWIndex
|
||||||
from nw.convert.tokenizer import Tokenizer
|
from nw.convert.tokenizer import Tokenizer
|
||||||
from nw.convert.tohtml import ToHtml
|
from nw.convert.tohtml import ToHtml
|
||||||
from nw.enum import nwItemType, nwAlert
|
from nw.enum import nwItemType, nwAlert
|
||||||
from nw.constants import nwFiles
|
from nw.constants import nwFiles
|
||||||
|
from nw.tools.wordcount import countWords
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -50,6 +54,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.theTheme = Theme()
|
self.theTheme = Theme()
|
||||||
self.theProject = NWProject(self)
|
self.theProject = NWProject(self)
|
||||||
self.theDocument = NWDoc(self.theProject, self)
|
self.theDocument = NWDoc(self.theProject, self)
|
||||||
|
self.theIndex = NWIndex(self.theProject, self)
|
||||||
self.hasProject = False
|
self.hasProject = False
|
||||||
|
|
||||||
self.resize(*self.mainConf.winGeometry)
|
self.resize(*self.mainConf.winGeometry)
|
||||||
@@ -208,6 +213,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
if saveOK:
|
if saveOK:
|
||||||
self.theProject.closeProject()
|
self.theProject.closeProject()
|
||||||
|
self.theIndex.clearIndex()
|
||||||
self.clearGUI()
|
self.clearGUI()
|
||||||
self.hasProject = False
|
self.hasProject = False
|
||||||
|
|
||||||
@@ -230,6 +236,9 @@ class GuiMain(QMainWindow):
|
|||||||
if not self.theProject.openProject(projFile):
|
if not self.theProject.openProject(projFile):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Load the tag index
|
||||||
|
self.theIndex.loadIndex()
|
||||||
|
|
||||||
# Update GUI
|
# Update GUI
|
||||||
self._setWindowTitle(self.theProject.projName)
|
self._setWindowTitle(self.theProject.projName)
|
||||||
self.rebuildTree()
|
self.rebuildTree()
|
||||||
@@ -260,6 +269,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
self.treeView.saveTreeOrder()
|
self.treeView.saveTreeOrder()
|
||||||
self.theProject.saveProject()
|
self.theProject.saveProject()
|
||||||
|
self.theIndex.saveIndex()
|
||||||
self.mainMenu.updateRecentProjects()
|
self.mainMenu.updateRecentProjects()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -277,9 +287,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
def openDocument(self, tHandle):
|
def openDocument(self, tHandle):
|
||||||
self.closeDocument()
|
self.closeDocument()
|
||||||
self.docEditor.setText(self.theDocument.openDocument(tHandle))
|
self.docEditor.loadText(tHandle)
|
||||||
self.docEditor.setReadOnly(False)
|
|
||||||
self.docEditor.setCursorPosition(self.theDocument.theItem.cursorPos)
|
|
||||||
self.docEditor.changeWidth()
|
self.docEditor.changeWidth()
|
||||||
self.docEditor.setFocus()
|
self.docEditor.setFocus()
|
||||||
self.theProject.setLastEdited(tHandle)
|
self.theProject.setLastEdited(tHandle)
|
||||||
@@ -289,12 +297,14 @@ class GuiMain(QMainWindow):
|
|||||||
if self.theDocument.theItem is not None:
|
if self.theDocument.theItem is not None:
|
||||||
docText = self.docEditor.getText()
|
docText = self.docEditor.getText()
|
||||||
cursPos = self.docEditor.getCursorPosition()
|
cursPos = self.docEditor.getCursorPosition()
|
||||||
self.theDocument.theItem.setCharCount(self.docEditor.charCount)
|
theItem = self.theDocument.theItem
|
||||||
self.theDocument.theItem.setWordCount(self.docEditor.wordCount)
|
theItem.setCharCount(self.docEditor.charCount)
|
||||||
self.theDocument.theItem.setParaCount(self.docEditor.paraCount)
|
theItem.setWordCount(self.docEditor.wordCount)
|
||||||
self.theDocument.theItem.setCursorPos(cursPos)
|
theItem.setParaCount(self.docEditor.paraCount)
|
||||||
|
theItem.setCursorPos(cursPos)
|
||||||
self.theDocument.saveDocument(docText)
|
self.theDocument.saveDocument(docText)
|
||||||
self.docEditor.setDocumentChanged(False)
|
self.docEditor.setDocumentChanged(False)
|
||||||
|
self.theIndex.scanText(theItem.itemHandle, docText)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def viewDocument(self, tHandle=None):
|
def viewDocument(self, tHandle=None):
|
||||||
@@ -356,9 +366,10 @@ class GuiMain(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
logger.verbose("Requesting change to item %s" % tHandle)
|
logger.verbose("Requesting change to item %s" % tHandle)
|
||||||
dlgProj = GuiItemEditor(self, self.theProject, tHandle)
|
if self.mainConf.showGUI:
|
||||||
if dlgProj.exec_():
|
dlgProj = GuiItemEditor(self, self.theProject, tHandle)
|
||||||
self.treeView.setTreeItemValues(tHandle)
|
if dlgProj.exec_():
|
||||||
|
self.treeView.setTreeItemValues(tHandle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -369,6 +380,55 @@ class GuiMain(QMainWindow):
|
|||||||
self.treeView.buildTree()
|
self.treeView.buildTree()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def rebuildIndex(self):
|
||||||
|
|
||||||
|
logger.debug("Rebuilding indices ...")
|
||||||
|
|
||||||
|
self.treeView.saveTreeOrder()
|
||||||
|
self.theIndex.clearIndex()
|
||||||
|
nItems = len(self.theProject.treeOrder)
|
||||||
|
|
||||||
|
dlgProg = QProgressDialog("Scanning files ...", "Cancel", 0, nItems, self)
|
||||||
|
dlgProg.setWindowModality(Qt.WindowModal)
|
||||||
|
dlgProg.setMinimumDuration(0)
|
||||||
|
dlgProg.setFixedWidth(480)
|
||||||
|
dlgProg.setLabelText("Starting file scan ...")
|
||||||
|
dlgProg.setValue(0)
|
||||||
|
dlgProg.show()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
nDone = 0
|
||||||
|
for tHandle in self.theProject.treeOrder:
|
||||||
|
|
||||||
|
tItem = self.theProject.getItem(tHandle)
|
||||||
|
|
||||||
|
dlgProg.setValue(nDone)
|
||||||
|
dlgProg.setLabelText("Scanning: %s" % tItem.itemName)
|
||||||
|
logger.verbose("Scanning: %s" % tItem.itemName)
|
||||||
|
|
||||||
|
if tItem is not None and tItem.itemType == nwItemType.FILE:
|
||||||
|
theDoc = NWDoc(self.theProject, self)
|
||||||
|
theText = theDoc.openDocument(tHandle, False)
|
||||||
|
|
||||||
|
# Run Word Count
|
||||||
|
cC, wC, pC = countWords(theText)
|
||||||
|
tItem.setCharCount(cC)
|
||||||
|
tItem.setWordCount(wC)
|
||||||
|
tItem.setParaCount(pC)
|
||||||
|
self.treeView.propagateCount(tHandle, wC)
|
||||||
|
self.treeView.projectWordCount()
|
||||||
|
|
||||||
|
# Build tag index
|
||||||
|
self.theIndex.scanText(tHandle, theText)
|
||||||
|
|
||||||
|
nDone += 1
|
||||||
|
if dlgProg.wasCanceled():
|
||||||
|
break
|
||||||
|
|
||||||
|
dlgProg.setValue(nItems)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Main Dialogs
|
# Main Dialogs
|
||||||
##
|
##
|
||||||
@@ -413,6 +473,11 @@ class GuiMain(QMainWindow):
|
|||||||
self._setWindowTitle(self.theProject.projName)
|
self._setWindowTitle(self.theProject.projName)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def showTimeLineDialog(self):
|
||||||
|
dlgTLine = GuiTimeLineView(self, self.theProject, self.theIndex)
|
||||||
|
dlgTLine.exec_()
|
||||||
|
return True
|
||||||
|
|
||||||
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
|
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
|
"""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.
|
array of strings. Severity level is 0 = info, 1 = warning, and 2 = error.
|
||||||
|
|||||||
+7
-47
@@ -13,10 +13,10 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from time import time
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QThread
|
from PyQt5.QtCore import QThread
|
||||||
|
|
||||||
|
from nw.tools.wordcount import countWords
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class WordCounter(QThread):
|
class WordCounter(QThread):
|
||||||
@@ -31,52 +31,12 @@ class WordCounter(QThread):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
self.charCount = 0
|
theText = self.theParent.getText()
|
||||||
self.wordCount = 0
|
cC, wC, pC = countWords(theText)
|
||||||
self.paraCount = 0
|
|
||||||
|
|
||||||
prevEmpty = True
|
self.charCount = cC
|
||||||
|
self.wordCount = wC
|
||||||
for n in range(self.theParent.theDoc.blockCount()):
|
self.paraCount = pC
|
||||||
|
|
||||||
theBlock = self.theParent.theDoc.findBlockByNumber(n)
|
|
||||||
if not theBlock.isValid():
|
|
||||||
continue
|
|
||||||
|
|
||||||
countPara = True
|
|
||||||
theText = theBlock.text()
|
|
||||||
theLen = len(theText)
|
|
||||||
|
|
||||||
if theLen == 0:
|
|
||||||
prevEmpty = True
|
|
||||||
continue
|
|
||||||
if theText[0] == "@" or theText[0] == "%":
|
|
||||||
prevEmpty = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if theText[0:5] == "#### ":
|
|
||||||
self.wordCount -= 1
|
|
||||||
self.charCount -= 5
|
|
||||||
countPara = False
|
|
||||||
elif theText[0:4] == "### ":
|
|
||||||
self.wordCount -= 1
|
|
||||||
self.charCount -= 4
|
|
||||||
countPara = False
|
|
||||||
elif theText[0:3] == "## ":
|
|
||||||
self.wordCount -= 1
|
|
||||||
self.charCount -= 3
|
|
||||||
countPara = False
|
|
||||||
elif theText[0:2] == "# ":
|
|
||||||
self.wordCount -= 1
|
|
||||||
self.charCount -= 2
|
|
||||||
countPara = False
|
|
||||||
|
|
||||||
theBuff = theText.replace("–"," ").replace("—"," ")
|
|
||||||
self.wordCount += len(theBuff.split())
|
|
||||||
self.charCount += theLen
|
|
||||||
if countPara and prevEmpty:
|
|
||||||
self.paraCount += 1
|
|
||||||
prevEmpty = countPara == False
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -41,11 +41,10 @@ class NWDoc():
|
|||||||
self.docHandle = None
|
self.docHandle = None
|
||||||
return
|
return
|
||||||
|
|
||||||
def openDocument(self, tHandle):
|
def openDocument(self, tHandle, showStatus=True):
|
||||||
|
|
||||||
self.docHandle = tHandle
|
self.docHandle = tHandle
|
||||||
self.theItem = self.theProject.getItem(tHandle)
|
self.theItem = self.theProject.getItem(tHandle)
|
||||||
self.theParent.statusBar.setDocHandleCount(tHandle)
|
|
||||||
|
|
||||||
docDir, docFile = self._assemblePath(self.FILE_MN)
|
docDir, docFile = self._assemblePath(self.FILE_MN)
|
||||||
logger.debug("Opening document %s" % path.join(docDir,docFile))
|
logger.debug("Opening document %s" % path.join(docDir,docFile))
|
||||||
@@ -63,7 +62,9 @@ class NWDoc():
|
|||||||
logger.debug("The requested document does not exist.")
|
logger.debug("The requested document does not exist.")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
self.theParent.statusBar.setStatus("Opened Document: %s" % self.theItem.itemName)
|
if showStatus:
|
||||||
|
self.theParent.statusBar.setStatus("Opened Document: %s" % self.theItem.itemName)
|
||||||
|
self.theParent.statusBar.setDocHandle(tHandle)
|
||||||
|
|
||||||
return theDoc
|
return theDoc
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,347 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""novelWriter Project Index
|
||||||
|
|
||||||
|
novelWriter – Project Index
|
||||||
|
=============================
|
||||||
|
Class holding the index of tags
|
||||||
|
|
||||||
|
File History:
|
||||||
|
Created: 2019-05-27 [0.1.4]
|
||||||
|
|
||||||
|
Structure:
|
||||||
|
|
||||||
|
We need to scan all files to get the novel layout. This is done by heading depth. Each heading
|
||||||
|
is recorded in a sorted list. Each such heading again can have a set of meta tags.
|
||||||
|
In the other class root folders, each file can have a tag which the meta tags in the novel files
|
||||||
|
point to. These tags should be stored in a dictionary where the tag is the key pointing to a single
|
||||||
|
file handle. That way we can do lookups on both keys and values.
|
||||||
|
The timeline view then consists of novel header elements in the horizontal header, possibly just
|
||||||
|
truncated to a Ch or Sc abbreviation, possibly with a number. This can be extracted from the item
|
||||||
|
layout. The vertical header column is then whatever notes we want to compare against, and the links
|
||||||
|
from the novel files are dots on the row.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import nw
|
||||||
|
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
from nw.project.document import NWDoc
|
||||||
|
from nw.enum import nwItemType, nwItemClass
|
||||||
|
from nw.constants import nwFiles
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class NWIndex():
|
||||||
|
|
||||||
|
TAG_KEY = "@tag"
|
||||||
|
POV_KEY = "@pov"
|
||||||
|
CHAR_KEY = "@char"
|
||||||
|
PLOT_KEY = "@plot"
|
||||||
|
TIME_KEY = "@time"
|
||||||
|
WORLD_KEY = "@location"
|
||||||
|
OBJECT_KEY = "@object"
|
||||||
|
CUSTOM_KEY = "@custom"
|
||||||
|
|
||||||
|
NOTE_KEYS = [TAG_KEY]
|
||||||
|
NOVEL_KEYS = [PLOT_KEY, POV_KEY, CHAR_KEY, WORLD_KEY, TIME_KEY, OBJECT_KEY, CUSTOM_KEY]
|
||||||
|
TAG_CLASS = {
|
||||||
|
CHAR_KEY : [nwItemClass.CHARACTER, 1],
|
||||||
|
POV_KEY : [nwItemClass.CHARACTER, 2],
|
||||||
|
PLOT_KEY : [nwItemClass.PLOT, 1],
|
||||||
|
TIME_KEY : [nwItemClass.TIMELINE, 1],
|
||||||
|
WORLD_KEY : [nwItemClass.WORLD, 1],
|
||||||
|
OBJECT_KEY : [nwItemClass.OBJECT, 1],
|
||||||
|
CUSTOM_KEY : [nwItemClass.CUSTOM, 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, theProject, theParent):
|
||||||
|
|
||||||
|
# Internal
|
||||||
|
self.theProject = theProject
|
||||||
|
self.theParent = theParent
|
||||||
|
self.mainConf = self.theParent.mainConf
|
||||||
|
|
||||||
|
# Indices
|
||||||
|
self.tagIndex = {}
|
||||||
|
self.refIndex = {}
|
||||||
|
self.novelIndex = {}
|
||||||
|
|
||||||
|
# Lists
|
||||||
|
self.novelList = []
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def clearIndex(self):
|
||||||
|
self.tagIndex = {}
|
||||||
|
self.refIndex = {}
|
||||||
|
self.novelIndex = {}
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Load and Save Index to/from File
|
||||||
|
##
|
||||||
|
|
||||||
|
def loadIndex(self):
|
||||||
|
|
||||||
|
theData = {}
|
||||||
|
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
||||||
|
if path.isfile(indexFile):
|
||||||
|
logger.debug("Loading index file")
|
||||||
|
try:
|
||||||
|
with open(indexFile,mode="r") as inFile:
|
||||||
|
theJson = inFile.read()
|
||||||
|
theData = json.loads(theJson)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to load index file")
|
||||||
|
logger.error(str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if "tagIndex" in theData.keys():
|
||||||
|
self.tagIndex = theData["tagIndex"]
|
||||||
|
if "refIndex" in theData.keys():
|
||||||
|
self.refIndex = theData["refIndex"]
|
||||||
|
if "novelIndex" in theData.keys():
|
||||||
|
self.novelIndex = theData["novelIndex"]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def saveIndex(self):
|
||||||
|
|
||||||
|
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
|
||||||
|
logger.debug("Saving index file")
|
||||||
|
if self.mainConf.debugInfo:
|
||||||
|
nIndent = 2
|
||||||
|
else:
|
||||||
|
nIndent = None
|
||||||
|
try:
|
||||||
|
with open(indexFile,mode="w+") as outFile:
|
||||||
|
outFile.write(json.dumps({
|
||||||
|
"tagIndex" : self.tagIndex,
|
||||||
|
"refIndex" : self.refIndex,
|
||||||
|
"novelIndex" : self.novelIndex,
|
||||||
|
}, indent=nIndent))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to save index file")
|
||||||
|
logger.error(str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
##
|
||||||
|
# Index Building
|
||||||
|
##
|
||||||
|
|
||||||
|
def scanText(self, tHandle, theText):
|
||||||
|
|
||||||
|
theItem = self.theProject.getItem(tHandle)
|
||||||
|
if theItem is None: return False
|
||||||
|
if theItem.itemType != nwItemType.FILE: return False
|
||||||
|
itemClass = theItem.itemClass
|
||||||
|
itemLayout = theItem.itemLayout
|
||||||
|
|
||||||
|
logger.debug("Indexing item with handle %s" % tHandle)
|
||||||
|
|
||||||
|
# Check file type, and reset its old index
|
||||||
|
if itemClass == nwItemClass.NOVEL:
|
||||||
|
self.novelIndex[tHandle] = []
|
||||||
|
self.refIndex[tHandle] = []
|
||||||
|
isNovel = True
|
||||||
|
else:
|
||||||
|
isNovel = False
|
||||||
|
|
||||||
|
# Also clear references to file in tag index
|
||||||
|
clearTags = []
|
||||||
|
for aTag in self.tagIndex:
|
||||||
|
if self.tagIndex[aTag][1] == tHandle:
|
||||||
|
clearTags.append(aTag)
|
||||||
|
for aTag in clearTags:
|
||||||
|
self.tagIndex.pop(aTag)
|
||||||
|
|
||||||
|
nLine = 0
|
||||||
|
nTitle = 0
|
||||||
|
for aLine in theText.splitlines():
|
||||||
|
aLine = aLine.strip()
|
||||||
|
nLine += 1
|
||||||
|
nChar = len(aLine)
|
||||||
|
if nChar == 0: continue
|
||||||
|
if aLine[0] == "#":
|
||||||
|
if isNovel:
|
||||||
|
isTitle = self.indexTitle(tHandle, aLine, nLine, itemLayout)
|
||||||
|
if isTitle:
|
||||||
|
nTitle = nLine
|
||||||
|
elif aLine[0] == "@":
|
||||||
|
if isNovel:
|
||||||
|
self.indexNoteRef(tHandle, aLine, nLine, nTitle)
|
||||||
|
else:
|
||||||
|
self.indexTag(tHandle, aLine, nLine, itemClass)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def indexTitle(self, tHandle, aLine, nLine, itemLayout):
|
||||||
|
|
||||||
|
if aLine.startswith("# "):
|
||||||
|
hDepth = 1
|
||||||
|
hText = aLine[2:].strip()
|
||||||
|
elif aLine.startswith("## "):
|
||||||
|
hDepth = 2
|
||||||
|
hText = aLine[3:].strip()
|
||||||
|
elif aLine.startswith("### "):
|
||||||
|
hDepth = 3
|
||||||
|
hText = aLine[4:].strip()
|
||||||
|
elif aLine.startswith("#### "):
|
||||||
|
hDepth = 4
|
||||||
|
hText = aLine[5:].strip()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if hText != "":
|
||||||
|
self.novelIndex[tHandle].append([nLine, hDepth, hText, itemLayout.name])
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def indexNoteRef(self, tHandle, aLine, nLine, nTitle):
|
||||||
|
|
||||||
|
isValid, theBits, thePos = self.scanThis(aLine)
|
||||||
|
if not isValid or len(theBits) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
theKey = theBits[0]
|
||||||
|
if theKey in self.NOVEL_KEYS:
|
||||||
|
for aVal in theBits[1:]:
|
||||||
|
self.refIndex[tHandle].append([nLine, theKey, aVal, nTitle])
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def indexTag(self, tHandle, aLine, nLine, itemClass):
|
||||||
|
|
||||||
|
isValid, theBits, thePos = self.scanThis(aLine)
|
||||||
|
if not isValid or len(theBits) != 2:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if theBits[0] == self.TAG_KEY:
|
||||||
|
self.tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
##
|
||||||
|
# Check @ Lines
|
||||||
|
##
|
||||||
|
|
||||||
|
def scanThis(self, aLine):
|
||||||
|
|
||||||
|
theBits = []
|
||||||
|
thePos = []
|
||||||
|
|
||||||
|
aLine = aLine.strip()
|
||||||
|
nChar = len(aLine)
|
||||||
|
if nChar < 2:
|
||||||
|
return False, theBits, thePos
|
||||||
|
if aLine[0] != "@":
|
||||||
|
return False, theBits, thePos
|
||||||
|
|
||||||
|
cPos = 0
|
||||||
|
cKey, cSep, cVals = aLine.partition(":")
|
||||||
|
sKey = cKey.strip()
|
||||||
|
if sKey == "@":
|
||||||
|
return False, theBits, thePos
|
||||||
|
|
||||||
|
theBits.append(sKey)
|
||||||
|
thePos.append(cPos)
|
||||||
|
cPos += len(sKey) + 1
|
||||||
|
|
||||||
|
if cVals == "":
|
||||||
|
# No values, so we're done
|
||||||
|
return True, theBits, thePos
|
||||||
|
|
||||||
|
aVals = cVals.split(",")
|
||||||
|
for cVal in aVals:
|
||||||
|
sVal = cVal.strip()
|
||||||
|
rLen = len(cVal.lstrip())
|
||||||
|
tLen = len(cVal)
|
||||||
|
theBits.append(sVal)
|
||||||
|
thePos.append(cPos+tLen-rLen)
|
||||||
|
cPos += tLen + 1
|
||||||
|
|
||||||
|
return True, theBits, thePos
|
||||||
|
|
||||||
|
def checkThese(self, theBits, tItem):
|
||||||
|
|
||||||
|
nBits = len(theBits)
|
||||||
|
isGood = [False]*nBits
|
||||||
|
if nBits == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# If we have a tag, only the first value is accepted, the rest is ignored
|
||||||
|
if theBits[0] == self.TAG_KEY and nBits > 1:
|
||||||
|
isGood[0] = True
|
||||||
|
if theBits[1] in self.tagIndex.keys():
|
||||||
|
if self.tagIndex[theBits[1]][1] == tItem.itemHandle:
|
||||||
|
isGood[1] = True
|
||||||
|
else:
|
||||||
|
isGood[1] = False
|
||||||
|
else:
|
||||||
|
isGood[1] = True
|
||||||
|
return isGood
|
||||||
|
|
||||||
|
# If we're still here, we better check that the references exist
|
||||||
|
if tItem.itemClass == nwItemClass.NOVEL:
|
||||||
|
isGood[0] = theBits[0] in self.NOVEL_KEYS
|
||||||
|
else:
|
||||||
|
isGood[0] = theBits[0] in self.NOTE_KEYS
|
||||||
|
if not isGood[0] or nBits == 1:
|
||||||
|
return isGood
|
||||||
|
|
||||||
|
for n in range(1,nBits):
|
||||||
|
if theBits[n] in self.tagIndex:
|
||||||
|
isGood[n] = self.TAG_CLASS[theBits[0]][0].name == self.tagIndex[theBits[n]][2]
|
||||||
|
|
||||||
|
return isGood
|
||||||
|
|
||||||
|
##
|
||||||
|
# Extract Data
|
||||||
|
##
|
||||||
|
|
||||||
|
def buildNovelList(self):
|
||||||
|
|
||||||
|
self.novelList = []
|
||||||
|
self.novelOrder = []
|
||||||
|
for tHandle in self.theProject.treeOrder:
|
||||||
|
if tHandle not in self.novelIndex:
|
||||||
|
continue
|
||||||
|
for tEntry in self.novelIndex[tHandle]:
|
||||||
|
self.novelList.append(tEntry)
|
||||||
|
self.novelOrder.append("%s:%d" % (tHandle,tEntry[0]))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def buildTagNovelMap(self, theTags):
|
||||||
|
|
||||||
|
tagMap = {}
|
||||||
|
tagClass = {}
|
||||||
|
|
||||||
|
for theTag in theTags:
|
||||||
|
tagMap[theTag] = [0]*len(self.novelOrder)
|
||||||
|
try:
|
||||||
|
tagClass[theTag] = nwItemClass[self.tagIndex[theTag][2]]
|
||||||
|
except:
|
||||||
|
logger.error("Could not map '%s' to nwItemClass" % self.tagIndex[theTag][2])
|
||||||
|
tagClass[theTag] = None
|
||||||
|
|
||||||
|
for tHandle in self.refIndex:
|
||||||
|
for nLine, tKey, tTag, nTitle in self.refIndex[tHandle]:
|
||||||
|
if tTag in tagMap.keys() and tKey in self.TAG_CLASS:
|
||||||
|
try:
|
||||||
|
nPos = self.novelOrder.index("%s:%d" % (tHandle, nTitle))
|
||||||
|
if self.TAG_CLASS[tKey][0] == tagClass[tTag]:
|
||||||
|
tagMap[tTag][nPos] = self.TAG_CLASS[tKey][1]
|
||||||
|
except:
|
||||||
|
logger.error("Could not find '%s:%d' in novelOrder" % (tHandle, nTitle))
|
||||||
|
|
||||||
|
return tagMap
|
||||||
|
|
||||||
|
# END Class NWIndex
|
||||||
@@ -37,6 +37,7 @@ class Theme:
|
|||||||
self.colKey = [0,0,0]
|
self.colKey = [0,0,0]
|
||||||
self.colVal = [0,0,0]
|
self.colVal = [0,0,0]
|
||||||
self.colSpell = [0,0,0]
|
self.colSpell = [0,0,0]
|
||||||
|
self.colTagErr = [0,0,0]
|
||||||
|
|
||||||
# Changeable Settings
|
# Changeable Settings
|
||||||
self.guiTheme = None
|
self.guiTheme = None
|
||||||
@@ -94,6 +95,7 @@ class Theme:
|
|||||||
self.colKey = self._loadColour(confParser,cnfSec,"keyword")
|
self.colKey = self._loadColour(confParser,cnfSec,"keyword")
|
||||||
self.colVal = self._loadColour(confParser,cnfSec,"value")
|
self.colVal = self._loadColour(confParser,cnfSec,"value")
|
||||||
self.colSpell = self._loadColour(confParser,cnfSec,"spellcheckline")
|
self.colSpell = self._loadColour(confParser,cnfSec,"spellcheckline")
|
||||||
|
self.colTagErr = self._loadColour(confParser,cnfSec,"tagerror")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ hidden = 150, 150, 150
|
|||||||
keyword = 200, 46, 0
|
keyword = 200, 46, 0
|
||||||
value = 184, 200, 0
|
value = 184, 200, 0
|
||||||
spellcheckline = 200, 46, 0
|
spellcheckline = 200, 46, 0
|
||||||
|
tagerror = 46, 200, 0
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""novelWriter Word Counter
|
||||||
|
|
||||||
|
novelWriter – Word Counter
|
||||||
|
============================
|
||||||
|
Simple word counter
|
||||||
|
|
||||||
|
File History:
|
||||||
|
Created: 2019-04-22 [0.0.1]
|
||||||
|
Moved: 2019-05-30 [0.1.4]
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import nw
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def countWords(theText):
|
||||||
|
|
||||||
|
charCount = 0
|
||||||
|
wordCount = 0
|
||||||
|
paraCount = 0
|
||||||
|
prevEmpty = True
|
||||||
|
|
||||||
|
for aLine in theText.splitlines():
|
||||||
|
|
||||||
|
countPara = True
|
||||||
|
theLen = len(aLine)
|
||||||
|
|
||||||
|
if theLen == 0:
|
||||||
|
prevEmpty = True
|
||||||
|
continue
|
||||||
|
if aLine[0] == "@" or aLine[0] == "%":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if aLine[0:5] == "#### ":
|
||||||
|
wordCount -= 1
|
||||||
|
charCount -= 5
|
||||||
|
countPara = False
|
||||||
|
elif aLine[0:4] == "### ":
|
||||||
|
wordCount -= 1
|
||||||
|
charCount -= 4
|
||||||
|
countPara = False
|
||||||
|
elif aLine[0:3] == "## ":
|
||||||
|
wordCount -= 1
|
||||||
|
charCount -= 3
|
||||||
|
countPara = False
|
||||||
|
elif aLine[0:2] == "# ":
|
||||||
|
wordCount -= 1
|
||||||
|
charCount -= 2
|
||||||
|
countPara = False
|
||||||
|
|
||||||
|
theBuff = aLine.replace("–"," ").replace("—"," ")
|
||||||
|
wordCount += len(theBuff.split())
|
||||||
|
charCount += theLen
|
||||||
|
if countPara and prevEmpty:
|
||||||
|
paraCount += 1
|
||||||
|
prevEmpty = countPara == False
|
||||||
|
|
||||||
|
return charCount, wordCount, paraCount
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
# John Smith
|
# John Smith
|
||||||
|
|
||||||
|
@tag: John
|
||||||
|
|
||||||
He’s pretty cool. Not Brad Pitt though.
|
He’s pretty cool. Not Brad Pitt though.
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
## This is the Subtitle
|
## This is the Subtitle
|
||||||
|
|
||||||
% Begin Meta
|
% Begin Meta
|
||||||
@POV: Sam
|
@pov: Jane
|
||||||
@Chars: Sam, Adam, Scott
|
@char: John
|
||||||
|
@location: Earth
|
||||||
% End Meta
|
% End Meta
|
||||||
|
|
||||||
Some text here would look good as well, and maybe some "dialogue"?
|
Some text here would look good as well, and maybe some "dialogue"?
|
||||||
@@ -17,8 +18,3 @@ This paragraph is also meaningless. At least a bit. It’s also very short. But
|
|||||||
|
|
||||||
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.
|
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
# Earth
|
# Earth
|
||||||
|
|
||||||
|
@tag: Earth
|
||||||
|
|
||||||
Third planet from the sun, fairly dense, and with lots of people on it.
|
Third planet from the sun, fairly dense, and with lots of people on it.
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
# Jane Smith
|
# Jane Smith
|
||||||
|
|
||||||
|
@tag: Jane
|
||||||
|
|
||||||
She’s pretty cool. Not Angelina Jolie though.
|
She’s pretty cool. Not Angelina Jolie though.
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
# This is a New File!
|
# This is a New File!
|
||||||
|
|
||||||
|
@pov: John
|
||||||
|
@location: Space
|
||||||
|
|
||||||
Although, not so new now that it has text in it an everything …
|
Although, not so new now that it has text in it an everything …
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Space
|
||||||
|
|
||||||
|
@tag: Space
|
||||||
|
|
||||||
|
This is somewhere in outer space.
|
||||||
|
|
||||||
@@ -13,3 +13,143 @@ 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: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: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
|
Start: 2019-05-26 15:52:39 End: 2019-05-26 15:52:56 Words: 0
|
||||||
|
Start: 2019-05-27 21:23:07 End: 2019-05-27 21:26:41 Words: 0
|
||||||
|
Start: 2019-05-27 21:26:56 End: 2019-05-27 21:27:48 Words: 0
|
||||||
|
Start: 2019-05-27 21:29:11 End: 2019-05-27 21:30:30 Words: 0
|
||||||
|
Start: 2019-05-27 21:33:30 End: 2019-05-27 21:35:14 Words: 0
|
||||||
|
Start: 2019-05-27 21:35:20 End: 2019-05-27 21:36:03 Words: 0
|
||||||
|
Start: 2019-05-27 21:36:07 End: 2019-05-27 21:36:23 Words: 0
|
||||||
|
Start: 2019-05-27 21:36:27 End: 2019-05-27 21:37:46 Words: 0
|
||||||
|
Start: 2019-05-27 21:37:50 End: 2019-05-27 21:38:05 Words: 0
|
||||||
|
Start: 2019-05-27 21:38:14 End: 2019-05-27 21:38:24 Words: 0
|
||||||
|
Start: 2019-05-27 21:39:08 End: 2019-05-27 21:39:23 Words: 0
|
||||||
|
Start: 2019-05-27 21:42:21 End: 2019-05-27 21:42:39 Words: 0
|
||||||
|
Start: 2019-05-27 21:45:07 End: 2019-05-27 21:45:18 Words: 0
|
||||||
|
Start: 2019-05-27 21:58:30 End: 2019-05-27 21:58:43 Words: 0
|
||||||
|
Start: 2019-05-27 22:02:01 End: 2019-05-27 22:02:09 Words: 0
|
||||||
|
Start: 2019-05-27 22:05:47 End: 2019-05-27 22:06:02 Words: 0
|
||||||
|
Start: 2019-05-27 22:08:25 End: 2019-05-27 22:09:35 Words: 0
|
||||||
|
Start: 2019-05-27 22:11:40 End: 2019-05-27 22:14:18 Words: 3
|
||||||
|
Start: 2019-05-27 22:20:55 End: 2019-05-27 22:21:40 Words: -2
|
||||||
|
Start: 2019-05-27 22:22:38 End: 2019-05-27 22:23:12 Words: 16
|
||||||
|
Start: 2019-05-27 22:55:34 End: 2019-05-27 22:56:09 Words: 0
|
||||||
|
Start: 2019-05-27 22:58:47 End: 2019-05-27 23:00:47 Words: 17
|
||||||
|
Start: 2019-05-28 18:20:15 End: 2019-05-28 18:20:30 Words: -1
|
||||||
|
Start: 2019-05-28 18:21:04 End: 2019-05-28 18:21:24 Words: 0
|
||||||
|
Start: 2019-05-28 18:29:41 End: 2019-05-28 18:30:16 Words: 0
|
||||||
|
Start: 2019-05-28 18:30:59 End: 2019-05-28 18:32:06 Words: 0
|
||||||
|
Start: 2019-05-28 18:34:33 End: 2019-05-28 18:34:48 Words: 1
|
||||||
|
Start: 2019-05-28 18:43:08 End: 2019-05-28 18:43:21 Words: 0
|
||||||
|
Start: 2019-05-28 18:49:29 End: 2019-05-28 18:49:40 Words: 0
|
||||||
|
Start: 2019-05-28 19:13:20 End: 2019-05-28 19:13:24 Words: -1
|
||||||
|
Start: 2019-05-28 19:17:05 End: 2019-05-28 19:17:13 Words: 1
|
||||||
|
Start: 2019-05-28 19:49:44 End: 2019-05-28 19:52:23 Words: -1
|
||||||
|
Start: 2019-05-28 19:52:29 End: 2019-05-28 19:54:42 Words: 0
|
||||||
|
Start: 2019-05-28 19:55:33 End: 2019-05-28 19:55:56 Words: 0
|
||||||
|
Start: 2019-05-28 19:57:29 End: 2019-05-28 20:02:33 Words: 0
|
||||||
|
Start: 2019-05-28 20:02:38 End: 2019-05-28 20:05:19 Words: 0
|
||||||
|
Start: 2019-05-28 20:05:23 End: 2019-05-28 20:06:48 Words: 0
|
||||||
|
Start: 2019-05-28 20:07:00 End: 2019-05-28 20:12:26 Words: 0
|
||||||
|
Start: 2019-05-28 20:12:31 End: 2019-05-28 20:17:58 Words: -16
|
||||||
|
Start: 2019-05-28 20:28:31 End: 2019-05-28 20:28:53 Words: 0
|
||||||
|
Start: 2019-05-28 20:28:57 End: 2019-05-28 20:29:05 Words: 16
|
||||||
|
Start: 2019-05-28 21:00:49 End: 2019-05-28 21:01:45 Words: 1
|
||||||
|
Start: 2019-05-28 21:02:01 End: 2019-05-28 21:02:06 Words: 0
|
||||||
|
Start: 2019-05-28 21:17:57 End: 2019-05-28 21:25:48 Words: 0
|
||||||
|
Start: 2019-05-28 21:35:39 End: 2019-05-28 21:35:47 Words: 0
|
||||||
|
Start: 2019-05-28 22:22:54 End: 2019-05-28 22:23:12 Words: 0
|
||||||
|
Start: 2019-05-30 11:58:19 End: 2019-05-30 11:59:15 Words: 0
|
||||||
|
Start: 2019-05-30 11:59:19 End: 2019-05-30 11:59:26 Words: 0
|
||||||
|
Start: 2019-05-30 11:59:55 End: 2019-05-30 12:00:09 Words: 0
|
||||||
|
Start: 2019-05-30 12:01:47 End: 2019-05-30 12:02:08 Words: 0
|
||||||
|
Start: 2019-05-30 12:03:56 End: 2019-05-30 12:04:23 Words: 0
|
||||||
|
Start: 2019-05-30 12:09:24 End: 2019-05-30 12:09:47 Words: 0
|
||||||
|
Start: 2019-05-30 12:10:13 End: 2019-05-30 12:10:40 Words: 0
|
||||||
|
Start: 2019-05-30 12:12:14 End: 2019-05-30 12:12:33 Words: 0
|
||||||
|
Start: 2019-05-30 12:13:56 End: 2019-05-30 12:14:08 Words: 0
|
||||||
|
Start: 2019-05-30 12:14:24 End: 2019-05-30 12:14:44 Words: 0
|
||||||
|
Start: 2019-05-30 12:14:53 End: 2019-05-30 12:15:31 Words: 0
|
||||||
|
Start: 2019-05-30 12:17:36 End: 2019-05-30 12:17:49 Words: 0
|
||||||
|
Start: 2019-05-30 12:20:12 End: 2019-05-30 12:20:24 Words: 0
|
||||||
|
Start: 2019-05-30 12:36:42 End: 2019-05-30 12:37:21 Words: 0
|
||||||
|
Start: 2019-05-30 12:37:42 End: 2019-05-30 12:38:01 Words: 0
|
||||||
|
Start: 2019-05-30 12:47:13 End: 2019-05-30 12:47:27 Words: 0
|
||||||
|
Start: 2019-05-30 12:47:47 End: 2019-05-30 12:48:03 Words: 0
|
||||||
|
Start: 2019-05-30 12:50:01 End: 2019-05-30 12:50:15 Words: 0
|
||||||
|
Start: 2019-05-30 12:54:26 End: 2019-05-30 12:54:34 Words: 0
|
||||||
|
Start: 2019-05-30 15:25:50 End: 2019-05-30 15:25:59 Words: 0
|
||||||
|
Start: 2019-05-30 15:43:36 End: 2019-05-30 15:43:44 Words: 0
|
||||||
|
Start: 2019-05-30 15:52:46 End: 2019-05-30 15:53:12 Words: 0
|
||||||
|
Start: 2019-05-30 15:53:44 End: 2019-05-30 15:53:56 Words: 0
|
||||||
|
Start: 2019-05-30 15:56:29 End: 2019-05-30 15:57:35 Words: 0
|
||||||
|
Start: 2019-05-30 15:57:41 End: 2019-05-30 15:59:58 Words: 0
|
||||||
|
Start: 2019-05-30 16:00:01 End: 2019-05-30 16:00:28 Words: 0
|
||||||
|
Start: 2019-05-30 16:00:56 End: 2019-05-30 16:01:06 Words: 0
|
||||||
|
Start: 2019-05-30 16:01:32 End: 2019-05-30 16:01:48 Words: 0
|
||||||
|
Start: 2019-05-30 16:02:35 End: 2019-05-30 16:03:03 Words: 0
|
||||||
|
Start: 2019-05-30 17:00:23 End: 2019-05-30 17:21:07 Words: 0
|
||||||
|
Start: 2019-05-30 17:21:11 End: 2019-05-30 17:21:24 Words: 0
|
||||||
|
Start: 2019-05-30 17:24:41 End: 2019-05-30 17:24:46 Words: 0
|
||||||
|
Start: 2019-05-30 17:29:08 End: 2019-05-30 17:29:19 Words: 0
|
||||||
|
Start: 2019-05-30 18:12:59 End: 2019-05-30 18:13:07 Words: 0
|
||||||
|
Start: 2019-05-30 18:13:32 End: 2019-05-30 18:13:42 Words: 0
|
||||||
|
Start: 2019-05-30 18:16:13 End: 2019-05-30 18:16:18 Words: 0
|
||||||
|
Start: 2019-05-30 18:22:24 End: 2019-05-30 18:22:30 Words: 0
|
||||||
|
Start: 2019-05-30 18:22:43 End: 2019-05-30 18:24:11 Words: 0
|
||||||
|
Start: 2019-05-30 18:25:40 End: 2019-05-30 18:25:45 Words: 0
|
||||||
|
Start: 2019-05-30 18:26:01 End: 2019-05-30 18:27:56 Words: 0
|
||||||
|
Start: 2019-05-30 18:28:00 End: 2019-05-30 18:30:22 Words: 0
|
||||||
|
Start: 2019-05-30 18:42:16 End: 2019-05-30 18:43:10 Words: 0
|
||||||
|
Start: 2019-05-30 18:43:24 End: 2019-05-30 18:43:31 Words: 0
|
||||||
|
Start: 2019-05-30 18:48:54 End: 2019-05-30 18:49:19 Words: 0
|
||||||
|
Start: 2019-05-30 18:49:23 End: 2019-05-30 18:49:53 Words: 0
|
||||||
|
Start: 2019-05-30 18:51:06 End: 2019-05-30 19:02:40 Words: 0
|
||||||
|
Start: 2019-05-30 19:07:17 End: 2019-05-30 19:07:26 Words: 0
|
||||||
|
Start: 2019-05-30 19:17:16 End: 2019-05-30 19:17:39 Words: 0
|
||||||
|
Start: 2019-05-30 19:17:44 End: 2019-05-30 19:17:55 Words: 0
|
||||||
|
Start: 2019-05-30 19:18:44 End: 2019-05-30 19:19:15 Words: 0
|
||||||
|
Start: 2019-05-30 19:19:20 End: 2019-05-30 19:19:47 Words: 0
|
||||||
|
Start: 2019-05-30 19:21:41 End: 2019-05-30 19:21:44 Words: 0
|
||||||
|
Start: 2019-05-30 19:24:00 End: 2019-05-30 19:24:03 Words: 0
|
||||||
|
Start: 2019-05-30 19:24:45 End: 2019-05-30 19:26:48 Words: 0
|
||||||
|
Start: 2019-05-30 19:28:23 End: 2019-05-30 19:32:41 Words: 0
|
||||||
|
Start: 2019-05-30 19:32:57 End: 2019-05-30 19:33:18 Words: 0
|
||||||
|
Start: 2019-05-30 19:33:22 End: 2019-05-30 19:33:39 Words: 0
|
||||||
|
Start: 2019-05-30 19:34:08 End: 2019-05-30 19:34:31 Words: 0
|
||||||
|
Start: 2019-05-30 19:34:43 End: 2019-05-30 19:34:45 Words: 0
|
||||||
|
Start: 2019-05-30 19:34:58 End: 2019-05-30 19:36:33 Words: 0
|
||||||
|
Start: 2019-05-30 19:36:38 End: 2019-05-30 19:36:47 Words: 0
|
||||||
|
Start: 2019-05-30 19:43:30 End: 2019-05-30 19:43:35 Words: 0
|
||||||
|
Start: 2019-05-30 19:43:42 End: 2019-05-30 19:43:49 Words: 0
|
||||||
|
Start: 2019-05-30 19:45:34 End: 2019-05-30 19:45:59 Words: 0
|
||||||
|
Start: 2019-05-30 19:53:25 End: 2019-05-30 19:55:00 Words: 0
|
||||||
|
Start: 2019-05-30 19:55:21 End: 2019-05-30 19:56:16 Words: 0
|
||||||
|
Start: 2019-05-30 20:51:36 End: 2019-05-30 20:51:40 Words: 0
|
||||||
|
Start: 2019-05-30 20:51:40 End: 2019-05-30 20:51:45 Words: 0
|
||||||
|
Start: 2019-05-30 20:25:18 End: 2019-05-30 21:55:25 Words: 0
|
||||||
|
Start: 2019-05-30 21:56:09 End: 2019-05-30 21:57:09 Words: 0
|
||||||
|
Start: 2019-05-30 21:57:14 End: 2019-05-30 21:58:29 Words: 0
|
||||||
|
Start: 2019-05-30 21:58:33 End: 2019-05-30 21:58:59 Words: 0
|
||||||
|
Start: 2019-05-30 21:59:33 End: 2019-05-30 22:00:08 Words: 0
|
||||||
|
Start: 2019-05-30 22:00:27 End: 2019-05-30 22:01:18 Words: 0
|
||||||
|
Start: 2019-05-30 22:01:35 End: 2019-05-30 22:01:57 Words: 0
|
||||||
|
Start: 2019-05-30 22:02:05 End: 2019-05-30 22:02:24 Words: 0
|
||||||
|
Start: 2019-05-30 22:07:43 End: 2019-05-30 22:08:48 Words: 0
|
||||||
|
Start: 2019-05-30 22:18:15 End: 2019-05-30 22:18:24 Words: 0
|
||||||
|
Start: 2019-05-31 00:01:48 End: 2019-05-31 00:01:57 Words: 0
|
||||||
|
Start: 2019-05-31 00:10:01 End: 2019-05-31 00:10:07 Words: 0
|
||||||
|
Start: 2019-05-31 00:11:12 End: 2019-05-31 00:11:21 Words: 0
|
||||||
|
Start: 2019-05-31 00:11:49 End: 2019-05-31 00:12:04 Words: 0
|
||||||
|
Start: 2019-05-31 00:12:42 End: 2019-05-31 00:13:24 Words: 0
|
||||||
|
Start: 2019-05-31 00:38:11 End: 2019-05-31 00:38:38 Words: 0
|
||||||
|
Start: 2019-05-31 00:39:35 End: 2019-05-31 00:40:23 Words: 0
|
||||||
|
Start: 2019-05-31 00:40:27 End: 2019-05-31 00:40:54 Words: 0
|
||||||
|
Start: 2019-05-31 00:56:15 End: 2019-05-31 00:56:23 Words: 0
|
||||||
|
Start: 2019-05-31 00:59:56 End: 2019-05-31 01:00:05 Words: 0
|
||||||
|
Start: 2019-05-31 01:00:39 End: 2019-05-31 01:00:56 Words: 0
|
||||||
|
Start: 2019-05-31 19:10:29 End: 2019-05-31 19:21:11 Words: 0
|
||||||
|
Start: 2019-05-31 19:50:56 End: 2019-05-31 19:51:03 Words: 0
|
||||||
|
Start: 2019-05-31 19:58:24 End: 2019-05-31 19:59:04 Words: 0
|
||||||
|
Start: 2019-05-31 20:11:22 End: 2019-05-31 20:11:56 Words: 0
|
||||||
|
Start: 2019-05-31 20:23:06 End: 2019-05-31 20:23:18 Words: 0
|
||||||
|
Start: 2019-05-31 20:23:56 End: 2019-05-31 20:27:02 Words: 7
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-26 15:52:53">
|
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-31 20:26:54">
|
||||||
<project>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<spellCheck>True</spellCheck>
|
<spellCheck>True</spellCheck>
|
||||||
<lastEdited>636b6aa9b697b</lastEdited>
|
<lastEdited>bc0cbd2a407f3</lastEdited>
|
||||||
<lastViewed>None</lastViewed>
|
<lastViewed>None</lastViewed>
|
||||||
<lastWordCount>540</lastWordCount>
|
<lastWordCount>565</lastWordCount>
|
||||||
<status>
|
<status>
|
||||||
<entry blue="100" green="100" red="100">New</entry>
|
<entry blue="100" green="100" red="100">New</entry>
|
||||||
<entry blue="0" green="50" red="200">Notes</entry>
|
<entry blue="0" green="50" red="200">Notes</entry>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<entry blue="175" green="0" red="117">Main</entry>
|
<entry blue="175" green="0" red="117">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="14">
|
<content count="15">
|
||||||
<item handle="7031beac91f75" order="0" parent="None">
|
<item handle="7031beac91f75" order="0" parent="None">
|
||||||
<name>Novel</name>
|
<name>Novel</name>
|
||||||
<type>ROOT</type>
|
<type>ROOT</type>
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
<charCount>656</charCount>
|
<charCount>656</charCount>
|
||||||
<wordCount>121</wordCount>
|
<wordCount>121</wordCount>
|
||||||
<paraCount>5</paraCount>
|
<paraCount>5</paraCount>
|
||||||
<cursorPos>573</cursorPos>
|
<cursorPos>77</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
||||||
<name>New File</name>
|
<name>New File</name>
|
||||||
@@ -85,10 +85,10 @@
|
|||||||
<status>Notes</status>
|
<status>Notes</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>69</charCount>
|
<charCount>82</charCount>
|
||||||
<wordCount>17</wordCount>
|
<wordCount>19</wordCount>
|
||||||
<paraCount>1</paraCount>
|
<paraCount>1</paraCount>
|
||||||
<cursorPos>0</cursorPos>
|
<cursorPos>69</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f6622b4617424" order="1" parent="None">
|
<item handle="f6622b4617424" order="1" parent="None">
|
||||||
<name>Characters</name>
|
<name>Characters</name>
|
||||||
@@ -111,10 +111,10 @@
|
|||||||
<status>Minor</status>
|
<status>Minor</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>42</charCount>
|
<charCount>49</charCount>
|
||||||
<wordCount>8</wordCount>
|
<wordCount>9</wordCount>
|
||||||
<paraCount>1</paraCount>
|
<paraCount>1</paraCount>
|
||||||
<cursorPos>0</cursorPos>
|
<cursorPos>24</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615">
|
<item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615">
|
||||||
<name>Jane Smith</name>
|
<name>Jane Smith</name>
|
||||||
@@ -123,10 +123,10 @@
|
|||||||
<status>Major</status>
|
<status>Major</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>51</charCount>
|
<charCount>55</charCount>
|
||||||
<wordCount>9</wordCount>
|
<wordCount>9</wordCount>
|
||||||
<paraCount>1</paraCount>
|
<paraCount>1</paraCount>
|
||||||
<cursorPos>0</cursorPos>
|
<cursorPos>25</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="15c4492bd5107" order="2" parent="None">
|
<item handle="15c4492bd5107" order="2" parent="None">
|
||||||
<name>Locations</name>
|
<name>Locations</name>
|
||||||
@@ -142,10 +142,22 @@
|
|||||||
<status>None</status>
|
<status>None</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
<layout>NOTE</layout>
|
<layout>NOTE</layout>
|
||||||
<charCount>0</charCount>
|
<charCount>76</charCount>
|
||||||
<wordCount>0</wordCount>
|
<wordCount>15</wordCount>
|
||||||
<paraCount>0</paraCount>
|
<paraCount>1</paraCount>
|
||||||
<cursorPos>0</cursorPos>
|
<cursorPos>20</cursorPos>
|
||||||
|
</item>
|
||||||
|
<item handle="f1471bef9f2ae" order="1" parent="15c4492bd5107">
|
||||||
|
<name>Space</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>WORLD</class>
|
||||||
|
<status>None</status>
|
||||||
|
<expanded>False</expanded>
|
||||||
|
<layout>NOTE</layout>
|
||||||
|
<charCount>38</charCount>
|
||||||
|
<wordCount>7</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>57</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98acd8c76c93a" order="3" parent="None">
|
<item handle="98acd8c76c93a" order="3" parent="None">
|
||||||
<name>Trash</name>
|
<name>Trash</name>
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
# Hello World!
|
# Novel
|
||||||
|
|
||||||
## With a Subtitle
|
## Chapter
|
||||||
|
|
||||||
### An Even Subier Title
|
@pov: Jane
|
||||||
|
@plot: MainPlot
|
||||||
|
|
||||||
#### Basically Not a Title at All
|
### Scene
|
||||||
|
|
||||||
% How about a comment?
|
% How about a comment?
|
||||||
@keyword: value
|
@pov: Jane
|
||||||
|
@plot: MainPlot
|
||||||
|
@location: Home
|
||||||
|
|
||||||
|
#### Some Section
|
||||||
|
|
||||||
|
@char: Jane
|
||||||
|
|
||||||
This is a paragraph of dummy text.
|
This is a paragraph of dummy text.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Main Plot
|
||||||
|
|
||||||
|
@tag: MainPlot
|
||||||
|
|
||||||
|
This is a file detailing the main plot.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Main Location
|
||||||
|
|
||||||
|
@tag: Home
|
||||||
|
|
||||||
|
This is a file describing Jane’s home.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Jane Doe
|
||||||
|
|
||||||
|
@tag: Jane
|
||||||
|
|
||||||
|
This is a file about Jane.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-05-25 23:29:32">
|
<novelWriterXML appVersion="0.1.4" fileVersion="1.0" timeStamp="2019-06-01 20:57:28">
|
||||||
<project>
|
<project>
|
||||||
<name></name>
|
<name></name>
|
||||||
<title></title>
|
<title></title>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<spellCheck>True</spellCheck>
|
<spellCheck>True</spellCheck>
|
||||||
<lastEdited>31489056e0916</lastEdited>
|
<lastEdited>31489056e0916</lastEdited>
|
||||||
<lastViewed>31489056e0916</lastViewed>
|
<lastViewed>31489056e0916</lastViewed>
|
||||||
<lastWordCount>69</lastWordCount>
|
<lastWordCount>86</lastWordCount>
|
||||||
<status>
|
<status>
|
||||||
<entry blue="100" green="100" red="100">New</entry>
|
<entry blue="100" green="100" red="100">New</entry>
|
||||||
<entry blue="0" green="50" red="200">Note</entry>
|
<entry blue="0" green="50" red="200">Note</entry>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<entry blue="0" green="200" red="50">Main</entry>
|
<entry blue="0" green="200" red="50">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="6">
|
<content count="9">
|
||||||
<item handle="73475cb40a568" order="0" parent="None">
|
<item handle="73475cb40a568" order="0" parent="None">
|
||||||
<name>Novel</name>
|
<name>Novel</name>
|
||||||
<type>ROOT</type>
|
<type>ROOT</type>
|
||||||
@@ -44,31 +44,67 @@
|
|||||||
<status>New</status>
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
<layout>SCENE</layout>
|
<layout>SCENE</layout>
|
||||||
<charCount>377</charCount>
|
<charCount>331</charCount>
|
||||||
<wordCount>69</wordCount>
|
<wordCount>59</wordCount>
|
||||||
<paraCount>2</paraCount>
|
<paraCount>2</paraCount>
|
||||||
<cursorPos>443</cursorPos>
|
<cursorPos>465</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="44cb730c42048" order="1" parent="None">
|
<item handle="44cb730c42048" order="1" parent="None">
|
||||||
<name>Characters</name>
|
<name>Characters</name>
|
||||||
<type>ROOT</type>
|
<type>ROOT</type>
|
||||||
<class>CHARACTER</class>
|
<class>CHARACTER</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
|
<expanded>True</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="2fca346db6561" order="0" parent="44cb730c42048">
|
||||||
|
<name>New File</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>CHARACTER</class>
|
||||||
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
|
<layout>NOTE</layout>
|
||||||
|
<charCount>34</charCount>
|
||||||
|
<wordCount>8</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>51</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="71ee45a3c0db9" order="2" parent="None">
|
<item handle="71ee45a3c0db9" order="2" parent="None">
|
||||||
<name>Plot</name>
|
<name>Plot</name>
|
||||||
<type>ROOT</type>
|
<type>ROOT</type>
|
||||||
<class>PLOT</class>
|
<class>PLOT</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
|
<expanded>True</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="02d20bbd7e394" order="0" parent="71ee45a3c0db9">
|
||||||
|
<name>New File</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>PLOT</class>
|
||||||
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
|
<layout>NOTE</layout>
|
||||||
|
<charCount>48</charCount>
|
||||||
|
<wordCount>10</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>69</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="811786ad1ae74" order="3" parent="None">
|
<item handle="811786ad1ae74" order="3" parent="None">
|
||||||
<name>World</name>
|
<name>World</name>
|
||||||
<type>ROOT</type>
|
<type>ROOT</type>
|
||||||
<class>WORLD</class>
|
<class>WORLD</class>
|
||||||
<status>New</status>
|
<status>New</status>
|
||||||
|
<expanded>True</expanded>
|
||||||
|
</item>
|
||||||
|
<item handle="7688b6ef52555" order="0" parent="811786ad1ae74">
|
||||||
|
<name>New File</name>
|
||||||
|
<type>FILE</type>
|
||||||
|
<class>WORLD</class>
|
||||||
|
<status>New</status>
|
||||||
<expanded>False</expanded>
|
<expanded>False</expanded>
|
||||||
|
<layout>NOTE</layout>
|
||||||
|
<charCount>51</charCount>
|
||||||
|
<wordCount>9</wordCount>
|
||||||
|
<paraCount>1</paraCount>
|
||||||
|
<cursorPos>68</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"tagIndex": {"Jane": [3, "2fca346db6561", "CHARACTER"], "MainPlot": [3, "02d20bbd7e394", "PLOT"], "Home": [3, "7688b6ef52555", "WORLD"]}, "refIndex": {"31489056e0916": [[5, "@pov", "Jane", 3], [6, "@plot", "MainPlot", 3], [11, "@pov", "Jane", 8], [12, "@plot", "MainPlot", 8], [13, "@location", "Home", 8], [17, "@char", "Jane", 15]]}, "novelIndex": {"31489056e0916": [[1, 1, "Novel", "SCENE"], [3, 2, "Chapter", "SCENE"], [8, 3, "Scene", "SCENE"], [15, 4, "Some Section", "SCENE"]]}}
|
||||||
@@ -7,6 +7,7 @@ geometry = 1100, 650
|
|||||||
treecols = 120, 30, 50
|
treecols = 120, 30, 50
|
||||||
mainpane = 300, 800
|
mainpane = 300, 800
|
||||||
docpane = 400, 400
|
docpane = 400, 400
|
||||||
|
timeline = 600, 400
|
||||||
|
|
||||||
[Project]
|
[Project]
|
||||||
autosaveproject = 60
|
autosaveproject = 60
|
||||||
|
|||||||
+131
-15
@@ -2,13 +2,14 @@
|
|||||||
"""novelWriter Main GUI Class Tester
|
"""novelWriter Main GUI Class Tester
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import nw, pytest
|
import nw, pytest, sys
|
||||||
from nwtools import *
|
from nwtools import *
|
||||||
|
|
||||||
from os import path, unlink
|
from os import path, unlink
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
from nw.gui.projecteditor import GuiProjectEditor
|
from nw.gui.projecteditor import GuiProjectEditor
|
||||||
|
from nw.gui.timelineview import GuiTimeLineView
|
||||||
from nw.gui.itemeditor import GuiItemEditor
|
from nw.gui.itemeditor import GuiItemEditor
|
||||||
from nw.enum import *
|
from nw.enum import *
|
||||||
|
|
||||||
@@ -47,8 +48,6 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
|
|||||||
assert cmpFiles(projFile, path.join(nwRef,"gui","0_nwProject.nwx"), [2])
|
assert cmpFiles(projFile, path.join(nwRef,"gui","0_nwProject.nwx"), [2])
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
# qtbot.stopForInteraction()
|
|
||||||
|
|
||||||
# Re-open project
|
# Re-open project
|
||||||
assert nwGUI.openProject(nwTempGUI)
|
assert nwGUI.openProject(nwTempGUI)
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
@@ -75,33 +74,101 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
|
|||||||
assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") is not None
|
assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") is not None
|
||||||
assert nwGUI.treeView._getTreeItem("811786ad1ae74") is not None
|
assert nwGUI.treeView._getTreeItem("811786ad1ae74") is not None
|
||||||
|
|
||||||
|
nwGUI.mainMenu.toolsSpellCheck.setChecked(True)
|
||||||
|
assert nwGUI.mainMenu._toggleSpellCheck()
|
||||||
|
|
||||||
|
# Add a Character File
|
||||||
|
nwGUI.setFocus(1)
|
||||||
|
nwGUI.treeView.clearSelection()
|
||||||
|
nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True)
|
||||||
|
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
||||||
|
assert nwGUI.openSelectedItem()
|
||||||
|
|
||||||
|
# Type something into the document
|
||||||
|
nwGUI.setFocus(2)
|
||||||
|
for c in "# Jane Doe":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@tag: Jane":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "This is a file about Jane.":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
# Add a Plot File
|
||||||
|
nwGUI.setFocus(1)
|
||||||
|
nwGUI.treeView.clearSelection()
|
||||||
|
nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
|
||||||
|
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
||||||
|
assert nwGUI.openSelectedItem()
|
||||||
|
|
||||||
|
# Type something into the document
|
||||||
|
nwGUI.setFocus(2)
|
||||||
|
for c in "# Main Plot":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@tag: MainPlot":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "This is a file detailing the main plot.":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
# Add a World File
|
||||||
|
nwGUI.setFocus(1)
|
||||||
|
nwGUI.treeView.clearSelection()
|
||||||
|
nwGUI.treeView._getTreeItem("811786ad1ae74").setSelected(True)
|
||||||
|
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
|
||||||
|
assert nwGUI.openSelectedItem()
|
||||||
|
|
||||||
|
# Type something into the document
|
||||||
|
nwGUI.setFocus(2)
|
||||||
|
for c in "# Main Location":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@tag: Home":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "This is a file describing Jane's home.":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
# Select the 'New Scene' file
|
# Select the 'New Scene' file
|
||||||
nwGUI.setFocus(1)
|
nwGUI.setFocus(1)
|
||||||
|
nwGUI.treeView.clearSelection()
|
||||||
nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True)
|
nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True)
|
||||||
nwGUI.treeView._getTreeItem("25fc0e7096fc6").setExpanded(True)
|
nwGUI.treeView._getTreeItem("25fc0e7096fc6").setExpanded(True)
|
||||||
nwGUI.treeView._getTreeItem("31489056e0916").setSelected(True)
|
nwGUI.treeView._getTreeItem("31489056e0916").setSelected(True)
|
||||||
assert nwGUI.openSelectedItem()
|
assert nwGUI.openSelectedItem()
|
||||||
nwGUI.mainMenu.toolsSpellCheck.setChecked(True)
|
|
||||||
assert nwGUI.mainMenu._toggleSpellCheck()
|
|
||||||
|
|
||||||
# Type something into the document
|
# Type something into the document
|
||||||
nwGUI.setFocus(2)
|
nwGUI.setFocus(2)
|
||||||
for c in "# Hello World!":
|
for c in "# Novel":
|
||||||
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
for c in "## With a Subtitle":
|
for c in "## Chapter":
|
||||||
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
for c in "### An Even Subier Title":
|
for c in "@pov: Jane":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@plot: MainPlot":
|
||||||
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
for c in "#### Basically Not a Title at All":
|
for c in "### Scene":
|
||||||
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
@@ -109,7 +176,23 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
|
|||||||
for c in "% How about a comment?":
|
for c in "% How about a comment?":
|
||||||
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
for c in "@keyword: value":
|
for c in "@pov: Jane":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@plot: MainPlot":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
for c in "@location: Home":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "#### Some Section":
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
|
|
||||||
|
for c in "@char: Jane":
|
||||||
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
|
||||||
@@ -133,13 +216,14 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
|
|||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
nwGUI.docEditor.wCounter.run()
|
nwGUI.docEditor.wCounter.run()
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
nwGUI.docEditor._updateCounts()
|
|
||||||
|
|
||||||
# Save the document
|
# Save the document
|
||||||
assert nwGUI.docEditor.docChanged
|
assert nwGUI.docEditor.docChanged
|
||||||
assert nwGUI.saveDocument()
|
assert nwGUI.saveDocument()
|
||||||
assert not nwGUI.docEditor.docChanged
|
assert not nwGUI.docEditor.docChanged
|
||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
|
nwGUI.rebuildIndex()
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
# Open and view the edited document
|
# Open and view the edited document
|
||||||
nwGUI.setFocus(3)
|
nwGUI.setFocus(3)
|
||||||
@@ -148,16 +232,48 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
|
|||||||
qtbot.wait(stepDelay)
|
qtbot.wait(stepDelay)
|
||||||
assert nwGUI.saveProject()
|
assert nwGUI.saveProject()
|
||||||
assert nwGUI.closeDocViewer()
|
assert nwGUI.closeDocViewer()
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
# Check the files
|
# Check the files
|
||||||
projFile = path.join(nwTempGUI,"nwProject.nwx")
|
refFile = path.join(nwTempGUI,"nwProject.nwx")
|
||||||
assert cmpFiles(projFile, path.join(nwRef,"gui","1_nwProject.nwx"), [2])
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_nwProject.nwx"), [2])
|
||||||
sceneFile = path.join(nwTempGUI,"data_3","1489056e0916_main.nwd")
|
refFile = path.join(nwTempGUI,"data_0","2d20bbd7e394_main.nwd")
|
||||||
assert cmpFiles(sceneFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_2d20bbd7e394_main.nwd"))
|
||||||
|
refFile = path.join(nwTempGUI,"data_2","fca346db6561_main.nwd")
|
||||||
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_fca346db6561_main.nwd"))
|
||||||
|
refFile = path.join(nwTempGUI,"data_3","1489056e0916_main.nwd")
|
||||||
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
|
||||||
|
refFile = path.join(nwTempGUI,"data_7","688b6ef52555_main.nwd")
|
||||||
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_688b6ef52555_main.nwd"))
|
||||||
|
if sys.version_info[0] >= 3 and sys.version_info[1] >= 6:
|
||||||
|
refFile = path.join(nwTempGUI,"meta","tagsIndex.json")
|
||||||
|
assert cmpFiles(refFile, path.join(nwRef,"gui","1_tagsIndex.json"))
|
||||||
|
|
||||||
nwGUI.closeMain()
|
nwGUI.closeMain()
|
||||||
# qtbot.stopForInteraction()
|
# qtbot.stopForInteraction()
|
||||||
|
|
||||||
|
@pytest.mark.gui
|
||||||
|
def testTimeLineView(qtbot, nwTempGUI, nwRef):
|
||||||
|
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI])
|
||||||
|
qtbot.addWidget(nwGUI)
|
||||||
|
nwGUI.show()
|
||||||
|
qtbot.waitForWindowShown(nwGUI)
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
# Create new, save, open project
|
||||||
|
nwGUI.theProject.handleSeed = 42
|
||||||
|
assert nwGUI.openProject(nwTempGUI)
|
||||||
|
qtbot.wait(stepDelay)
|
||||||
|
|
||||||
|
timeLine = GuiTimeLineView(nwGUI, nwGUI.theProject, nwGUI.theIndex)
|
||||||
|
qtbot.addWidget(timeLine)
|
||||||
|
|
||||||
|
assert timeLine.numRows == 4
|
||||||
|
assert timeLine.numCols == 3
|
||||||
|
|
||||||
|
# qtbot.stopForInteraction()
|
||||||
|
nwGUI.closeMain()
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testProjectEditor(qtbot, nwTempGUI, nwRef):
|
def testProjectEditor(qtbot, nwTempGUI, nwRef):
|
||||||
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI])
|
nwGUI = nw.main(["--testmode","--config=%s" % nwTempGUI])
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from nwdummy import DummyMain
|
|||||||
from nw.config import Config
|
from nw.config import Config
|
||||||
from nw.project.project import NWProject
|
from nw.project.project import NWProject
|
||||||
from nw.project.item import NWItem
|
from nw.project.item import NWItem
|
||||||
|
from nw.project.index import NWIndex
|
||||||
from nw.enum import nwItemClass
|
from nw.enum import nwItemClass
|
||||||
|
|
||||||
theConf = Config()
|
theConf = Config()
|
||||||
@@ -60,3 +61,59 @@ def testProjectNewRoot(nwTempProj,nwRef):
|
|||||||
assert theProject.saveProject()
|
assert theProject.saveProject()
|
||||||
assert cmpFiles(projFile, refFile, [2])
|
assert cmpFiles(projFile, refFile, [2])
|
||||||
assert not theProject.projChanged
|
assert not theProject.projChanged
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testIndexScanThis(nwTempProj):
|
||||||
|
projFile = path.join(nwTempProj,"nwProject.nwx")
|
||||||
|
assert theProject.openProject(projFile)
|
||||||
|
|
||||||
|
theIndex = NWIndex(theProject,theMain)
|
||||||
|
tHandle = "31489056e0916"
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("tag: this, and this")
|
||||||
|
assert not isValid
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@:")
|
||||||
|
assert not isValid
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@a:")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@a']"
|
||||||
|
assert str(thePos) == "[0]"
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@a:b")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@a', 'b']"
|
||||||
|
assert str(thePos) == "[0, 3]"
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@a:b,c,d")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@a', 'b', 'c', 'd']"
|
||||||
|
assert str(thePos) == "[0, 3, 5, 7]"
|
||||||
|
|
||||||
|
isValid, theBits, thePos = theIndex.scanThis("@tag: this, and this")
|
||||||
|
assert isValid
|
||||||
|
assert str(theBits) == "['@tag', 'this', 'and this']"
|
||||||
|
assert str(thePos) == "[0, 6, 12]"
|
||||||
|
|
||||||
|
@pytest.mark.project
|
||||||
|
def testBuildIndex(nwTempProj):
|
||||||
|
projFile = path.join(nwTempProj,"nwProject.nwx")
|
||||||
|
assert theProject.openProject(projFile)
|
||||||
|
|
||||||
|
theIndex = NWIndex(theProject,theMain)
|
||||||
|
tHandle = "31489056e0916"
|
||||||
|
|
||||||
|
theIndex.scanText(tHandle, (
|
||||||
|
"# Novel\n\n"
|
||||||
|
"## Chapter\n\n"
|
||||||
|
"### Scene\n\n"
|
||||||
|
"#### Section\n\n"
|
||||||
|
"@pov: John\n"
|
||||||
|
"@char: Jane\n"
|
||||||
|
"@location: Somewhere\n"
|
||||||
|
))
|
||||||
|
|
||||||
|
assert theIndex.buildNovelList()
|
||||||
|
assert str(theIndex.novelList) == "[[1, 1, 'Novel', 'SCENE'], [3, 2, 'Chapter', 'SCENE'], [5, 3, 'Scene', 'SCENE'], [7, 4, 'Section', 'SCENE']]"
|
||||||
|
assert str(theIndex.novelOrder) == "['31489056e0916:1', '31489056e0916:3', '31489056e0916:5', '31489056e0916:7']"
|
||||||
|
|||||||
Reference in New Issue
Block a user