diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index c708536e..13f1092a 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -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 diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py index 697b2dea..a4f61a68 100644 --- a/nw/gui/dochighlight.py +++ b/nw/gui/dochighlight.py @@ -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 = [] @@ -90,12 +93,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 +155,10 @@ class GuiDocHighlighter(QSyntaxHighlighter): return + ## + # Setters + ## + def setDict(self, theDict): self.theDict = theDict return True @@ -160,6 +167,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.colSpell) + 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 +257,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 diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index d605f8ec..cf7592f9 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -53,7 +53,7 @@ class GuiMain(QMainWindow): self.theTheme = Theme() self.theProject = NWProject(self) self.theDocument = NWDoc(self.theProject, self) - self.tagIndex = NWIndex(self.theProject, self) + self.theIndex = NWIndex(self.theProject, self) self.hasProject = False self.resize(*self.mainConf.winGeometry) @@ -212,7 +212,7 @@ class GuiMain(QMainWindow): if saveOK: self.theProject.closeProject() - self.tagIndex.clearIndex() + self.theIndex.clearIndex() self.clearGUI() self.hasProject = False @@ -236,7 +236,7 @@ class GuiMain(QMainWindow): return False # Load the tag index - self.tagIndex.loadIndex() + self.theIndex.loadIndex() # Update GUI self._setWindowTitle(self.theProject.projName) @@ -268,7 +268,7 @@ class GuiMain(QMainWindow): self.treeView.saveTreeOrder() self.theProject.saveProject() - self.tagIndex.saveIndex() + self.theIndex.saveIndex() self.mainMenu.updateRecentProjects() return True @@ -286,9 +286,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) @@ -305,7 +303,7 @@ class GuiMain(QMainWindow): theItem.setCursorPos(cursPos) self.theDocument.saveDocument(docText) self.docEditor.setDocumentChanged(False) - self.tagIndex.scanText(theItem.itemHandle, docText) + self.theIndex.scanText(theItem.itemHandle, docText) return True def viewDocument(self, tHandle=None): @@ -385,7 +383,7 @@ class GuiMain(QMainWindow): logger.debug("Rebuilding indices ...") self.treeView.saveTreeOrder() - self.tagIndex.clearIndex() + self.theIndex.clearIndex() nItems = len(self.theProject.treeOrder) dlgProg = QProgressDialog("Scanning files ...", "Cancel", 0, nItems, self) @@ -419,7 +417,7 @@ class GuiMain(QMainWindow): self.treeView.projectWordCount() # Build tag index - self.tagIndex.scanText(tHandle, theText) + self.theIndex.scanText(tHandle, theText) time.sleep(0.05) nDone += 1 diff --git a/nw/project/index.py b/nw/project/index.py index c15409aa..b08ed313 100644 --- a/nw/project/index.py +++ b/nw/project/index.py @@ -36,24 +36,25 @@ 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" + 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 = [PLOT_KEY, POV_KEY, CHAR_KEY, WORLD_KEY, TIME_KEY, OBJECT_KEY, CUSTOM_KEY] - VALID_CLASS = { - nwItemClass.NOVEL : [], - nwItemClass.PLOT : [PLOT_KEY], - nwItemClass.CHARACTER : [POV_KEY, CHAR_KEY], - nwItemClass.WORLD : [WORLD_KEY], - nwItemClass.TIMELINE : [TIME_KEY], - nwItemClass.OBJECT : [OBJECT_KEY], - nwItemClass.CUSTOM : [CUSTOM_KEY], + NOTE_KEYS = [TAG_KEY] + NOVEL_KEYS = [PLOT_KEY, POV_KEY, CHAR_KEY, WORLD_KEY, TIME_KEY, OBJECT_KEY, CUSTOM_KEY] + TAG_CLASS = { + POV_KEY : nwItemClass.CHARACTER, + CHAR_KEY : nwItemClass.CHARACTER, + PLOT_KEY : nwItemClass.PLOT, + TIME_KEY : nwItemClass.TIMELINE, + WORLD_KEY : nwItemClass.WORLD, + OBJECT_KEY : nwItemClass.OBJECT, + CUSTOM_KEY : nwItemClass.CUSTOM, } def __init__(self, theProject, theParent): @@ -137,7 +138,8 @@ class NWIndex(): theItem = self.theProject.getItem(tHandle) if theItem is None: return False if theItem.itemType != nwItemType.FILE: return False - itemClass = theItem.itemClass + itemClass = theItem.itemClass + itemLayout = theItem.itemLayout logger.debug("Indexing item with handle %s" % tHandle) @@ -162,7 +164,7 @@ class NWIndex(): if nChar == 0: continue if aLine[0] == "#": if isNovel: - self.indexTitle(tHandle, aLine, nLine) + self.indexTitle(tHandle, aLine, nLine, itemLayout) elif aLine[0] == "@": if isNovel: self.indexNoteRef(tHandle, aLine, nLine) @@ -171,7 +173,7 @@ class NWIndex(): return True - def indexTitle(self, tHandle, aLine, nLine): + def indexTitle(self, tHandle, aLine, nLine, itemLayout): if aLine.startswith("# "): hDepth = 1 @@ -189,7 +191,7 @@ class NWIndex(): return False if hText != "": - self.novelIndex[tHandle].append([nLine, hDepth, hText]) + self.novelIndex[tHandle].append([nLine, hDepth, hText, itemLayout.name]) return True @@ -200,7 +202,7 @@ class NWIndex(): return False theKey = theBits[0] - if theKey in self.NOTE_KEYS: + if theKey in self.NOVEL_KEYS: for aVal in theBits[1:]: self.noteIndex[tHandle].append([nLine, theKey, aVal]) @@ -254,4 +256,32 @@ class NWIndex(): return True, theBits, thePos + def checkThese(self, theBits, tItem): + + theBits = [aBit.lower() for aBit in theBits] + nBits = len(theBits) + isGood = [False]*nBits + if nBits == 0: + return [] + + # If we have a tag, the first value is always OK, rest is ignored + if theBits[0] == self.TAG_KEY and nBits > 1: + isGood[0] = True + isGood[1] = True + return isGood + + # If we're still here, we better check the references + 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]].name == self.tagIndex[theBits[n]][2] + + return isGood + # END Class NWIndex diff --git a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd index de9fae73..60cccf00 100644 --- a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd +++ b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd @@ -5,6 +5,7 @@ % Begin Meta @POV: Jane @Char: Jane, John +@Location: Earth % End Meta Some text here would look good as well, and maybe some "dialogue"? diff --git a/sample/sampleNovel/data_b/3e74dbc1f584_main.nwd b/sample/sampleNovel/data_b/3e74dbc1f584_main.nwd index bcdf600f..b82ef89a 100644 --- a/sample/sampleNovel/data_b/3e74dbc1f584_main.nwd +++ b/sample/sampleNovel/data_b/3e74dbc1f584_main.nwd @@ -1,3 +1,5 @@ # Earth +@tag: Earth + Third planet from the sun, fairly dense, and with lots of people on it. \ No newline at end of file diff --git a/sample/sampleNovel/meta/sessionInfo.log b/sample/sampleNovel/meta/sessionInfo.log index 908773f6..ed02d290 100644 --- a/sample/sampleNovel/meta/sessionInfo.log +++ b/sample/sampleNovel/meta/sessionInfo.log @@ -101,3 +101,26 @@ 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 diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index c7108caf..90e826e8 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -76,7 +76,7 @@ 656 121 5 - 729 + 105 New File @@ -145,7 +145,7 @@ 76 15 1 - 0 + 20 Trash