From 43e420ab0b68fb608bb6e69a3527c5991506a07f Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sat, 22 Feb 2025 23:13:57 +0100 Subject: [PATCH] Restore novel heading details tooltip and active row colouring --- novelwriter/core/index.py | 6 +- novelwriter/core/novelmodel.py | 6 +- novelwriter/gui/noveltree.py | 139 ++++++++++++++++++--------------- novelwriter/gui/projtree.py | 1 - 4 files changed, 83 insertions(+), 69 deletions(-) diff --git a/novelwriter/core/index.py b/novelwriter/core/index.py index 0adbe3c1..59899c6b 100644 --- a/novelwriter/core/index.py +++ b/novelwriter/core/index.py @@ -125,7 +125,11 @@ class Index: if (item := SHARED.project.tree[tHandle]) and item.isRootType() and item.isNovelLike(): model = NovelModel() for handle in SHARED.project.tree.subTree(tHandle): - if node := self._itemIndex[handle]: + if ( + (node := self._itemIndex[handle]) + and node.item.isDocumentLayout() + and node.item.isActive + ): model.append(node) self._novelModels[tHandle] = model return diff --git a/novelwriter/core/novelmodel.py b/novelwriter/core/novelmodel.py index caa7c977..32f1c518 100644 --- a/novelwriter/core/novelmodel.py +++ b/novelwriter/core/novelmodel.py @@ -84,8 +84,10 @@ class NovelModel(QAbstractTableModel): def data(self, index: QModelIndex, role: Qt.ItemDataRole) -> T_NodeData: """Return display data for a node.""" - if index.isValid() and (row := index.row()) < len(self._rows): - return self._rows[row].get(C_FACTOR*index.column() | role) + try: + return self._rows[index.row()].get(C_FACTOR*index.column() | role) + except Exception: + print("NovelModel Debug: Oops!") return None def keys(self, index: QModelIndex) -> tuple[str | None, str | None]: diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 4d315731..c3f8a5fe 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -29,20 +29,24 @@ import logging from enum import Enum -from PyQt6.QtCore import QModelIndex, pyqtSignal, pyqtSlot -from PyQt6.QtGui import QActionGroup, QFont, QPalette -from PyQt6.QtWidgets import QAbstractItemView, QFrame, QHBoxLayout, QMenu, QVBoxLayout, QWidget +from PyQt6.QtCore import QModelIndex, QPoint, pyqtSignal, pyqtSlot +from PyQt6.QtGui import QActionGroup, QFont, QPainter, QPalette +from PyQt6.QtWidgets import ( + QAbstractItemView, QFrame, QHBoxLayout, QMenu, QStyleOptionViewItem, + QToolTip, QVBoxLayout, QWidget +) from novelwriter import CONFIG, SHARED from novelwriter.common import qtAddAction, qtAddMenu, qtLambda +from novelwriter.constants import nwKeyWords, nwLabels, trConst from novelwriter.core.novelmodel import NovelModel -from novelwriter.enum import nwChange, nwDocMode, nwItemClass +from novelwriter.enum import nwChange, nwDocMode, nwItemClass, 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, QtUserRole + QtSizeExpanding ) logger = logging.getLogger(__name__) @@ -166,7 +170,7 @@ class GuiNovelView(QWidget): @pyqtSlot(str) def setActiveHandle(self, tHandle: str) -> None: """Highlight the rows associated with a given handle.""" - # self.novelTree.setActiveHandle(tHandle) + self.novelTree.setActiveHandle(tHandle) return @pyqtSlot() @@ -350,17 +354,6 @@ class GuiNovelToolBar(QWidget): class GuiNovelTree(NTreeView): - C_DATA = 0 - C_TITLE = 0 - C_WORDS = 1 - C_EXTRA = 2 - C_MORE = 3 - - D_HANDLE = QtUserRole - D_TITLE = QtUserRole + 1 - D_KEY = QtUserRole + 2 - D_EXTRA = QtUserRole + 3 - def __init__(self, novelView: GuiNovelView) -> None: super().__init__(parent=novelView) @@ -369,10 +362,10 @@ class GuiNovelTree(NTreeView): self.novelView = novelView # Internal Variables + self._actHandle = None # self._lastBuild = 0 # self._lastCol = NovelTreeColumn.POV # self._lastColSize = 0.25 - # self._actHandle = None # self._treeMap: dict[str, QTreeWidgetItem] = {} # Cached Strings @@ -428,6 +421,17 @@ class GuiNovelTree(NTreeView): return model.keys(self.currentIndex()) return None, None + ## + # Setters + ## + + def setActiveHandle(self, tHandle: str | None) -> None: + """Set the handle to be highlighted.""" + self._actHandle = tHandle + if viewport := self.viewport(): + viewport.repaint() + return + ## # Class Methods ## @@ -442,6 +446,17 @@ class GuiNovelTree(NTreeView): header.setSectionResizeMode(i, QtHeaderToContents) return + ## + # Overloads + ## + + def drawRow(self, painter: QPainter, opt: QStyleOptionViewItem, index: QModelIndex) -> None: + """Draw a box on the active row.""" + if (model := self._getModel()) and model.keys(index)[0] == self._actHandle: + painter.fillRect(opt.rect, self.palette().alternateBase()) + super().drawRow(painter, opt, index) + return + ## # Private Slots ## @@ -449,8 +464,13 @@ class GuiNovelTree(NTreeView): @pyqtSlot(QModelIndex) def _onSingleClick(self, index: QModelIndex) -> None: """The user single-clicked an index.""" - if (model := self._getModel()) and (keys := model.keys(index)) and (tHandle := keys[0]): - self.novelView.selectedItemChanged.emit(tHandle) + if index.isValid() and (model := self._getModel()): + keys = model.keys(index) + if (tHandle := keys[0]) and (sTitle := keys[1]): + self.novelView.selectedItemChanged.emit(tHandle) + if index.column() == model.columnCount(index) - 1: + pos = self.mapToGlobal(self.visualRect(index).topRight()) + self._popMetaBox(pos, tHandle, sTitle) return @pyqtSlot(QModelIndex) @@ -483,6 +503,40 @@ class GuiNovelTree(NTreeView): return model return None + def _popMetaBox(self, qPos: QPoint, tHandle: str, sTitle: str) -> None: + """Show the novel meta data box.""" + if head := SHARED.project.index.getItemHeading(tHandle, sTitle): + logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle) + if synopsis := head.synopsis: + label = trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP]) + synopsis = f"

