Add a tool tip with meta data behind a button on each novel tree item

This commit is contained in:
Veronica Berglyd Olsen
2022-06-15 19:17:24 +02:00
parent 50f299171a
commit ba0ce33cfc
7 changed files with 151 additions and 20 deletions
@@ -88,3 +88,4 @@ deco_doc_h1 = nw_deco-h1.svg
deco_doc_h2 = nw_deco-h2.svg deco_doc_h2 = nw_deco-h2.svg
deco_doc_h3 = nw_deco-h3.svg deco_doc_h3 = nw_deco-h3.svg
deco_doc_h4 = nw_deco-h4.svg deco_doc_h4 = nw_deco-h4.svg
deco_doc_more = nw_deco-noveltree-more.svg
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<path
d="M 9.7,18.2 16,12 9.7,5.8 C 9.5,5.6 9.2,5.5 9,5.5 8.8,5.5 8.5,5.6 8.3,5.8 8.1,6 8,6.2 8,6.5 v 11 c 0,0.3 0.1,0.5 0.3,0.7 0.2,0.2 0.5,0.3 0.7,0.3 0.2,0 0.5,-0.1 0.7,-0.3 z"
id="path3096"
style="fill:#848484;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 928 B

@@ -88,3 +88,4 @@ deco_doc_h1 = nw_deco-h1.svg
deco_doc_h2 = nw_deco-h2.svg deco_doc_h2 = nw_deco-h2.svg
deco_doc_h3 = nw_deco-h3.svg deco_doc_h3 = nw_deco-h3.svg
deco_doc_h4 = nw_deco-h4.svg deco_doc_h4 = nw_deco-h4.svg
deco_doc_more = nw_deco-noveltree-more.svg
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.2"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg2161"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<metadata
id="metadata2167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs2165" />
<path
d="M 9.7,18.2 16,12 9.7,5.8 C 9.5,5.6 9.2,5.5 9,5.5 8.8,5.5 8.5,5.6 8.3,5.8 8.1,6 8,6.2 8,6.5 v 11 c 0,0.3 0.1,0.5 0.3,0.7 0.2,0.2 0.5,0.3 0.7,0.3 0.2,0 0.5,-0.1 0.7,-0.3 z"
id="path3096"
style="fill:#848484;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 928 B

