Simplify the novel outline tree
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 2018–2020, 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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user