diff --git a/novelwriter/assets/icons/typicons_dark/icons.conf b/novelwriter/assets/icons/typicons_dark/icons.conf index d4106803..f372c60c 100644 --- a/novelwriter/assets/icons/typicons_dark/icons.conf +++ b/novelwriter/assets/icons/typicons_dark/icons.conf @@ -88,3 +88,4 @@ deco_doc_h1 = nw_deco-h1.svg deco_doc_h2 = nw_deco-h2.svg deco_doc_h3 = nw_deco-h3.svg deco_doc_h4 = nw_deco-h4.svg +deco_doc_more = nw_deco-noveltree-more.svg diff --git a/novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg b/novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg new file mode 100644 index 00000000..f42d3306 --- /dev/null +++ b/novelwriter/assets/icons/typicons_dark/nw_deco-noveltree-more.svg @@ -0,0 +1,30 @@ + + diff --git a/novelwriter/assets/icons/typicons_light/icons.conf b/novelwriter/assets/icons/typicons_light/icons.conf index 8d267a47..4514eab9 100644 --- a/novelwriter/assets/icons/typicons_light/icons.conf +++ b/novelwriter/assets/icons/typicons_light/icons.conf @@ -88,3 +88,4 @@ deco_doc_h1 = nw_deco-h1.svg deco_doc_h2 = nw_deco-h2.svg deco_doc_h3 = nw_deco-h3.svg deco_doc_h4 = nw_deco-h4.svg +deco_doc_more = nw_deco-noveltree-more.svg diff --git a/novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg b/novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg new file mode 100644 index 00000000..f42d3306 --- /dev/null +++ b/novelwriter/assets/icons/typicons_light/nw_deco-noveltree-more.svg @@ -0,0 +1,30 @@ + + diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index 1c4dd1ee..711a71e3 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -35,11 +35,11 @@ from PyQt5.QtGui import QPalette from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal from PyQt5.QtWidgets import ( QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel, - QMenu, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, - QWidget + QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget, QTreeWidgetItem, + QVBoxLayout, QWidget ) -from novelwriter.enum import nwDocMode, nwItemClass +from novelwriter.enum import nwDocMode, nwItemClass, nwOutline from novelwriter.common import checkInt from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst @@ -322,7 +322,12 @@ class GuiNovelTree(QTreeWidget): C_TITLE = 0 C_WORDS = 1 - C_LAST = 2 + C_EXTRA = 2 + C_MORE = 3 + + D_HANDLE = Qt.UserRole + D_TITLE = Qt.UserRole + 1 + D_KEY = Qt.UserRole + 2 def __init__(self, novelView): QTreeWidget.__init__(self, novelView) @@ -357,7 +362,7 @@ class GuiNovelTree(QTreeWidget): self.setAllColumnsShowFocus(True) self.setHeaderHidden(True) self.setIndentation(0) - self.setColumnCount(3) + self.setColumnCount(4) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.SingleSelection) self.setExpandsOnDoubleClick(False) @@ -369,7 +374,8 @@ class GuiNovelTree(QTreeWidget): treeHeader.setMinimumSectionSize(iPx + cMg) treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.Stretch) treeHeader.setSectionResizeMode(self.C_WORDS, QHeaderView.ResizeToContents) - treeHeader.setSectionResizeMode(self.C_LAST, QHeaderView.ResizeToContents) + treeHeader.setSectionResizeMode(self.C_EXTRA, QHeaderView.ResizeToContents) + treeHeader.setSectionResizeMode(self.C_MORE, QHeaderView.ResizeToContents) # Pre-Generate Tree Formatting fH1 = self.font() @@ -387,8 +393,12 @@ class GuiNovelTree(QTreeWidget): self.mainTheme.loadDecoration("deco_doc_h3", pxH=iPx), self.mainTheme.loadDecoration("deco_doc_h4", pxH=iPx), ] + self._pMore = self.mainTheme.loadDecoration("deco_doc_more", pxH=iPx) + self._pActive = self.mainTheme.loadDecoration("deco_more_on", pxH=iPx) + self._pInactive = self.mainTheme.loadDecoration("deco_more_off", pxH=iPx) # Connect signals + self.clicked.connect(self._treeItemClicked) self.itemDoubleClicked.connect(self._treeDoubleClick) self.itemSelectionChanged.connect(self._treeSelectionChange) @@ -451,7 +461,7 @@ class GuiNovelTree(QTreeWidget): selItem = self.selectedItems() titleKey = None if selItem: - titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2] + titleKey = selItem[0].data(self.C_TITLE, self.D_KEY) self._populateTree(rootHandle) self.theProject.setLastNovelViewed(rootHandle) @@ -478,8 +488,9 @@ class GuiNovelTree(QTreeWidget): tHandle = None tLine = 0 if selItem: - tHandle = selItem[0].data(self.C_TITLE, Qt.UserRole)[0] - tLine = checkInt(selItem[0].data(self.C_TITLE, Qt.UserRole)[1], 1) - 1 + tHandle = selItem[0].data(self.C_TITLE, self.D_HANDLE) + sTitle = selItem[0].data(self.C_TITLE, self.D_TITLE) + tLine = checkInt(sTitle[1:], 1) - 1 return tHandle, tLine @@ -489,7 +500,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 == NovelTreeColumn.HIDDEN) + self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN) if doRefresh: self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True) return @@ -527,6 +538,17 @@ class GuiNovelTree(QTreeWidget): # Private Slots ## + @pyqtSlot("QModelIndex") + def _treeItemClicked(self, mIndex): + """The user clicked on an item in the tree. + """ + if mIndex.column() == self.C_MORE: + tHandle = mIndex.siblingAtColumn(self.C_TITLE).data(self.D_HANDLE) + sTitle = mIndex.siblingAtColumn(self.C_TITLE).data(self.D_TITLE) + tipPos = self.mapToGlobal(self.visualRect(mIndex).topRight()) + self._popMetaBox(tipPos, tHandle, sTitle) + return + @pyqtSlot() def _treeSelectionChange(self): """Extract the handle and line number of the currently selected @@ -566,19 +588,21 @@ class GuiNovelTree(QTreeWidget): continue newItem = QTreeWidgetItem() - theData = (tHandle, sTitle[1:].lstrip("0"), tKey) - newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel]) newItem.setText(self.C_TITLE, novIdx.title) - newItem.setData(self.C_TITLE, Qt.UserRole, theData) + newItem.setData(self.C_TITLE, self.D_HANDLE, tHandle) + newItem.setData(self.C_TITLE, self.D_TITLE, sTitle) + newItem.setData(self.C_TITLE, self.D_KEY, tKey) newItem.setFont(self.C_TITLE, self._hFonts[iLevel]) newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}") newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) lastText, toolTip = self._getLastColumnText(tHandle, sTitle) - newItem.setText(self.C_LAST, lastText) + newItem.setText(self.C_EXTRA, lastText) if lastText: - newItem.setToolTip(self.C_LAST, toolTip) + newItem.setToolTip(self.C_EXTRA, toolTip) + + newItem.setData(self.C_MORE, Qt.DecorationRole, self._pMore) self._treeMap[tKey] = newItem self.addTopLevelItem(newItem) @@ -609,4 +633,49 @@ class GuiNovelTree(QTreeWidget): return "", "" + def _popMetaBox(self, qPos, tHandle, sTitle): + """Show the novel meta data box. + """ + logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle) + + pIndex = self.theProject.index + novIdx = pIndex.getNovelData(tHandle, sTitle) + refTags = pIndex.getReferences(tHandle, sTitle) + + 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 = "{refList}
" + + ttText = refText + synopText or self.tr("No meta data") + if ttText: + QToolTip.showText(qPos, ttText) + + return + + @staticmethod + def _appendMetaTag(refs, key, lines): + """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 + # END Class GuiNovelTree diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 6c4ff50e..e72a8c97 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -474,7 +474,7 @@ class GuiIcons: "bullet-on", "bullet-off", # Decorations - "deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", + "deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", "deco_doc_more", } IMAGE_MAP = { diff --git a/tests/test_gui/test_gui_noveltree.py b/tests/test_gui/test_gui_noveltree.py index 102b03d2..53275b12 100644 --- a/tests/test_gui/test_gui_noveltree.py +++ b/tests/test_gui/test_gui_noveltree.py @@ -137,26 +137,26 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd): # =========== novelBar.setLastColType(NovelTreeColumn.HIDDEN) - assert novelTree.isColumnHidden(novelTree.C_LAST) is True + assert novelTree.isColumnHidden(novelTree.C_EXTRA) is True assert novelTree.lastColType == NovelTreeColumn.HIDDEN assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "") novelBar.setLastColType(NovelTreeColumn.POV) - assert novelTree.isColumnHidden(novelTree.C_LAST) is False + assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.POV assert novelTree._getLastColumnText("000000000000f", "T000001") == ( "Jane", "Point of View: Jane" ) novelBar.setLastColType(NovelTreeColumn.FOCUS) - assert novelTree.isColumnHidden(novelTree.C_LAST) is False + assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.FOCUS assert novelTree._getLastColumnText("000000000000f", "T000001") == ( "Jane", "Focus: Jane" ) novelBar.setLastColType(NovelTreeColumn.PLOT) - assert novelTree.isColumnHidden(novelTree.C_LAST) is False + assert novelTree.isColumnHidden(novelTree.C_EXTRA) is False assert novelTree.lastColType == NovelTreeColumn.PLOT assert novelTree._getLastColumnText("000000000000f", "T000001") == ( "", "Plot: "