Merge pull request #191 from vkbo/outline_step2
Outline Feature - Step 2
This commit is contained in:
@@ -121,6 +121,7 @@ class nwLabels():
|
|||||||
}
|
}
|
||||||
OUTLINE_COLS = {
|
OUTLINE_COLS = {
|
||||||
nwOutline.TITLE : "Title",
|
nwOutline.TITLE : "Title",
|
||||||
|
nwOutline.HANDLE : "Handle",
|
||||||
nwOutline.LEVEL : "Level",
|
nwOutline.LEVEL : "Level",
|
||||||
nwOutline.LABEL : "Document",
|
nwOutline.LABEL : "Document",
|
||||||
nwOutline.LINE : "Line",
|
nwOutline.LINE : "Line",
|
||||||
|
|||||||
+16
-15
@@ -107,20 +107,21 @@ class nwAlert(Enum):
|
|||||||
class nwOutline(Enum):
|
class nwOutline(Enum):
|
||||||
|
|
||||||
TITLE = 0
|
TITLE = 0
|
||||||
LEVEL = 1
|
HANDLE = 1
|
||||||
LABEL = 2
|
LEVEL = 2
|
||||||
LINE = 3
|
LABEL = 3
|
||||||
CCOUNT = 4
|
LINE = 4
|
||||||
WCOUNT = 5
|
CCOUNT = 5
|
||||||
PCOUNT = 6
|
WCOUNT = 6
|
||||||
POV = 7
|
PCOUNT = 7
|
||||||
CHAR = 8
|
POV = 8
|
||||||
PLOT = 9
|
CHAR = 9
|
||||||
TIME = 10
|
PLOT = 10
|
||||||
WORLD = 11
|
TIME = 11
|
||||||
OBJECT = 12
|
WORLD = 12
|
||||||
ENTITY = 13
|
OBJECT = 13
|
||||||
CUSTOM = 14
|
ENTITY = 14
|
||||||
SYNOP = 15
|
CUSTOM = 15
|
||||||
|
SYNOP = 16
|
||||||
|
|
||||||
# END Enum nwOutline
|
# END Enum nwOutline
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def loadText(self, tHandle):
|
def loadText(self, tHandle, tLine=None):
|
||||||
"""Load text from a document into the editor. If we have an io
|
"""Load text from a document into the editor. If we have an io
|
||||||
error, we must handle this and clear the editor so that we don't
|
error, we must handle this and clear the editor so that we don't
|
||||||
risk overwriting the file if it exists. This can for instance
|
risk overwriting the file if it exists. This can for instance
|
||||||
@@ -249,7 +249,10 @@ class GuiDocEditor(QTextEdit):
|
|||||||
afTime = time()
|
afTime = time()
|
||||||
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
|
logger.debug("Document highlighted in %.3f milliseconds" % (1000*(afTime-bfTime)))
|
||||||
|
|
||||||
self.setCursorPosition(self.nwDocument.theItem.cursorPos)
|
if tLine is None:
|
||||||
|
self.setCursorPosition(self.nwDocument.theItem.cursorPos)
|
||||||
|
else:
|
||||||
|
self.setCursorLine(tLine)
|
||||||
self.lastEdit = time()
|
self.lastEdit = time()
|
||||||
self._runCounter()
|
self._runCounter()
|
||||||
self.wcTimer.start()
|
self.wcTimer.start()
|
||||||
@@ -353,6 +356,8 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return theText
|
return theText
|
||||||
|
|
||||||
def setCursorPosition(self, thePosition):
|
def setCursorPosition(self, thePosition):
|
||||||
|
"""Move the cursor to a given position in the document.
|
||||||
|
"""
|
||||||
if thePosition >= 0:
|
if thePosition >= 0:
|
||||||
theCursor = self.textCursor()
|
theCursor = self.textCursor()
|
||||||
theCursor.setPosition(thePosition)
|
theCursor.setPosition(thePosition)
|
||||||
@@ -360,9 +365,22 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def getCursorPosition(self):
|
def getCursorPosition(self):
|
||||||
|
"""Find the cursor position in the document.
|
||||||
|
"""
|
||||||
theCursor = self.textCursor()
|
theCursor = self.textCursor()
|
||||||
return theCursor.position()
|
return theCursor.position()
|
||||||
|
|
||||||
|
def setCursorLine(self, theLine):
|
||||||
|
"""Move the cursor to a given line in the document.
|
||||||
|
"""
|
||||||
|
if theLine is None:
|
||||||
|
return False
|
||||||
|
if theLine >= 0:
|
||||||
|
theBlock = self.qDocument.findBlockByLineNumber(theLine)
|
||||||
|
if theBlock:
|
||||||
|
self.setCursorPosition(theBlock.position())
|
||||||
|
return True
|
||||||
|
|
||||||
##
|
##
|
||||||
# Spell Checking
|
# Spell Checking
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class GuiProjectOutline(QTreeWidget):
|
|||||||
|
|
||||||
DEF_WIDTH = {
|
DEF_WIDTH = {
|
||||||
nwOutline.TITLE : 200,
|
nwOutline.TITLE : 200,
|
||||||
|
nwOutline.HANDLE : 0,
|
||||||
nwOutline.LEVEL : 40,
|
nwOutline.LEVEL : 40,
|
||||||
nwOutline.LABEL : 150,
|
nwOutline.LABEL : 150,
|
||||||
nwOutline.LINE : 40,
|
nwOutline.LINE : 40,
|
||||||
@@ -62,6 +63,7 @@ class GuiProjectOutline(QTreeWidget):
|
|||||||
|
|
||||||
DEF_HIDDEN = {
|
DEF_HIDDEN = {
|
||||||
nwOutline.TITLE : False,
|
nwOutline.TITLE : False,
|
||||||
|
nwOutline.HANDLE : True,
|
||||||
nwOutline.LEVEL : True,
|
nwOutline.LEVEL : True,
|
||||||
nwOutline.LABEL : False,
|
nwOutline.LABEL : False,
|
||||||
nwOutline.LINE : True,
|
nwOutline.LINE : True,
|
||||||
@@ -182,7 +184,17 @@ class GuiProjectOutline(QTreeWidget):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _treeDoubleClick(self, tItem, tCol):
|
def _treeDoubleClick(self, tItem, tCol):
|
||||||
print(tItem, tCol)
|
"""Extract the handle and line number of the title double-
|
||||||
|
clicked, and send it to the main gui class for opening in the
|
||||||
|
document editor.
|
||||||
|
"""
|
||||||
|
tHandle = tItem.text(self.colIndex[nwOutline.HANDLE])
|
||||||
|
try:
|
||||||
|
tLine = int(tItem.text(self.colIndex[nwOutline.LINE]))
|
||||||
|
except:
|
||||||
|
tLine = 1
|
||||||
|
logger.verbose("User selected entry with handle %s on line %s" % (tHandle, tLine))
|
||||||
|
self.theParent.openDocument(tHandle, tLine - 1)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _headerRightClick(self, clickPos):
|
def _headerRightClick(self, clickPos):
|
||||||
@@ -301,7 +313,11 @@ class GuiProjectOutline(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _populateTree(self):
|
def _populateTree(self):
|
||||||
"""Build the tree based on the project index.
|
"""Build the tree based on the project index, and the header
|
||||||
|
based on the defined constants, default values and user selected
|
||||||
|
width, order and hidden state. All columns are populated, even
|
||||||
|
if they are hidden. This ensures that showing and hiding columns
|
||||||
|
is fast and doesn't require a rebuild of the tree.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.clear()
|
self.clear()
|
||||||
@@ -317,6 +333,11 @@ class GuiProjectOutline(QTreeWidget):
|
|||||||
self.setColumnWidth(self.colIndex[hItem], self.colWidth[hItem])
|
self.setColumnWidth(self.colIndex[hItem], self.colWidth[hItem])
|
||||||
self.setColumnHidden(self.colIndex[hItem], self.colHidden[hItem])
|
self.setColumnHidden(self.colIndex[hItem], self.colHidden[hItem])
|
||||||
|
|
||||||
|
# Make sure title column is always visible,
|
||||||
|
# and handle column always hidden
|
||||||
|
self.setColumnHidden(self.colIndex[nwOutline.TITLE], False)
|
||||||
|
self.setColumnHidden(self.colIndex[nwOutline.HANDLE], True)
|
||||||
|
|
||||||
headItem = self.headerItem()
|
headItem = self.headerItem()
|
||||||
headItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight)
|
headItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight)
|
||||||
headItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight)
|
headItem.setTextAlignment(self.colIndex[nwOutline.WCOUNT], Qt.AlignRight)
|
||||||
@@ -390,6 +411,7 @@ class GuiProjectOutline(QTreeWidget):
|
|||||||
newItem = QTreeWidgetItem()
|
newItem = QTreeWidgetItem()
|
||||||
|
|
||||||
newItem.setText(self.colIndex[nwOutline.TITLE], novIdx["title"])
|
newItem.setText(self.colIndex[nwOutline.TITLE], novIdx["title"])
|
||||||
|
newItem.setText(self.colIndex[nwOutline.HANDLE], tHandle)
|
||||||
newItem.setText(self.colIndex[nwOutline.LEVEL], novIdx["level"])
|
newItem.setText(self.colIndex[nwOutline.LEVEL], novIdx["level"])
|
||||||
newItem.setText(self.colIndex[nwOutline.LABEL], nwItem.itemName)
|
newItem.setText(self.colIndex[nwOutline.LABEL], nwItem.itemName)
|
||||||
newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:])
|
newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:])
|
||||||
@@ -429,7 +451,7 @@ class GuiOutlineHeaderMenu(QMenu):
|
|||||||
|
|
||||||
self.actionMap = {}
|
self.actionMap = {}
|
||||||
for hItem in nwOutline:
|
for hItem in nwOutline:
|
||||||
if hItem == nwOutline.TITLE:
|
if hItem == nwOutline.TITLE or hItem == nwOutline.HANDLE:
|
||||||
continue
|
continue
|
||||||
self.actionMap[hItem] = QAction(nwLabels.OUTLINE_COLS[hItem], self)
|
self.actionMap[hItem] = QAction(nwLabels.OUTLINE_COLS[hItem], self)
|
||||||
self.actionMap[hItem].setCheckable(True)
|
self.actionMap[hItem].setCheckable(True)
|
||||||
@@ -447,7 +469,7 @@ class GuiOutlineHeaderMenu(QMenu):
|
|||||||
self.acceptToggle = False
|
self.acceptToggle = False
|
||||||
|
|
||||||
for hItem in nwOutline:
|
for hItem in nwOutline:
|
||||||
if hItem == nwOutline.TITLE or hItem not in hiddenState:
|
if hItem == nwOutline.TITLE or hItem == nwOutline.HANDLE or hItem not in hiddenState:
|
||||||
continue
|
continue
|
||||||
self.actionMap[hItem].setChecked(not hiddenState[hItem])
|
self.actionMap[hItem].setChecked(not hiddenState[hItem])
|
||||||
|
|
||||||
|
|||||||
@@ -267,11 +267,15 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.setFormat(4, len(theText), self.hStyles["header4"])
|
self.setFormat(4, len(theText), self.hStyles["header4"])
|
||||||
|
|
||||||
elif theText.startswith("%"): # Comments
|
elif theText.startswith("%"): # Comments
|
||||||
if theText.startswith("%synopsis:"):
|
toCheck = theText[1:].lstrip().lower()
|
||||||
self.setFormat(0, 10, self.hStyles["modifier"])
|
tLen = len(theText)
|
||||||
self.setFormat(10, len(theText), self.hStyles["hidden"])
|
cLen = len(toCheck)
|
||||||
|
cOff = tLen - cLen
|
||||||
|
if toCheck.startswith("synopsis:"):
|
||||||
|
self.setFormat(0, cOff+9, self.hStyles["modifier"])
|
||||||
|
self.setFormat(cOff+9, tLen, self.hStyles["hidden"])
|
||||||
else:
|
else:
|
||||||
self.setFormat(0, len(theText), self.hStyles["hidden"])
|
self.setFormat(0, tLen, self.hStyles["hidden"])
|
||||||
|
|
||||||
else: # Text Paragraph
|
else: # Text Paragraph
|
||||||
for rX, xFmt in self.rxRules:
|
for rX, xFmt in self.rxRules:
|
||||||
|
|||||||
+4
-2
@@ -452,11 +452,13 @@ class GuiMain(QMainWindow):
|
|||||||
self.docEditor.clearEditor()
|
self.docEditor.clearEditor()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def openDocument(self, tHandle):
|
def openDocument(self, tHandle, tLine=None):
|
||||||
|
"""Open a specific document, optionally at a given line.
|
||||||
|
"""
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
self.closeDocument()
|
self.closeDocument()
|
||||||
self.tabWidget.setCurrentWidget(self.splitView)
|
self.tabWidget.setCurrentWidget(self.splitView)
|
||||||
if self.docEditor.loadText(tHandle):
|
if self.docEditor.loadText(tHandle, tLine):
|
||||||
self.docEditor.setFocus()
|
self.docEditor.setFocus()
|
||||||
self.theProject.setLastEdited(tHandle)
|
self.theProject.setLastEdited(tHandle)
|
||||||
else:
|
else:
|
||||||
|
|||||||
+7
-2
@@ -308,9 +308,14 @@ class NWIndex():
|
|||||||
self.indexNoteRef(tHandle, aLine, nLine, nTitle)
|
self.indexNoteRef(tHandle, aLine, nLine, nTitle)
|
||||||
self.indexTag(tHandle, aLine, nLine, itemClass)
|
self.indexTag(tHandle, aLine, nLine, itemClass)
|
||||||
|
|
||||||
elif aLine.startswith(r"%synopsis:"):
|
elif aLine.startswith(r"%"):
|
||||||
if nTitle > 0:
|
if nTitle > 0:
|
||||||
self.indexSynopsis(tHandle, isNovel, aLine[10:].strip(), nTitle)
|
toCheck = aLine[1:].lstrip().lower()
|
||||||
|
tLen = len(aLine)
|
||||||
|
cLen = len(toCheck)
|
||||||
|
cOff = tLen - cLen
|
||||||
|
if toCheck.startswith("synopsis:"):
|
||||||
|
self.indexSynopsis(tHandle, isNovel, aLine[cOff+9:].strip(), nTitle)
|
||||||
|
|
||||||
# Count words for remaining text after last heading
|
# Count words for remaining text after last heading
|
||||||
if nTitle > 0:
|
if nTitle > 0:
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
@pov: Jane
|
@pov: Jane
|
||||||
@location: Earth
|
@location: Earth
|
||||||
|
|
||||||
%synopsis: We can add a chapter file, but keep the scene files separate. In the chapter file we can set the meta data that applies to the whole chapter if we wish to.
|
% Synopsis: We can add a chapter file, but keep the scene files separate. In the chapter file we can set the meta data that applies to the whole chapter if we wish to.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="0.4.5" fileVersion="1.0" saveCount="38" autoCount="8" timeStamp="2020-04-13 16:55:55">
|
<novelWriterXML appVersion="0.4.5" fileVersion="1.0" saveCount="52" autoCount="8" timeStamp="2020-04-26 18:05:45">
|
||||||
<project>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
<charCount>12</charCount>
|
<charCount>12</charCount>
|
||||||
<wordCount>3</wordCount>
|
<wordCount>3</wordCount>
|
||||||
<paraCount>0</paraCount>
|
<paraCount>0</paraCount>
|
||||||
<cursorPos>214</cursorPos>
|
<cursorPos>215</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="636b6aa9b697b" order="1" parent="e7ded148d6e4a">
|
<item handle="636b6aa9b697b" order="1" parent="e7ded148d6e4a">
|
||||||
<name>Making a Scene</name>
|
<name>Making a Scene</name>
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
<charCount>1199</charCount>
|
<charCount>1199</charCount>
|
||||||
<wordCount>216</wordCount>
|
<wordCount>216</wordCount>
|
||||||
<paraCount>7</paraCount>
|
<paraCount>7</paraCount>
|
||||||
<cursorPos>19</cursorPos>
|
<cursorPos>950</cursorPos>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
|
||||||
<name>Another Scene</name>
|
<name>Another Scene</name>
|
||||||
|
|||||||
Reference in New Issue
Block a user