Merge pull request #22 from vkbo/indexer

Tags Indexer and Simple TimeLine
This commit is contained in:
Veronica K. Berglyd Olsen
2019-06-01 21:21:53 +02:00
committed by GitHub
33 changed files with 1266 additions and 217 deletions
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+130
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4 -3
View File
@@ -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
+347
View File
@@ -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
+2
View File
@@ -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
+1
View File
@@ -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
+61
View File
@@ -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
Hes pretty cool. Not Brad Pitt though. Hes 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. Its 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
Shes pretty cool. Not Angelina Jolie though. Shes 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.
+140
View File
@@ -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
+29 -17
View File
@@ -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>
+12 -5
View File
@@ -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 Janes home.
@@ -0,0 +1,5 @@
# Jane Doe
@tag: Jane
This is a file about Jane.
+42 -6
View File
@@ -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>
+1
View File
@@ -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"]]}}
+1
View File
@@ -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
View File
@@ -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])
+57
View File
@@ -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']"