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/**/wordlist.txt
sample/**/*.bak
sample/**/*.json
# PyTest
tests/temp
+3 -2
View File
@@ -151,8 +151,9 @@ def main(sysArgs):
debugGUI = True
# Set Config Options
CONFIG.showGUI = not testMode
CONFIG.debugGUI = debugGUI
CONFIG.showGUI = not testMode
CONFIG.debugGUI = debugGUI
CONFIG.debugInfo = debugLevel < logging.INFO
# Set Logging
if showTime: debugStr = timeStr+debugStr
+19 -7
View File
@@ -22,9 +22,6 @@ logger = logging.getLogger(__name__)
class Config:
WIN_WIDTH = 0
WIN_HEIGHT = 1
CNF_STR = 0
CNF_INT = 1
CNF_BOOL = 2
@@ -37,6 +34,7 @@ class Config:
self.appHandle = nw.__package__.lower()
self.showGUI = True
self.debugGUI = False
self.debugInfo = False
# Set Paths
self.confPath = None
@@ -57,6 +55,9 @@ class Config:
self.mainPanePos = [300, 800]
self.docPanePos = [400, 400]
## Dialogs
self.dlgTimeLine = [600, 400]
## Project
self.autoSaveProj = 60
self.autoSaveDoc = 30
@@ -142,6 +143,7 @@ class Config:
self.treeColWidth = self._parseLine(cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth)
self.mainPanePos = self._parseLine(cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos)
self.docPanePos = self._parseLine(cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos)
self.dlgTimeLine = self._parseLine(cnfParse, cnfSec, "timeline", self.CNF_LIST, self.dlgTimeLine)
## Project
cnfSec = "Project"
@@ -191,6 +193,7 @@ class Config:
cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth))
cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos))
cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos))
cnfParse.set(cnfSec,"timeline", self._packList(self.dlgTimeLine))
## Project
cnfSec = "Project"
@@ -253,11 +256,20 @@ class Config:
return True
def setWinSize(self, newWidth, newHeight):
if abs(self.winGeometry[self.WIN_WIDTH] - newWidth) >= 10:
self.winGeometry[self.WIN_WIDTH] = newWidth
if abs(self.winGeometry[0] - newWidth) > 5:
self.winGeometry[0] = newWidth
self.confChanged = True
if abs(self.winGeometry[self.WIN_HEIGHT] - newHeight) >= 10:
self.winGeometry[self.WIN_HEIGHT] = newHeight
if abs(self.winGeometry[1] - newHeight) > 5:
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
return True
+5 -4
View File
@@ -14,10 +14,11 @@ from nw.enum import nwItemClass, nwItemLayout
class nwFiles():
APP_ICON = "novelWriter.svg"
PROJ_FILE = "nwProject.nwx"
PROJ_DICT = "wordlist.txt"
SESS_INFO = "sessionInfo.log"
APP_ICON = "novelWriter.svg"
PROJ_FILE = "nwProject.nwx"
PROJ_DICT = "wordlist.txt"
SESS_INFO = "sessionInfo.log"
INDEX_FILE = "tagsIndex.json"
# END Class nwFiles
+23 -19
View File
@@ -34,11 +34,12 @@ class GuiDocEditor(QTextEdit):
logger.debug("Initialising DocEditor ...")
# Class Variables
self.mainConf = nw.CONFIG
self.theParent = theParent
self.docChanged = False
self.pwlFile = None
self.spellCheck = False
self.mainConf = nw.CONFIG
self.theParent = theParent
self.docChanged = False
self.pwlFile = None
self.spellCheck = False
self.theDocument = theParent.theDocument
# Document Variables
self.charCount = 0
@@ -54,9 +55,9 @@ class GuiDocEditor(QTextEdit):
self.typApos = self.mainConf.fmtApostrophe
# Core Elements
self.theDoc = self.document()
self.theQDoc = self.document()
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)
# Context Menu
@@ -71,8 +72,8 @@ class GuiDocEditor(QTextEdit):
self.clearEditor()
self.initEditor()
self.theDoc.setDocumentMargin(0)
self.theDoc.contentsChange.connect(self._docChange)
self.theQDoc.setDocumentMargin(0)
self.theQDoc.contentsChange.connect(self._docChange)
# Custom Shortcuts
QShortcut(QKeySequence("Ctrl+."), self, context=Qt.WidgetShortcut, activated=self._openSpellContext)
@@ -109,7 +110,18 @@ class GuiDocEditor(QTextEdit):
theOpt.setTabStopDistance(self.mainConf.tabWidth)
if self.mainConf.doJustify:
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
##
@@ -121,14 +133,6 @@ class GuiDocEditor(QTextEdit):
self.theParent.statusBar.setDocumentStatus(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):
theText = self.toPlainText()
return theText
@@ -296,7 +300,7 @@ class GuiDocEditor(QTextEdit):
if not self.wcTimer.isActive():
self.wcTimer.start()
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))
return
+85 -38
View File
@@ -20,14 +20,17 @@ logger = logging.getLogger(__name__)
class GuiDocHighlighter(QSyntaxHighlighter):
def __init__(self, theDoc, theTheme):
def __init__(self, theDoc, theParent):
QSyntaxHighlighter.__init__(self, theDoc)
logger.debug("Initialising DocHighlighter ...")
self.mainConf = nw.CONFIG
self.theDoc = theDoc
self.theTheme = theTheme
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theIndex = theParent.theIndex
self.theDict = None
self.theHandle = None
self.spellCheck = False
self.hRules = []
@@ -41,6 +44,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.colKey = QColor(*self.theTheme.colKey)
self.colVal = QColor(*self.theTheme.colVal)
self.colSpell = QColor(*self.theTheme.colSpell)
self.colTagErr = QColor(*self.theTheme.colTagErr)
self.hStyles = {
"header1" : self._makeFormat(self.colHead, "bold",1.8),
@@ -90,12 +94,12 @@ class GuiDocHighlighter(QSyntaxHighlighter):
))
# Keyword/Value
self.hRules.append((
r"^(@.+?)\s*:\s*(.+?)$", {
1 : self.hStyles["keyword"],
2 : self.hStyles["value"],
}
))
# self.hRules.append((
# r"^(@.+?)\s*:\s*(.+?)$", {
# 1 : self.hStyles["keyword"],
# 2 : self.hStyles["value"],
# }
# ))
# Comments
self.hRules.append((
@@ -152,6 +156,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
return
##
# Setters
##
def setDict(self, theDict):
self.theDict = theDict
return True
@@ -160,6 +168,75 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.spellCheck = theMode
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):
theFormat = QTextCharFormat()
@@ -181,34 +258,4 @@ class GuiDocHighlighter(QSyntaxHighlighter):
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
+54 -34
View File
@@ -135,27 +135,27 @@ class GuiMainMenu(QMenuBar):
# Project > New Project
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))
self.projMenu.addAction(menuItem)
# Project > Open Project
menuItem = QAction(QIcon.fromTheme("folder-open"), "Open Project", self)
menuItem.setStatusTip("Open Project")
menuItem.setStatusTip("Open project")
menuItem.setShortcut("Ctrl+Shift+O")
menuItem.triggered.connect(lambda : self.theParent.openProject(None))
self.projMenu.addAction(menuItem)
# Project > Save Project
menuItem = QAction(QIcon.fromTheme("document-save"), "Save Project", self)
menuItem.setStatusTip("Save Project")
menuItem.setStatusTip("Save project")
menuItem.setShortcut("Ctrl+Shift+S")
menuItem.triggered.connect(self.theParent.saveProject)
self.projMenu.addAction(menuItem)
# Project > Close Project
menuItem = QAction(QIcon.fromTheme("document-revert"), "Close Project", self)
menuItem.setStatusTip("Close Project")
menuItem.setStatusTip("Close project")
menuItem.setShortcut("Ctrl+Shift+W")
menuItem.triggered.connect(lambda : self.theParent.closeProject(False))
self.projMenu.addAction(menuItem)
@@ -166,7 +166,7 @@ class GuiMainMenu(QMenuBar):
# Project > Project Settings
menuItem = QAction(QIcon.fromTheme("document-properties"), "Project Settings", self)
menuItem.setStatusTip("Project Settings")
menuItem.setStatusTip("Project settings")
menuItem.triggered.connect(self.theParent.editProjectDialog)
self.projMenu.addAction(menuItem)
@@ -193,7 +193,7 @@ class GuiMainMenu(QMenuBar):
# Project > New Folder
menuItem = QAction(QIcon.fromTheme("folder-new"), "Create Folder", self)
menuItem.setStatusTip("Create Folder")
menuItem.setStatusTip("Create folder")
menuItem.setShortcut("Ctrl+Shift+N")
menuItem.triggered.connect(lambda : self._newTreeItem(nwItemType.FOLDER, None))
self.projMenu.addAction(menuItem)
@@ -203,14 +203,14 @@ class GuiMainMenu(QMenuBar):
# Project > Edit
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.triggered.connect(self.theParent.editItem)
self.projMenu.addAction(menuItem)
# Project > Delete
menuItem = QAction(QIcon.fromTheme("edit-delete"), "&Delete Item", self)
menuItem.setStatusTip("Delete Selected Item")
menuItem.setStatusTip("Delete selected item")
menuItem.setShortcut("Ctrl+Del")
menuItem.triggered.connect(lambda : self.theParent.treeView.deleteItem(None))
self.projMenu.addAction(menuItem)
@@ -234,28 +234,28 @@ class GuiMainMenu(QMenuBar):
# Document > New
menuItem = QAction(QIcon.fromTheme("document-new"), "&New Document", self)
menuItem.setStatusTip("Create New Document")
menuItem.setStatusTip("Create new document")
menuItem.setShortcut("Ctrl+N")
menuItem.triggered.connect(lambda : self._newTreeItem(nwItemType.FILE, None))
self.docuMenu.addAction(menuItem)
# Document > Open
menuItem = QAction(QIcon.fromTheme("document-open"), "&Open Document", self)
menuItem.setStatusTip("Open Selected Document")
menuItem.setStatusTip("Open selected document")
menuItem.setShortcut("Ctrl+O")
menuItem.triggered.connect(self.theParent.openSelectedItem)
self.docuMenu.addAction(menuItem)
# Document > Save
menuItem = QAction(QIcon.fromTheme("document-save"), "&Save Document", self)
menuItem.setStatusTip("Save Current Document")
menuItem.setStatusTip("Save current document")
menuItem.setShortcut("Ctrl+S")
menuItem.triggered.connect(self.theParent.saveDocument)
self.docuMenu.addAction(menuItem)
# Document > Close
menuItem = QAction(QIcon.fromTheme("document-revert"), "Close Document", self)
menuItem.setStatusTip("Close Current Document")
menuItem.setStatusTip("Close current document")
menuItem.setShortcut("Ctrl+W")
menuItem.triggered.connect(self.theParent.closeDocEditor)
self.docuMenu.addAction(menuItem)
@@ -265,14 +265,14 @@ class GuiMainMenu(QMenuBar):
# Document > Preview
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.triggered.connect(lambda : self.theParent.viewDocument(None))
self.docuMenu.addAction(menuItem)
# Document > Close Preview
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.triggered.connect(self.theParent.closeDocViewer)
self.docuMenu.addAction(menuItem)
@@ -299,25 +299,35 @@ class GuiMainMenu(QMenuBar):
# View > TreeView
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.triggered.connect(lambda : self.theParent.setFocus(1))
self.viewMenu.addAction(menuItem)
# View > Document Pane 1
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.triggered.connect(lambda : self.theParent.setFocus(2))
self.viewMenu.addAction(menuItem)
# # View > Document Pane 2
# View > Document Pane 2
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.triggered.connect(lambda : self.theParent.setFocus(3))
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
def _buildEditMenu(self):
@@ -327,14 +337,14 @@ class GuiMainMenu(QMenuBar):
# Edit > Undo
menuItem = QAction(QIcon.fromTheme("edit-undo"), "Undo", self)
menuItem.setStatusTip("Undo Last Change")
menuItem.setStatusTip("Undo last change")
menuItem.setShortcut("Ctrl+Z")
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.UNDO))
self.editMenu.addAction(menuItem)
# Edit > Redo
menuItem = QAction(QIcon.fromTheme("edit-redo"), "Redo", self)
menuItem.setStatusTip("Redo Last Change")
menuItem.setStatusTip("Redo last change")
menuItem.setShortcut("Ctrl+Y")
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.REDO))
self.editMenu.addAction(menuItem)
@@ -344,21 +354,21 @@ class GuiMainMenu(QMenuBar):
# Edit > Cut
menuItem = QAction(QIcon.fromTheme("edit-cut"), "Cut", self)
menuItem.setStatusTip("Cut Selected Text")
menuItem.setStatusTip("Cut selected text")
menuItem.setShortcut("Ctrl+X")
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.CUT))
self.editMenu.addAction(menuItem)
# Edit > Copy
menuItem = QAction(QIcon.fromTheme("edit-copy"), "Copy", self)
menuItem.setStatusTip("Copy Selected Text")
menuItem.setStatusTip("Copy selected text")
menuItem.setShortcut("Ctrl+C")
menuItem.triggered.connect(lambda: self._docAction(nwDocAction.COPY))
self.editMenu.addAction(menuItem)
# Edit > Paste
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.triggered.connect(lambda: self._docAction(nwDocAction.PASTE))
self.editMenu.addAction(menuItem)
@@ -368,14 +378,14 @@ class GuiMainMenu(QMenuBar):
# Edit > Select All
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.triggered.connect(lambda: self._docAction(nwDocAction.SEL_ALL))
self.editMenu.addAction(menuItem)
# Edit > Select Paragraph
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.triggered.connect(lambda: self._docAction(nwDocAction.SEL_PARA))
self.editMenu.addAction(menuItem)
@@ -389,21 +399,21 @@ class GuiMainMenu(QMenuBar):
# Format > Bold Text
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.triggered.connect(lambda: self._docAction(nwDocAction.BOLD))
self.fmtMenu.addAction(menuItem)
# Format > Italic Text
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.triggered.connect(lambda: self._docAction(nwDocAction.ITALIC))
self.fmtMenu.addAction(menuItem)
# Format > Underline Text
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.triggered.connect(lambda: self._docAction(nwDocAction.U_LINE))
self.fmtMenu.addAction(menuItem)
@@ -413,14 +423,14 @@ class GuiMainMenu(QMenuBar):
# Format > Double Quotes
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.triggered.connect(lambda: self._docAction(nwDocAction.D_QUOTE))
self.fmtMenu.addAction(menuItem)
# Format > Single Quotes
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.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
self.fmtMenu.addAction(menuItem)
@@ -434,14 +444,14 @@ class GuiMainMenu(QMenuBar):
# Tools > Move Up
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.triggered.connect(lambda : self._moveTreeItem(-1))
self.toolsMenu.addAction(self.toolsMoveUp)
# Tools > Move Down
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.triggered.connect(lambda : self._moveTreeItem(1))
self.toolsMenu.addAction(self.toolsMoveDown)
@@ -451,7 +461,7 @@ class GuiMainMenu(QMenuBar):
# Tools > Toggle Spell Check
self.toolsSpellCheck = QAction("Check Spelling", self)
self.toolsSpellCheck.setStatusTip("Toggle Check Spelling")
self.toolsSpellCheck.setStatusTip("Toggle check spelling")
self.toolsSpellCheck.setCheckable(True)
self.toolsSpellCheck.setChecked(self.theProject.spellCheck)
self.toolsSpellCheck.toggled.connect(self._toggleSpellCheck)
@@ -460,11 +470,21 @@ class GuiMainMenu(QMenuBar):
# Tools > Update Spell Check
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.triggered.connect(self.theParent.docEditor.updateSpellCheck)
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
# menuItem = QAction(QIcon.fromTheme("preferences-system"), "Preferences", self)
# menuItem.setStatusTip("Preferences")
+2 -2
View File
@@ -88,7 +88,7 @@ class GuiMainStatus(QStatusBar):
self.setRefTime(None)
self.setStats(0,0)
self.setCounts(0,0,0)
self.setDocHandleCount(None)
self.setDocHandle(None)
self.setProjectStatus(None)
self.setDocumentStatus(None)
self._updateTime()
@@ -132,7 +132,7 @@ class GuiMainStatus(QStatusBar):
self.boxCounts.setText("<b>Document:</b> {:d} : {:d} : {:d}".format(cC,wC,pC))
return
def setDocHandleCount(self, theHandle):
def setDocHandle(self, theHandle):
if theHandle is None:
self.boxDocHandle.setText("0000000000000")
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 time
import nw
from os import path
@@ -18,7 +19,7 @@ from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QPixmap, QColor
from PyQt5.QtWidgets import (
qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog,
QShortcut, QMessageBox
QShortcut, QMessageBox, QProgressDialog
)
from nw.theme import Theme
@@ -30,13 +31,16 @@ from nw.gui.mainmenu import GuiMainMenu
from nw.gui.projecteditor import GuiProjectEditor
from nw.gui.itemeditor import GuiItemEditor
from nw.gui.statusbar import GuiMainStatus
from nw.gui.timelineview import GuiTimeLineView
from nw.project.project import NWProject
from nw.project.document import NWDoc
from nw.project.item import NWItem
from nw.project.index import NWIndex
from nw.convert.tokenizer import Tokenizer
from nw.convert.tohtml import ToHtml
from nw.enum import nwItemType, nwAlert
from nw.constants import nwFiles
from nw.tools.wordcount import countWords
logger = logging.getLogger(__name__)
@@ -50,6 +54,7 @@ class GuiMain(QMainWindow):
self.theTheme = Theme()
self.theProject = NWProject(self)
self.theDocument = NWDoc(self.theProject, self)
self.theIndex = NWIndex(self.theProject, self)
self.hasProject = False
self.resize(*self.mainConf.winGeometry)
@@ -208,6 +213,7 @@ class GuiMain(QMainWindow):
if saveOK:
self.theProject.closeProject()
self.theIndex.clearIndex()
self.clearGUI()
self.hasProject = False
@@ -230,6 +236,9 @@ class GuiMain(QMainWindow):
if not self.theProject.openProject(projFile):
return False
# Load the tag index
self.theIndex.loadIndex()
# Update GUI
self._setWindowTitle(self.theProject.projName)
self.rebuildTree()
@@ -260,6 +269,7 @@ class GuiMain(QMainWindow):
self.treeView.saveTreeOrder()
self.theProject.saveProject()
self.theIndex.saveIndex()
self.mainMenu.updateRecentProjects()
return True
@@ -277,9 +287,7 @@ class GuiMain(QMainWindow):
def openDocument(self, tHandle):
self.closeDocument()
self.docEditor.setText(self.theDocument.openDocument(tHandle))
self.docEditor.setReadOnly(False)
self.docEditor.setCursorPosition(self.theDocument.theItem.cursorPos)
self.docEditor.loadText(tHandle)
self.docEditor.changeWidth()
self.docEditor.setFocus()
self.theProject.setLastEdited(tHandle)
@@ -289,12 +297,14 @@ class GuiMain(QMainWindow):
if self.theDocument.theItem is not None:
docText = self.docEditor.getText()
cursPos = self.docEditor.getCursorPosition()
self.theDocument.theItem.setCharCount(self.docEditor.charCount)
self.theDocument.theItem.setWordCount(self.docEditor.wordCount)
self.theDocument.theItem.setParaCount(self.docEditor.paraCount)
self.theDocument.theItem.setCursorPos(cursPos)
theItem = self.theDocument.theItem
theItem.setCharCount(self.docEditor.charCount)
theItem.setWordCount(self.docEditor.wordCount)
theItem.setParaCount(self.docEditor.paraCount)
theItem.setCursorPos(cursPos)
self.theDocument.saveDocument(docText)
self.docEditor.setDocumentChanged(False)
self.theIndex.scanText(theItem.itemHandle, docText)
return True
def viewDocument(self, tHandle=None):
@@ -356,9 +366,10 @@ class GuiMain(QMainWindow):
return
logger.verbose("Requesting change to item %s" % tHandle)
dlgProj = GuiItemEditor(self, self.theProject, tHandle)
if dlgProj.exec_():
self.treeView.setTreeItemValues(tHandle)
if self.mainConf.showGUI:
dlgProj = GuiItemEditor(self, self.theProject, tHandle)
if dlgProj.exec_():
self.treeView.setTreeItemValues(tHandle)
return
@@ -369,6 +380,55 @@ class GuiMain(QMainWindow):
self.treeView.buildTree()
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
##
@@ -413,6 +473,11 @@ class GuiMain(QMainWindow):
self._setWindowTitle(self.theProject.projName)
return True
def showTimeLineDialog(self):
dlgTLine = GuiTimeLineView(self, self.theProject, self.theIndex)
dlgTLine.exec_()
return True
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
"""Alert both the user and the logger at the same time. Message can be either a string or an
array of strings. Severity level is 0 = info, 1 = warning, and 2 = error.
+7 -47
View File
@@ -13,10 +13,10 @@
import logging
import nw
from time import time
from PyQt5.QtCore import QThread
from nw.tools.wordcount import countWords
logger = logging.getLogger(__name__)
class WordCounter(QThread):
@@ -31,52 +31,12 @@ class WordCounter(QThread):
def run(self):
self.charCount = 0
self.wordCount = 0
self.paraCount = 0
theText = self.theParent.getText()
cC, wC, pC = countWords(theText)
prevEmpty = True
for n in range(self.theParent.theDoc.blockCount()):
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
self.charCount = cC
self.wordCount = wC
self.paraCount = pC
return
+4 -3
View File
@@ -41,11 +41,10 @@ class NWDoc():
self.docHandle = None
return
def openDocument(self, tHandle):
def openDocument(self, tHandle, showStatus=True):
self.docHandle = tHandle
self.theItem = self.theProject.getItem(tHandle)
self.theParent.statusBar.setDocHandleCount(tHandle)
docDir, docFile = self._assemblePath(self.FILE_MN)
logger.debug("Opening document %s" % path.join(docDir,docFile))
@@ -63,7 +62,9 @@ class NWDoc():
logger.debug("The requested document does not exist.")
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
+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.colVal = [0,0,0]
self.colSpell = [0,0,0]
self.colTagErr = [0,0,0]
# Changeable Settings
self.guiTheme = None
@@ -94,6 +95,7 @@ class Theme:
self.colKey = self._loadColour(confParser,cnfSec,"keyword")
self.colVal = self._loadColour(confParser,cnfSec,"value")
self.colSpell = self._loadColour(confParser,cnfSec,"spellcheckline")
self.colTagErr = self._loadColour(confParser,cnfSec,"tagerror")
return True
+1
View File
@@ -9,3 +9,4 @@ hidden = 150, 150, 150
keyword = 200, 46, 0
value = 184, 200, 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
@tag: John
Hes pretty cool. Not Brad Pitt though.
@@ -3,8 +3,9 @@
## This is the Subtitle
% Begin Meta
@POV: Sam
@Chars: Sam, Adam, Scott
@pov: Jane
@char: John
@location: Earth
% End Meta
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.
@ToDo: Stuff that will be done at some point
@@ -1,3 +1,5 @@
# Earth
@tag: Earth
Third planet from the sun, fairly dense, and with lots of people on it.
@@ -1,3 +1,5 @@
# Jane Smith
@tag: Jane
Shes pretty cool. Not Angelina Jolie though.
@@ -1,3 +1,6 @@
# This is a New File!
@pov: John
@location: Space
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: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-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'?>
<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>
<name>Sample Project</name>
<title>Sample Project</title>
@@ -8,9 +8,9 @@
</project>
<settings>
<spellCheck>True</spellCheck>
<lastEdited>636b6aa9b697b</lastEdited>
<lastEdited>bc0cbd2a407f3</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>540</lastWordCount>
<lastWordCount>565</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Notes</entry>
@@ -27,7 +27,7 @@
<entry blue="175" green="0" red="117">Main</entry>
</importance>
</settings>
<content count="14">
<content count="15">
<item handle="7031beac91f75" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -76,7 +76,7 @@
<charCount>656</charCount>
<wordCount>121</wordCount>
<paraCount>5</paraCount>
<cursorPos>573</cursorPos>
<cursorPos>77</cursorPos>
</item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>New File</name>
@@ -85,10 +85,10 @@
<status>Notes</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>69</charCount>
<wordCount>17</wordCount>
<charCount>82</charCount>
<wordCount>19</wordCount>
<paraCount>1</paraCount>
<cursorPos>0</cursorPos>
<cursorPos>69</cursorPos>
</item>
<item handle="f6622b4617424" order="1" parent="None">
<name>Characters</name>
@@ -111,10 +111,10 @@
<status>Minor</status>
<expanded>False</expanded>
<layout>NOTE</layout>
<charCount>42</charCount>
<wordCount>8</wordCount>
<charCount>49</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>0</cursorPos>
<cursorPos>24</cursorPos>
</item>
<item handle="bb2c23b3c42cc" order="1" parent="f7e2d9f330615">
<name>Jane Smith</name>
@@ -123,10 +123,10 @@
<status>Major</status>
<expanded>False</expanded>
<layout>NOTE</layout>
<charCount>51</charCount>
<charCount>55</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>0</cursorPos>
<cursorPos>25</cursorPos>
</item>
<item handle="15c4492bd5107" order="2" parent="None">
<name>Locations</name>
@@ -142,10 +142,22 @@
<status>None</status>
<expanded>False</expanded>
<layout>NOTE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<cursorPos>0</cursorPos>
<charCount>76</charCount>
<wordCount>15</wordCount>
<paraCount>1</paraCount>
<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 handle="98acd8c76c93a" order="3" parent="None">
<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?
@keyword: value
@pov: Jane
@plot: MainPlot
@location: Home
#### Some Section
@char: Jane
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'?>
<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>
<name></name>
<title></title>
@@ -8,7 +8,7 @@
<spellCheck>True</spellCheck>
<lastEdited>31489056e0916</lastEdited>
<lastViewed>31489056e0916</lastViewed>
<lastWordCount>69</lastWordCount>
<lastWordCount>86</lastWordCount>
<status>
<entry blue="100" green="100" red="100">New</entry>
<entry blue="0" green="50" red="200">Note</entry>
@@ -22,7 +22,7 @@
<entry blue="0" green="200" red="50">Main</entry>
</importance>
</settings>
<content count="6">
<content count="9">
<item handle="73475cb40a568" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -44,31 +44,67 @@
<status>New</status>
<expanded>False</expanded>
<layout>SCENE</layout>
<charCount>377</charCount>
<wordCount>69</wordCount>
<charCount>331</charCount>
<wordCount>59</wordCount>
<paraCount>2</paraCount>
<cursorPos>443</cursorPos>
<cursorPos>465</cursorPos>
</item>
<item handle="44cb730c42048" order="1" parent="None">
<name>Characters</name>
<type>ROOT</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="2fca346db6561" order="0" parent="44cb730c42048">
<name>New File</name>
<type>FILE</type>
<class>CHARACTER</class>
<status>New</status>
<expanded>False</expanded>
<layout>NOTE</layout>
<charCount>34</charCount>
<wordCount>8</wordCount>
<paraCount>1</paraCount>
<cursorPos>51</cursorPos>
</item>
<item handle="71ee45a3c0db9" order="2" parent="None">
<name>Plot</name>
<type>ROOT</type>
<class>PLOT</class>
<status>New</status>
<expanded>True</expanded>
</item>
<item handle="02d20bbd7e394" order="0" parent="71ee45a3c0db9">
<name>New File</name>
<type>FILE</type>
<class>PLOT</class>
<status>New</status>
<expanded>False</expanded>
<layout>NOTE</layout>
<charCount>48</charCount>
<wordCount>10</wordCount>
<paraCount>1</paraCount>
<cursorPos>69</cursorPos>
</item>
<item handle="811786ad1ae74" order="3" parent="None">
<name>World</name>
<type>ROOT</type>
<class>WORLD</class>
<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>
<layout>NOTE</layout>
<charCount>51</charCount>
<wordCount>9</wordCount>
<paraCount>1</paraCount>
<cursorPos>68</cursorPos>
</item>
</content>
</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
mainpane = 300, 800
docpane = 400, 400
timeline = 600, 400
[Project]
autosaveproject = 60
+131 -15
View File
@@ -2,13 +2,14 @@
"""novelWriter Main GUI Class Tester
"""
import nw, pytest
import nw, pytest, sys
from nwtools import *
from os import path, unlink
from PyQt5.QtCore import Qt
from nw.gui.projecteditor import GuiProjectEditor
from nw.gui.timelineview import GuiTimeLineView
from nw.gui.itemeditor import GuiItemEditor
from nw.enum import *
@@ -47,8 +48,6 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
assert cmpFiles(projFile, path.join(nwRef,"gui","0_nwProject.nwx"), [2])
qtbot.wait(stepDelay)
# qtbot.stopForInteraction()
# Re-open project
assert nwGUI.openProject(nwTempGUI)
qtbot.wait(stepDelay)
@@ -75,33 +74,101 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") 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
nwGUI.setFocus(1)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True)
nwGUI.treeView._getTreeItem("25fc0e7096fc6").setExpanded(True)
nwGUI.treeView._getTreeItem("31489056e0916").setSelected(True)
assert nwGUI.openSelectedItem()
nwGUI.mainMenu.toolsSpellCheck.setChecked(True)
assert nwGUI.mainMenu._toggleSpellCheck()
# Type something into the document
nwGUI.setFocus(2)
for c in "# Hello World!":
for c in "# Novel":
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 "## With a Subtitle":
for c in "## Chapter":
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 "### 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, 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, 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?":
qtbot.keyClick(nwGUI.docEditor, c, 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, 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)
nwGUI.docEditor.wCounter.run()
qtbot.wait(stepDelay)
nwGUI.docEditor._updateCounts()
# Save the document
assert nwGUI.docEditor.docChanged
assert nwGUI.saveDocument()
assert not nwGUI.docEditor.docChanged
qtbot.wait(stepDelay)
nwGUI.rebuildIndex()
qtbot.wait(stepDelay)
# Open and view the edited document
nwGUI.setFocus(3)
@@ -148,16 +232,48 @@ def testMainWindows(qtbot, nwTempGUI, nwRef):
qtbot.wait(stepDelay)
assert nwGUI.saveProject()
assert nwGUI.closeDocViewer()
qtbot.wait(stepDelay)
# Check the files
projFile = path.join(nwTempGUI,"nwProject.nwx")
assert cmpFiles(projFile, path.join(nwRef,"gui","1_nwProject.nwx"), [2])
sceneFile = path.join(nwTempGUI,"data_3","1489056e0916_main.nwd")
assert cmpFiles(sceneFile, path.join(nwRef,"gui","1_1489056e0916_main.nwd"))
refFile = path.join(nwTempGUI,"nwProject.nwx")
assert cmpFiles(refFile, path.join(nwRef,"gui","1_nwProject.nwx"), [2])
refFile = path.join(nwTempGUI,"data_0","2d20bbd7e394_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()
# 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
def testProjectEditor(qtbot, nwTempGUI, nwRef):
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.project.project import NWProject
from nw.project.item import NWItem
from nw.project.index import NWIndex
from nw.enum import nwItemClass
theConf = Config()
@@ -60,3 +61,59 @@ def testProjectNewRoot(nwTempProj,nwRef):
assert theProject.saveProject()
assert cmpFiles(projFile, refFile, [2])
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']"