diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index e3c5517c..827d287a 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -39,7 +39,7 @@ from novelwriter.common import isHandle, isItemClass, isTitleTag, jsonEncode from novelwriter.constants import nwFiles, nwKeyWords, nwStyles from novelwriter.core.indexdata import NOTE_TYPES, TT_NONE, IndexHeading, IndexNode, T_NoteTypes from novelwriter.core.novelmodel import NovelModel -from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType +from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType, nwNovelExtra from novelwriter.error import logException from novelwriter.text.comments import processComment from novelwriter.text.counting import standardCounter @@ -92,6 +92,7 @@ class Index: # Models self._novelModels: dict[str, NovelModel] = {} + self._novelExtra = nwNovelExtra.HIDDEN # TimeStamps self._indexChange = 0.0 @@ -120,6 +121,15 @@ class Index: self._generateNovelModel(tHandle) return self._novelModels.get(tHandle) + ## + # Setters + ## + + def setNovelModelExtraColumn(self, extra: nwNovelExtra) -> None: + """Set the data content type of the novel model extra column.""" + self._novelExtra = extra + return + ## # Public Methods ## @@ -184,6 +194,7 @@ class Index: logger.info("Refreshing novel model '%s'", tHandle) model.beginResetModel() model.clear() + model.setExtraColumn(self._novelExtra) self._appendSubTreeToModel(tHandle, model) model.endResetModel() return @@ -439,8 +450,9 @@ class Index: self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC) return - def _indexKeyword(self, tHandle: str, line: str, sTitle: str, - itemClass: nwItemClass, tags: dict[str, bool]) -> None: + def _indexKeyword( + self, tHandle: str, line: str, sTitle: str, itemClass: nwItemClass, tags: dict[str, bool] + ) -> None: """Validate and save the information about a reference to a tag in another file, or the setting of a tag in the file. A record of active tags is updated so that no longer used tags can be @@ -469,6 +481,7 @@ class Index: """Generate a novel model for a specific handle.""" if (item := SHARED.project.tree[tHandle]) and item.isRootType() and item.isNovelLike(): model = NovelModel() + model.setExtraColumn(self._novelExtra) self._appendSubTreeToModel(tHandle, model) self._novelModels[tHandle] = model return diff --git a/novelwriter/core/novelmodel.py b/novelwriter/core/novelmodel.py index d4dafa35..e52b3bc2 100644 --- a/novelwriter/core/novelmodel.py +++ b/novelwriter/core/novelmodel.py @@ -25,12 +25,14 @@ from __future__ import annotations import logging +from PyQt5.QtCore import QSize from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt from PyQt6.QtGui import QIcon, QPixmap from novelwriter import SHARED -from novelwriter.constants import nwStyles +from novelwriter.constants import nwKeyWords, nwStyles from novelwriter.core.indexdata import IndexHeading, IndexNode +from novelwriter.enum import nwNovelExtra from novelwriter.error import logException from novelwriter.types import QtAlignRight @@ -50,26 +52,49 @@ T_NodeData = str | tuple[str, str] | QIcon | QPixmap | Qt.AlignmentFlag | None class NovelModel(QAbstractTableModel): - __slots__ = ("_rows", "_more", "_columns") + __slots__ = ("_rows", "_header", "_more", "_columns", "_extra") def __init__(self) -> None: super().__init__() self._rows: list[dict[int, T_NodeData]] = [] + self._header: list[QSize] = [] self._more = SHARED.theme.getIcon("more_arrow") self._columns = 3 + self._extra = "" return def __del__(self) -> None: # pragma: no cover logger.debug("Delete: NovelModel") return + ## + # Properties + ## + + @property + def columns(self) -> int: + """Return the number of columns.""" + return self._columns + ## # Setters ## - def setExtraColumn(self, state: bool) -> None: + def setExtraColumn(self, extra: nwNovelExtra) -> None: """Set extra data column settings.""" - self._columns = 4 if state else 3 + match extra: + case nwNovelExtra.HIDDEN: + self._columns = 3 + self._extra = "" + case nwNovelExtra.POV: + self._columns = 4 + self._extra = nwKeyWords.POV_KEY + case nwNovelExtra.FOCUS: + self._columns = 4 + self._extra = nwKeyWords.FOCUS_KEY + case nwNovelExtra.PLOT: + self._columns = 4 + self._extra = nwKeyWords.PLOT_KEY return ## @@ -183,13 +208,17 @@ class NovelModel(QAbstractTableModel): def _generateEntry(self, handle: str, key: str, heading: IndexHeading) -> dict: """Generate a cache entry.""" iLevel = nwStyles.H_LEVEL.get(heading.level, 0) - more = self._columns - 1 data = {} data[C_FACTOR*0 | R_TEXT] = heading.title data[C_FACTOR*0 | R_ICON] = SHARED.theme.getHeaderDecoration(iLevel) data[C_FACTOR*1 | R_TEXT] = f"{heading.mainCount:n}" data[C_FACTOR*1 | R_ALIGN] = QtAlignRight - data[C_FACTOR*more | R_ICON] = self._more + if self._columns == 3: + data[C_FACTOR*2 | R_ICON] = self._more + else: + data[C_FACTOR*2 | R_TIP] = "Hello World" + data[C_FACTOR*2 | R_TEXT] = "Hello World" + data[C_FACTOR*3 | R_ICON] = self._more data[R_HANDLE] = handle data[R_KEY] = key return data diff --git a/novelwriter/enum.py b/novelwriter/enum.py index c9d871b3..a837d824 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -180,6 +180,14 @@ class nwOutline(Enum): SYNOP = 19 +class nwNovelExtra(Enum): + + HIDDEN = 0 + POV = 1 + FOCUS = 2 + PLOT = 3 + + class nwBuildFmt(Enum): ODT = 0 diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 5710cb35..c26d0fdd 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -30,36 +30,28 @@ import logging from enum import Enum from PyQt6.QtCore import QModelIndex, QPoint, pyqtSignal, pyqtSlot -from PyQt6.QtGui import QActionGroup, QFont, QPainter, QPalette +from PyQt6.QtGui import QActionGroup, QFont, QPainter, QPalette, QResizeEvent from PyQt6.QtWidgets import ( - QAbstractItemView, QFrame, QHBoxLayout, QMenu, QStyleOptionViewItem, - QToolTip, QVBoxLayout, QWidget + QAbstractItemView, QFrame, QHBoxLayout, QInputDialog, QMenu, + QStyleOptionViewItem, QToolTip, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED -from novelwriter.common import qtAddAction, qtAddMenu, qtLambda +from novelwriter.common import minmax, qtAddAction, qtAddMenu, qtLambda from novelwriter.constants import nwKeyWords, nwLabels, trConst from novelwriter.core.novelmodel import NovelModel -from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwOutline +from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwNovelExtra, nwOutline from novelwriter.extensions.modified import NIconToolButton, NTreeView from novelwriter.extensions.novelselector import NovelSelector from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.types import ( - QtHeaderStretch, QtHeaderToContents, QtScrollAlwaysOff, QtScrollAsNeeded, - QtSizeExpanding + QtHeaderFixed, QtHeaderStretch, QtHeaderToContents, QtScrollAlwaysOff, + QtScrollAsNeeded, QtSizeExpanding ) logger = logging.getLogger(__name__) -class NovelTreeColumn(Enum): - - HIDDEN = 0 - POV = 1 - FOCUS = 2 - PLOT = 3 - - class GuiNovelView(QWidget): # Signals for user interaction with the novel tree @@ -119,11 +111,11 @@ class GuiNovelView(QWidget): logger.debug("Setting novel tree to root item '%s'", lastNovel) lastCol = SHARED.project.options.getEnum( - "GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.HIDDEN + "GuiNovelView", "lastCol", nwNovelExtra, nwNovelExtra.HIDDEN + ) + lastColSize = SHARED.project.options.getInt( + "GuiNovelView", "lastColSize", 25 ) - # lastColSize = SHARED.project.options.getInt( - # "GuiNovelView", "lastColSize", 25 - # ) self.clearNovelView() self.novelBar.buildNovelRootMenu() @@ -131,18 +123,18 @@ class GuiNovelView(QWidget): self.novelBar.setCurrentRoot(lastNovel) self.novelBar.setEnabled(True) - # self.novelTree.setLastColSize(lastColSize) + self.novelTree.setLastColSize(lastColSize) return def closeProjectTasks(self) -> None: """Run closing project tasks.""" - # lastColType = self.novelTree.lastColType - # lastColSize = self.novelTree.lastColSize - # logger.debug("Saving State: GuiNovelView") - # pOptions = SHARED.project.options - # pOptions.setValue("GuiNovelView", "lastCol", lastColType) - # pOptions.setValue("GuiNovelView", "lastColSize", lastColSize) + lastColType = self.novelTree.lastColType + lastColSize = self.novelTree.lastColSize + logger.debug("Saving State: GuiNovelView") + options = SHARED.project.options + options.setValue("GuiNovelView", "lastCol", lastColType) + options.setValue("GuiNovelView", "lastColSize", lastColSize) self.clearNovelView() return @@ -164,7 +156,7 @@ class GuiNovelView(QWidget): """Set the current novel to display.""" if rootHandle and (model := SHARED.project.index.getNovelModel(rootHandle)): self.novelTree.setModel(model) - self.novelTree.resizeColumsn() + self.novelTree.resizeColumns() return @pyqtSlot(str) @@ -232,10 +224,10 @@ class GuiNovelToolBar(QWidget): self.mLastCol = qtAddMenu(self.mMore, self.tr("Last Column")) self.gLastCol = QActionGroup(self.mMore) self.aLastCol = {} - self._addLastColAction(NovelTreeColumn.HIDDEN, self.tr("Hidden")) - self._addLastColAction(NovelTreeColumn.POV, self.tr("Point of View Character")) - self._addLastColAction(NovelTreeColumn.FOCUS, self.tr("Focus Character")) - self._addLastColAction(NovelTreeColumn.PLOT, self.tr("Novel Plot")) + self._addLastColAction(nwNovelExtra.HIDDEN, self.tr("Hidden")) + self._addLastColAction(nwNovelExtra.POV, self.tr("Point of View Character")) + self._addLastColAction(nwNovelExtra.FOCUS, self.tr("Focus Character")) + self._addLastColAction(nwNovelExtra.PLOT, self.tr("Novel Plot")) self.mLastCol.addSeparator() self.aLastColSize = qtAddAction(self.mLastCol, self.tr("Column Size")) @@ -312,10 +304,13 @@ class GuiNovelToolBar(QWidget): self.novelView.setCurrentNovel(rootHandle) return - def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None: + def setLastColType(self, colType: nwNovelExtra, doRefresh: bool = True) -> None: """Set the last column type.""" - # self.aLastCol[colType].setChecked(True) - # self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh) + self.aLastCol[colType].setChecked(True) + self.novelView.novelTree.setLastColType(colType) + if doRefresh: + self._forceRefreshNovelTree() + self.novelView.novelTree.resizeColumns() return def setActive(self, state: bool) -> None: @@ -352,20 +347,20 @@ class GuiNovelToolBar(QWidget): @pyqtSlot() def _selectLastColumnSize(self) -> None: """Set the maximum width for the last column.""" - # oldSize = self.novelView.novelTree.lastColSize - # newSize, isOk = QInputDialog.getInt( - # self, self.tr("Column Size"), self.tr("Maximum column size in %"), oldSize, 15, 75, 5 - # ) - # if isOk: - # self.novelView.novelTree.setLastColSize(newSize) - # self._refreshNovelTree() + oldSize = self.novelView.novelTree.lastColSize + newSize, isOk = QInputDialog.getInt( + self, self.tr("Column Size"), self.tr("Maximum column size in %"), oldSize, 15, 75, 5 + ) + if isOk: + self.novelView.novelTree.setLastColSize(newSize) + self.novelView.novelTree.resizeColumns() return ## # Internal Functions ## - def _addLastColAction(self, colType: NovelTreeColumn, actionLabel: str) -> None: + def _addLastColAction(self, colType: nwNovelExtra, actionLabel: str) -> None: """Add a column selection entry to the last column menu.""" aLast = qtAddAction(self.mLastCol, actionLabel) aLast.setCheckable(True) @@ -385,10 +380,10 @@ class GuiNovelTree(NTreeView): self.novelView = novelView # Internal Variables - self._actHandle = None + self._actHandle = None + self._lastColType = nwNovelExtra.POV + self._lastColSize = 0.25 # self._lastBuild = 0 - # self._lastCol = NovelTreeColumn.POV - # self._lastColSize = 0.25 # self._treeMap: dict[str, QTreeWidgetItem] = {} # Cached Strings @@ -432,6 +427,20 @@ class GuiNovelTree(NTreeView): self.setHorizontalScrollBarPolicy(QtScrollAsNeeded) return + ## + # Properties + ## + + @property + def lastColType(self) -> nwNovelExtra: + """The data type of the extra column.""" + return self._lastColType + + @property + def lastColSize(self) -> int: + """Return the size of the extra column.""" + return int(self._lastColSize * 100) + ## # Getters ## @@ -456,18 +465,34 @@ class GuiNovelTree(NTreeView): viewport.repaint() return + def setLastColType(self, colType: nwNovelExtra) -> None: + """Set the extra column type.""" + self._lastColType = colType + SHARED.project.index.setNovelModelExtraColumn(colType) + return + + def setLastColSize(self, colSize: int) -> None: + """Set the extra column size between 15% and 75%.""" + self._lastColSize = minmax(colSize, 15, 75)/100.0 + return + ## # Class Methods ## - def resizeColumsn(self) -> None: + def resizeColumns(self) -> None: """Set the correct column sizes.""" - if (header := self.header()) and (model := self._getModel()): + if (header := self.header()) and (model := self._getModel()) and (vp := self.viewport()): header.setStretchLastSection(False) header.setMinimumSectionSize(SHARED.theme.baseIconHeight + 6) header.setSectionResizeMode(0, QtHeaderStretch) - for i in range(1, model.columnCount(QModelIndex())): - header.setSectionResizeMode(i, QtHeaderToContents) + header.setSectionResizeMode(1, QtHeaderToContents) + if model.columns == 3: + header.setSectionResizeMode(2, QtHeaderToContents) + elif model.columns == 4: + header.setSectionResizeMode(2, QtHeaderFixed) + header.setSectionResizeMode(3, QtHeaderToContents) + header.resizeSection(2, int(self._lastColSize * vp.width())) return ## @@ -481,6 +506,16 @@ class GuiNovelTree(NTreeView): super().drawRow(painter, opt, index) return + ## + # Events + ## + + def resizeEvent(self, event: QResizeEvent) -> None: + """Process size changed.""" + super().resizeEvent(event) + self.resizeColumns() + return + ## # Private Slots ## @@ -566,18 +601,6 @@ class GuiNovelTree(NTreeView): # Old Code ## - ## - # Properties - ## - - # @property - # def lastColType(self) -> NovelTreeColumn: - # return self._lastCol - - # @property - # def lastColSize(self) -> int: - # return int(self._lastColSize * 100) - ## # Class Methods ## @@ -632,11 +655,6 @@ class GuiNovelTree(NTreeView): # self.refreshTree(rootHandle=lastNovel, overRide=True) # return - # def setLastColSize(self, colSize: int) -> None: - # """Set the column size in integer values between 15 and 75.""" - # self._lastColSize = minmax(colSize, 15, 75)/100.0 - # return - # def setActiveHandle(self, tHandle: str | None) -> None: # """Highlight the rows associated with a given handle.""" # didScroll = False @@ -693,24 +711,6 @@ class GuiNovelTree(NTreeView): # self.clearSelection() # return - # def resizeEvent(self, event: QResizeEvent) -> None: - # """Elide labels in the extra column.""" - # super().resizeEvent(event) - # newW = event.size().width() - # oldW = event.oldSize().width() - # if newW != oldW: - # eliW = int(self._lastColSize * newW) - # fMetric = self.fontMetrics() - # for i in range(self.topLevelItemCount()): - # trItem = self.topLevelItem(i) - # if isinstance(trItem, QTreeWidgetItem): - # lastText = trItem.data(self.C_DATA, self.D_EXTRA) - # trItem.setText( - # self.C_EXTRA, - # fMetric.elidedText(lastText, Qt.TextElideMode.ElideRight, eliW) - # ) - # return - ## # Private Slots ##