Simplify the novel outline tree

This commit is contained in:
Veronica Berglyd Olsen
2022-06-14 00:14:51 +02:00
parent 49ace054bd
commit ea61db9591
5 changed files with 101 additions and 114 deletions
+9
View File
@@ -57,6 +57,15 @@ class nwRegEx:
# END Class nwRegEx
class nwHeaders:
H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
TT_NONE = "T000000"
# END Class nwHeaders
class nwFiles:
PROJ_FILE = "nwProject.nwx"
+8 -12
View File
@@ -34,7 +34,7 @@ from time import time
from novelwriter.enum import nwItemType, nwItemLayout
from novelwriter.error import logException
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode, nwHeaders
from novelwriter.core.document import NWDoc
from novelwriter.common import (
checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
@@ -42,10 +42,6 @@ from novelwriter.common import (
logger = logging.getLogger(__name__)
H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
TT_NONE = "T000000"
class NWIndex:
"""This class holds the entire index for a given project. The index
@@ -477,7 +473,7 @@ class NWIndex:
"""
hCount = [0, 0, 0, 0, 0]
for _, _, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl):
iLevel = H_LEVEL.get(hItem.level, 0)
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
hCount[iLevel] += 1
return hCount
@@ -510,7 +506,7 @@ class NWIndex:
pKey = None
for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl):
tKey = f"{tHandle}:{sTitle}"
iLevel = H_LEVEL.get(hItem.level, 0)
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
if iLevel > maxDepth:
if pKey in tData:
tData[pKey]["words"] += hItem.wordCount
@@ -660,7 +656,7 @@ class TagsIndex:
"""
if tagKey in self._tags:
return self._tags.get(tagKey).get("heading")
return TT_NONE
return nwHeaders.TT_NONE
def tagClass(self, tagKey):
"""Get the class of a given tag.
@@ -906,7 +902,7 @@ class IndexItem:
self._index = 0
# Add a placeholder heading
self._headings[TT_NONE] = IndexHeading(TT_NONE)
self._headings[nwHeaders.TT_NONE] = IndexHeading(nwHeaders.TT_NONE)
return
@@ -940,8 +936,8 @@ class IndexItem:
"""Add a heading to the item. Also remove the placeholder entry
if it exists.
"""
if TT_NONE in self._headings:
self._headings.pop(TT_NONE)
if nwHeaders.TT_NONE in self._headings:
self._headings.pop(nwHeaders.TT_NONE)
self._headings[tHeading.key] = tHeading
return
@@ -1110,7 +1106,7 @@ class IndexHeading:
def setLevel(self, level):
"""Set the level of the header if it's a valid value.
"""
if level in H_VALID:
if level in nwHeaders.H_VALID:
self._level = level
return
+1 -1
View File
@@ -653,7 +653,7 @@ class NWProject():
self._legacyDataFolder(projItem)
except Exception:
self.mainGui.makeAlert(self.tr(
"There was an error while converting project version 1.0. "
"There was an error updating the project. "
"Some data may not have been preserved."
), nwAlert.ERROR)
+73 -91
View File
@@ -4,7 +4,9 @@ novelWriter GUI Novel Tree
GUI classe for the main window novel tree
File History:
Created: 2020-12-20 [1.1a0]
Created: 2020-12-20 [1.1a0] GuiNovelTree
Created: 2022-06-12 [1.7b1] GuiNovelView
Created: 2022-06-12 [1.7b1] GuiNovelToolBar
This file is a part of novelWriter
Copyright 20182020, Veronica Berglyd Olsen
@@ -30,7 +32,7 @@ from time import time
from enum import Enum
from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QPalette
from PyQt5.QtGui import QPalette, QPixmap, QColor
from PyQt5.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel,
QMenu, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
@@ -39,19 +41,19 @@ from PyQt5.QtWidgets import (
from novelwriter.enum import nwDocMode, nwItemClass
from novelwriter.common import checkInt
from novelwriter.constants import nwKeyWords, nwLabels, trConst
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
logger = logging.getLogger(__name__)
class NovelColumnType(Enum):
class NovelTreeColumn(Enum):
HIDDEN = 0
POV = 1
FOCUS = 2
PLOT = 3
# END Enum NovelColumnType
# END Enum NovelTreeColumn
class GuiNovelView(QWidget):
@@ -209,16 +211,16 @@ class GuiNovelToolBar(QWidget):
self.mCol3 = self.mMore.addMenu(self.tr("Third Column"))
self.mCol3.addAction(self.tr("Hide Column")).triggered.connect(
lambda: self.novelView.novelTree.setLastColType(NovelColumnType.HIDDEN)
lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.HIDDEN)
)
self.mCol3.addAction(self.tr("Point of View Character")).triggered.connect(
lambda: self.novelView.novelTree.setLastColType(NovelColumnType.POV)
lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.POV)
)
self.mCol3.addAction(self.tr("Focus Character")).triggered.connect(
lambda: self.novelView.novelTree.setLastColType(NovelColumnType.FOCUS)
lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.FOCUS)
)
self.mCol3.addAction(self.tr("Novel Plot")).triggered.connect(
lambda: self.novelView.novelTree.setLastColType(NovelColumnType.PLOT)
lambda: self.novelView.novelTree.setLastColType(NovelTreeColumn.PLOT)
)
self.tbMore = QToolButton(self)
@@ -305,9 +307,9 @@ class GuiNovelTree(QTreeWidget):
# Internal Variables
self._treeMap = {}
self._lastBuild = 0
self._lastCol = NovelColumnType.POV
self._lastCol = NovelTreeColumn.POV
# Cached i18n Strings
# Cached Strings
self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
@@ -316,12 +318,15 @@ class GuiNovelTree(QTreeWidget):
# =========
iPx = self.mainTheme.baseIconSize
nPx = self.mainTheme.textNWidth
cMg = self.mainConf.pxInt(6)
mPx = self.mainConf.pxInt(4)
nMg = self.mainConf.pxInt(6)
self.setIconSize(QSize(iPx, iPx))
# self.setIconSize(QSize(iPx, iPx))
self.setFrameStyle(QFrame.NoFrame)
self.setHeaderHidden(True)
self.setIndentation(iPx)
self.setIndentation(mPx)
self.setColumnCount(3)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection)
@@ -336,6 +341,25 @@ class GuiNovelTree(QTreeWidget):
treeHeader.setSectionResizeMode(self.C_WORDS, QHeaderView.ResizeToContents)
treeHeader.setSectionResizeMode(self.C_LAST, QHeaderView.ResizeToContents)
# Pre-Generate Tree Formatting
fH1 = self.font()
fH1.setBold(True)
fH1.setUnderline(True)
fH2 = self.font()
fH2.setBold(True)
self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()]
self._hIndent = ["", "", "", "\u2022\u00a0", "\u00bb\u00a0"]
self._pIndent = [QPixmap(), QPixmap()]
hPix = QPixmap(QSize(iPx, iPx))
hPix.fill(QColor(0, 0, 0, 0))
for m in range(1, 4):
self._pIndent.append(hPix.scaled(
max(nPx*m - nMg, nMg), 2, Qt.IgnoreAspectRatio, Qt.FastTransformation
))
# Connect signals
self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._treeSelectionChange)
@@ -379,9 +403,9 @@ class GuiNovelTree(QTreeWidget):
"""Load user options.
"""
self._lastCol = self.theProject.options.getEnum(
"GuiNovelView", "lastCol", NovelColumnType, NovelColumnType.POV
"GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.POV
)
self.setColumnHidden(self.C_LAST, self._lastCol == NovelColumnType.HIDDEN)
self.setColumnHidden(self.C_LAST, self._lastCol == NovelTreeColumn.HIDDEN)
return True
def saveOptions(self):
@@ -444,7 +468,7 @@ class GuiNovelTree(QTreeWidget):
if self._lastCol != colType:
logger.debug("Changing last column to %s", colType.name)
self._lastCol = colType
self.setColumnHidden(self.C_LAST, colType == NovelColumnType.HIDDEN)
self.setColumnHidden(self.C_LAST, colType == NovelTreeColumn.HIDDEN)
self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True)
return
@@ -509,100 +533,58 @@ class GuiNovelTree(QTreeWidget):
"""Build the tree based on the project index.
"""
self.clearTree()
currTitle = None
currChapter = None
currScene = None
tStart = time()
logger.verbose("Building novel tree for root item '%s'", rootHandle)
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
for tKey, tHandle, sTitle, novIdx in novStruct:
tItem = self._createTreeItem(tHandle, sTitle, tKey, novIdx)
self._treeMap[tKey] = tItem
iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
if iLevel == 0:
continue
tLevel = novIdx.level
if tLevel == "H1":
self.addTopLevelItem(tItem)
currTitle = tItem
currChapter = None
currScene = None
newItem = QTreeWidgetItem()
theData = (tHandle, sTitle[1:].lstrip("0"), tKey)
elif tLevel == "H2":
if currTitle is None:
self.addTopLevelItem(tItem)
else:
currTitle.addChild(tItem)
currChapter = tItem
currScene = None
newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel])
newItem.setText(self.C_TITLE, self._hIndent[iLevel] + novIdx.title)
newItem.setData(self.C_TITLE, Qt.UserRole, theData)
newItem.setFont(self.C_TITLE, self._hFonts[iLevel])
newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}")
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
elif tLevel == "H3":
if currChapter is None:
if currTitle is None:
self.addTopLevelItem(tItem)
else:
currTitle.addChild(tItem)
else:
currChapter.addChild(tItem)
currScene = tItem
lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
newItem.setText(self.C_LAST, lastText)
if lastText:
newItem.setToolTip(self.C_LAST, toolTip)
elif tLevel == "H4":
if currScene is None:
if currChapter is None:
if currTitle is None:
self.addTopLevelItem(tItem)
else:
currTitle.addChild(tItem)
else:
currChapter.addChild(tItem)
else:
currScene.addChild(tItem)
tItem.setExpanded(True)
self._treeMap[tKey] = newItem
self.addTopLevelItem(newItem)
logger.verbose("Novel Tree built in %.3f ms", (time() - tStart)*1000)
self._lastBuild = time()
return
def _createTreeItem(self, tHandle, sTitle, titleKey, novIdx):
"""Populate a tree item with all the column values.
def _getLastColumnText(self, tHandle, sTitle):
"""Generate the text for the last column based on user settings.
"""
newItem = QTreeWidgetItem()
hIcon = "doc_%s" % novIdx.level.lower()
theData = (tHandle, sTitle[1:].lstrip("0"), titleKey)
if self._lastCol == NovelTreeColumn.HIDDEN:
return "", ""
wC = int(novIdx.wordCount)
theRefs = self.theProject.index.getReferences(tHandle, sTitle)
if self._lastCol == NovelTreeColumn.POV:
newText = ", ".join(theRefs[nwKeyWords.POV_KEY])
return newText, f"{self._povLabel}: {newText}"
newItem.setText(self.C_TITLE, novIdx.title)
newItem.setData(self.C_TITLE, Qt.UserRole, theData)
newItem.setIcon(self.C_TITLE, self.mainTheme.getIcon(hIcon))
newItem.setText(self.C_WORDS, f"{wC:n}")
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
elif self._lastCol == NovelTreeColumn.FOCUS:
newText = ", ".join(theRefs[nwKeyWords.FOCUS_KEY])
return newText, f"{self._focLabel}: {newText}"
if self._lastCol == NovelColumnType.HIDDEN:
newItem.setText(self.C_LAST, "")
else:
theRefs = self.theProject.index.getReferences(tHandle, sTitle)
if self._lastCol == NovelColumnType.POV:
newText = ", ".join(theRefs[nwKeyWords.POV_KEY])
newItem.setText(self.C_LAST, newText)
if newText:
newItem.setToolTip(self.C_LAST, f"{self._povLabel}: {newText}")
elif self._lastCol == NovelColumnType.FOCUS:
newText = ", ".join(theRefs[nwKeyWords.FOCUS_KEY])
newItem.setText(self.C_LAST, newText)
if newText:
newItem.setToolTip(self.C_LAST, f"{self._focLabel}: {newText}")
elif self._lastCol == NovelColumnType.PLOT:
newText = ", ".join(theRefs[nwKeyWords.PLOT_KEY])
newItem.setText(self.C_LAST, newText)
if newText:
newItem.setToolTip(self.C_LAST, f"{self._pltLabel}: {newText}")
elif self._lastCol == NovelTreeColumn.PLOT:
newText = ", ".join(theRefs[nwKeyWords.PLOT_KEY])
return newText, f"{self._pltLabel}: {newText}"
return newItem
return "", ""
# END Class GuiNovelTree
+10 -10
View File
@@ -1582,30 +1582,30 @@ class GuiMain(QMainWindow):
return
@pyqtSlot(int)
def _mainStackChanged(self, tabIndex):
def _mainStackChanged(self, stIndex):
"""Activated when the main window tab is changed.
"""
if tabIndex == self.idxEditorView:
logger.verbose("Editor tab activated")
elif tabIndex == self.idxOutlineView:
logger.verbose("Project outline tab activated")
if stIndex == self.idxEditorView:
logger.verbose("Editor View activated")
elif stIndex == self.idxOutlineView:
logger.verbose("Outline View activated")
if self.hasProject:
self.outlineView.refreshView()
return
@pyqtSlot(int)
def _projStackChanged(self, tabIndex):
def _projStackChanged(self, stIndex):
"""Activated when the project view tab is changed.
"""
sHandle = None
if tabIndex == self.idxProjView:
logger.verbose("Project tree tab activated")
if stIndex == self.idxProjView:
logger.verbose("Project Tree View activated")
sHandle = self.projView.getSelectedHandle()
elif tabIndex == self.idxNovelView:
logger.verbose("Novel tree tab activated")
elif stIndex == self.idxNovelView:
logger.verbose("Novel Tree View activated")
if self.hasProject:
self.novelView.refreshTree()
sHandle, _ = self.novelView.getSelectedHandle()