+84 -15
View File
@@ -35,11 +35,11 @@ from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel, QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel,
QMenu, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget, QTreeWidgetItem,
QWidget QVBoxLayout, QWidget
) )
from novelwriter.enum import nwDocMode, nwItemClass from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
from novelwriter.common import checkInt from novelwriter.common import checkInt
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
@@ -322,7 +322,12 @@ class GuiNovelTree(QTreeWidget):
C_TITLE = 0 C_TITLE = 0
C_WORDS = 1 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): def __init__(self, novelView):
QTreeWidget.__init__(self, novelView) QTreeWidget.__init__(self, novelView)
@@ -357,7 +362,7 @@ class GuiNovelTree(QTreeWidget):
self.setAllColumnsShowFocus(True) self.setAllColumnsShowFocus(True)
self.setHeaderHidden(True) self.setHeaderHidden(True)
self.setIndentation(0) self.setIndentation(0)
self.setColumnCount(3) self.setColumnCount(4)
self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection) self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setExpandsOnDoubleClick(False) self.setExpandsOnDoubleClick(False)
@@ -369,7 +374,8 @@ class GuiNovelTree(QTreeWidget):
treeHeader.setMinimumSectionSize(iPx + cMg) treeHeader.setMinimumSectionSize(iPx + cMg)
treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.Stretch) treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.Stretch)
treeHeader.setSectionResizeMode(self.C_WORDS, QHeaderView.ResizeToContents) 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 # Pre-Generate Tree Formatting
fH1 = self.font() fH1 = self.font()
@@ -387,8 +393,12 @@ class GuiNovelTree(QTreeWidget):
self.mainTheme.loadDecoration("deco_doc_h3", pxH=iPx), self.mainTheme.loadDecoration("deco_doc_h3", pxH=iPx),
self.mainTheme.loadDecoration("deco_doc_h4", 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 # Connect signals
self.clicked.connect(self._treeItemClicked)
self.itemDoubleClicked.connect(self._treeDoubleClick) self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._treeSelectionChange) self.itemSelectionChanged.connect(self._treeSelectionChange)
@@ -451,7 +461,7 @@ class GuiNovelTree(QTreeWidget):
selItem = self.selectedItems() selItem = self.selectedItems()
titleKey = None titleKey = None
if selItem: 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._populateTree(rootHandle)
self.theProject.setLastNovelViewed(rootHandle) self.theProject.setLastNovelViewed(rootHandle)
@@ -478,8 +488,9 @@ class GuiNovelTree(QTreeWidget):
tHandle = None tHandle = None
tLine = 0 tLine = 0
if selItem: if selItem:
tHandle = selItem[0].data(self.C_TITLE, Qt.UserRole)[0] tHandle = selItem[0].data(self.C_TITLE, self.D_HANDLE)
tLine = checkInt(selItem[0].data(self.C_TITLE, Qt.UserRole)[1], 1) - 1 sTitle = selItem[0].data(self.C_TITLE, self.D_TITLE)
tLine = checkInt(sTitle[1:], 1) - 1
return tHandle, tLine return tHandle, tLine
@@ -489,7 +500,7 @@ class GuiNovelTree(QTreeWidget):
if self._lastCol != colType: if self._lastCol != colType:
logger.debug("Changing last column to %s", colType.name) logger.debug("Changing last column to %s", colType.name)
self._lastCol = colType self._lastCol = colType
self.setColumnHidden(self.C_LAST, colType == NovelTreeColumn.HIDDEN) self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
if doRefresh: if doRefresh:
self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True) self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True)
return return
@@ -527,6 +538,17 @@ class GuiNovelTree(QTreeWidget):
# Private Slots # 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() @pyqtSlot()
def _treeSelectionChange(self): def _treeSelectionChange(self):
"""Extract the handle and line number of the currently selected """Extract the handle and line number of the currently selected
@@ -566,19 +588,21 @@ class GuiNovelTree(QTreeWidget):
continue continue
newItem = QTreeWidgetItem() newItem = QTreeWidgetItem()
theData = (tHandle, sTitle[1:].lstrip("0"), tKey)
newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel]) newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel])
newItem.setText(self.C_TITLE, novIdx.title) 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.setFont(self.C_TITLE, self._hFonts[iLevel])
newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}") newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}")
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
lastText, toolTip = self._getLastColumnText(tHandle, sTitle) lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
newItem.setText(self.C_LAST, lastText) newItem.setText(self.C_EXTRA, lastText)
if 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._treeMap[tKey] = newItem
self.addTopLevelItem(newItem) self.addTopLevelItem(newItem)
@@ -609,4 +633,49 @@ class GuiNovelTree(QTreeWidget):
return "", "" 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"<p><b>{synopLabel}</b>: {synopText}</p>"
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 = "<br>".join(refLines)
refText = f"<p>{refList}</p>"
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"<b>{trConst(nwLabels.KEY_NAME[key])}</b>: {tags}")
return lines
# END Class GuiNovelTree # END Class GuiNovelTree
+1 -1
View File
@@ -474,7 +474,7 @@ class GuiIcons:
"bullet-on", "bullet-off", "bullet-on", "bullet-off",
# Decorations # 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 = { IMAGE_MAP = {
+4 -4
View File
@@ -137,26 +137,26 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# =========== # ===========
novelBar.setLastColType(NovelTreeColumn.HIDDEN) 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.lastColType == NovelTreeColumn.HIDDEN
assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "") assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "")
novelBar.setLastColType(NovelTreeColumn.POV) 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.lastColType == NovelTreeColumn.POV
assert novelTree._getLastColumnText("000000000000f", "T000001") == ( assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"Jane", "Point of View: Jane" "Jane", "Point of View: Jane"
) )
novelBar.setLastColType(NovelTreeColumn.FOCUS) 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.lastColType == NovelTreeColumn.FOCUS
assert novelTree._getLastColumnText("000000000000f", "T000001") == ( assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"Jane", "Focus: Jane" "Jane", "Focus: Jane"
) )
novelBar.setLastColType(NovelTreeColumn.PLOT) 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.lastColType == NovelTreeColumn.PLOT
assert novelTree._getLastColumnText("000000000000f", "T000001") == ( assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"", "Plot: " "", "Plot: "