{label}: {synopsis}

" + + def appendTags(refs: dict, key: str, lines: list[str]) -> list[str]: + """Generate a reference list for a given reference key.""" + if tags := ", ".join(refs.get(key, [])): + lines.append(f"{trConst(nwLabels.KEY_NAME[key])}: {tags}") + return lines + + tags = SHARED.project.index.getReferences(tHandle, sTitle) + lines = [] + lines = appendTags(tags, nwKeyWords.POV_KEY, lines) + lines = appendTags(tags, nwKeyWords.FOCUS_KEY, lines) + lines = appendTags(tags, nwKeyWords.CHAR_KEY, lines) + lines = appendTags(tags, nwKeyWords.PLOT_KEY, lines) + lines = appendTags(tags, nwKeyWords.TIME_KEY, lines) + lines = appendTags(tags, nwKeyWords.WORLD_KEY, lines) + lines = appendTags(tags, nwKeyWords.OBJECT_KEY, lines) + lines = appendTags(tags, nwKeyWords.ENTITY_KEY, lines) + lines = appendTags(tags, nwKeyWords.CUSTOM_KEY, lines) + + text = "" + if lines: + refs = "
".join(lines) + text = f"

{refs}

" + if tooltip := (text + synopsis or self.tr("No meta data")): + QToolTip.showText(qPos, tooltip) + return + ## # Old Code ## @@ -747,48 +801,3 @@ class GuiNovelTree(NTreeView): # return toolText, f"{refName}: {toolText}" # return "", "" - - # def _popMetaBox(self, qPos: QPoint, tHandle: str, sTitle: str) -> None: - # """Show the novel meta data box.""" - # logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle) - - # pIndex = SHARED.project.index - # novIdx = pIndex.getItemHeading(tHandle, sTitle) - # refTags = pIndex.getReferences(tHandle, sTitle) - # if not novIdx: - # return - - # synopText = novIdx.synopsis - # if synopText: - # synopLabel = trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP]) - # synopText = f"

{synopLabel}: {synopText}

" - - # refLines = [] - # refLines = self._appendMetaTag(refTags, nwKeyWords.POV_KEY, refLines) - # refLines = self._appendMetaTag(refTags, nwKeyWords.FOCUS_KEY, refLines) - # refLines = self._appendMetaTag(refTags, nwKeyWords.CHAR_KEY, refLines) - # refLines = self._appendMetaTag(refTags, nwKeyWords.PLOT_KEY, refLines) - # refLines = self._appendMetaTag(refTags, nwKeyWords.TIME_KEY, refLines) - # refLines = self._appendMetaTag(refTags, nwKeyWords.WORLD_KEY, refLines) - # refLines = self._appendMetaTag(refTags, nwKeyWords.OBJECT_KEY, refLines) - # refLines = self._appendMetaTag(refTags, nwKeyWords.ENTITY_KEY, refLines) - # refLines = self._appendMetaTag(refTags, nwKeyWords.CUSTOM_KEY, refLines) - - # refText = "" - # if refLines: - # refList = "
".join(refLines) - # refText = f"

{refList}

" - - # ttText = refText + synopText or self.tr("No meta data") - # if ttText: - # QToolTip.showText(qPos, ttText) - - # return - - # @staticmethod - # def _appendMetaTag(refs: dict, key: str, lines: list[str]) -> list[str]: - # """Generate a reference list for a given reference key.""" - # tags = ", ".join(refs.get(key, [])) - # if tags: - # lines.append(f"{trConst(nwLabels.KEY_NAME[key])}: {tags}") - # return lines diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 877425ec..0d8575a2 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -517,7 +517,6 @@ class GuiProjectTree(QTreeView): def initSettings(self) -> None: """Set or update tree widget settings.""" - # Scroll bars if CONFIG.hideVScroll: self.setVerticalScrollBarPolicy(QtScrollAlwaysOff) else: