Add a basic model for the novel view

This commit is contained in:
Veronica Berglyd Olsen
2025-02-22 20:12:50 +01:00
parent bc26d2250e
commit 9a63a84fc1
7 changed files with 497 additions and 343 deletions
+30 -27
View File
@@ -68,23 +68,23 @@ class Config:
"_backupPath", "_backupPath",
"appName", "appHandle", "guiLocale", "guiTheme", "guiSyntax", "guiFont", "hideVScroll", "appName", "appHandle", "guiLocale", "guiTheme", "guiSyntax", "guiFont", "hideVScroll",
"hideHScroll", "lastNotes", "nativeFont", "iconTheme", "iconColTree", "iconColDocs", "hideHScroll", "lastNotes", "nativeFont", "useCharCount", "iconTheme", "iconColTree",
"mainWinSize", "welcomeWinSize", "prefsWinSize", "mainPanePos", "viewPanePos", "iconColDocs", "mainWinSize", "welcomeWinSize", "prefsWinSize", "mainPanePos",
"outlinePanePos", "autoSaveProj", "autoSaveDoc", "emphLabels", "backupOnClose", "viewPanePos", "outlinePanePos", "autoSaveProj", "autoSaveDoc", "emphLabels",
"askBeforeBackup", "askBeforeExit", "textFont", "textWidth", "textMargin", "tabWidth", "backupOnClose", "askBeforeBackup", "askBeforeExit", "textFont", "textWidth", "textMargin",
"cursorWidth", "focusWidth", "hideFocusFooter", "showFullPath", "autoSelect", "doJustify", "tabWidth", "cursorWidth", "focusWidth", "hideFocusFooter", "showFullPath", "autoSelect",
"showTabsNSpaces", "showLineEndings", "showMultiSpaces", "doReplace", "doReplaceSQuote", "doJustify", "showTabsNSpaces", "showLineEndings", "showMultiSpaces", "doReplace",
"doReplaceDQuote", "doReplaceDash", "doReplaceDots", "autoScroll", "autoScrollPos", "doReplaceSQuote", "doReplaceDQuote", "doReplaceDash", "doReplaceDots", "autoScroll",
"scrollPastEnd", "dialogStyle", "allowOpenDial", "dialogLine", "narratorBreak", "autoScrollPos", "scrollPastEnd", "dialogStyle", "allowOpenDial", "dialogLine",
"narratorDialog", "altDialogOpen", "altDialogClose", "highlightEmph", "stopWhenIdle", "narratorBreak", "narratorDialog", "altDialogOpen", "altDialogClose", "highlightEmph",
"userIdleTime", "incNotesWCount", "fmtApostrophe", "fmtSQuoteOpen", "fmtSQuoteClose", "stopWhenIdle", "userIdleTime", "incNotesWCount", "fmtApostrophe", "fmtSQuoteOpen",
"fmtDQuoteOpen", "fmtDQuoteClose", "fmtPadBefore", "fmtPadAfter", "fmtPadThin", "fmtSQuoteClose", "fmtDQuoteOpen", "fmtDQuoteClose", "fmtPadBefore", "fmtPadAfter",
"spellLanguage", "showViewerPanel", "showEditToolBar", "showSessionTime", "viewComments", "fmtPadThin", "spellLanguage", "showViewerPanel", "showEditToolBar", "showSessionTime",
"viewSynopsis", "searchCase", "searchWord", "searchRegEx", "searchLoop", "searchNextFile", "viewComments", "viewSynopsis", "searchCase", "searchWord", "searchRegEx", "searchLoop",
"searchMatchCap", "searchProjCase", "searchProjWord", "searchProjRegEx", "verQtString", "searchNextFile", "searchMatchCap", "searchProjCase", "searchProjWord", "searchProjRegEx",
"verQtValue", "verPyQtString", "verPyQtValue", "verPyString", "osType", "osLinux", "verQtString", "verQtValue", "verPyQtString", "verPyQtValue", "verPyString", "osType",
"osWindows", "osDarwin", "osUnknown", "hostName", "kernelVer", "isDebug", "memInfo", "osLinux", "osWindows", "osDarwin", "osUnknown", "hostName", "kernelVer", "isDebug",
"hasEnchant", "memInfo", "hasEnchant",
) )
LANG_NW = 1 LANG_NW = 1
@@ -157,6 +157,7 @@ class Config:
self.hideHScroll = False # Hide horizontal scroll bars on main widgets self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown self.lastNotes = "0x0" # The latest release notes that have been shown
self.nativeFont = True # Use native font dialog self.nativeFont = True # Use native font dialog
self.useCharCount = False # Use character count as primary count
# Icons # Icons
self.iconTheme = DEF_ICONS # Icons theme self.iconTheme = DEF_ICONS # Icons theme
@@ -591,16 +592,17 @@ class Config:
# Main # Main
sec = "Main" sec = "Main"
self.setGuiFont(conf.rdStr(sec, "font", "")) self.setGuiFont(conf.rdStr(sec, "font", ""))
self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme) self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax) self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
self.iconTheme = conf.rdStr(sec, "icons", self.iconTheme) self.iconTheme = conf.rdStr(sec, "icons", self.iconTheme)
self.iconColTree = conf.rdStr(sec, "iconcoltree", self.iconColTree) self.iconColTree = conf.rdStr(sec, "iconcoltree", self.iconColTree)
self.iconColDocs = conf.rdBool(sec, "iconcoldocs", self.iconColDocs) self.iconColDocs = conf.rdBool(sec, "iconcoldocs", self.iconColDocs)
self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale) self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale)
self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll) self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll)
self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll) self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll)
self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes) self.lastNotes = conf.rdStr(sec, "lastnotes", self.lastNotes)
self.nativeFont = conf.rdBool(sec, "nativefont", self.nativeFont) self.nativeFont = conf.rdBool(sec, "nativefont", self.nativeFont)
self.useCharCount = conf.rdBool(sec, "usecharcount", self.useCharCount)
# Sizes # Sizes
sec = "Sizes" sec = "Sizes"
@@ -717,6 +719,7 @@ class Config:
"hidehscroll": str(self.hideHScroll), "hidehscroll": str(self.hideHScroll),
"lastnotes": str(self.lastNotes), "lastnotes": str(self.lastNotes),
"nativefont": str(self.nativeFont), "nativefont": str(self.nativeFont),
"usecharcount": str(self.useCharCount),
} }
conf["Sizes"] = { conf["Sizes"] = {
+24
View File
@@ -38,6 +38,7 @@ from novelwriter import SHARED
from novelwriter.common import isHandle, isItemClass, isTitleTag, jsonEncode from novelwriter.common import isHandle, isItemClass, isTitleTag, jsonEncode
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
from novelwriter.core.indexdata import NOTE_TYPES, TT_NONE, IndexHeading, IndexNode, T_NoteTypes 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
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.text.comments import processComment from novelwriter.text.comments import processComment
@@ -89,6 +90,9 @@ class Index:
self._itemIndex = ItemIndex(project) self._itemIndex = ItemIndex(project)
self._indexBroken = False self._indexBroken = False
# Models
self._novelModels: dict[str, NovelModel] = {}
# TimeStamps # TimeStamps
self._indexChange = 0.0 self._indexChange = 0.0
self._rootChange = {} self._rootChange = {}
@@ -106,6 +110,26 @@ class Index:
def indexBroken(self) -> bool: def indexBroken(self) -> bool:
return self._indexBroken return self._indexBroken
##
# Getters
##
def getNovelModel(self, tHandle: str) -> NovelModel | None:
"""Get the model for a specific novel root."""
if tHandle not in self._novelModels:
self._generateNovelModel(tHandle)
return self._novelModels.get(tHandle)
def _generateNovelModel(self, tHandle: str) -> None:
"""Generate a novel model for a specific handle."""
if (item := SHARED.project.tree[tHandle]) and item.isRootType() and item.isNovelLike():
model = NovelModel(item)
for handle in SHARED.project.tree.subTree(tHandle):
if node := self._itemIndex[handle]:
model.append(node)
self._novelModels[tHandle] = model
return
## ##
# Public Methods # Public Methods
## ##
+5
View File
@@ -31,6 +31,7 @@ import logging
from collections.abc import ItemsView, Sequence from collections.abc import ItemsView, Sequence
from typing import TYPE_CHECKING, Literal from typing import TYPE_CHECKING, Literal
from novelwriter import CONFIG
from novelwriter.common import checkInt, isListInstance, isTitleTag from novelwriter.common import checkInt, isListInstance, isTitleTag
from novelwriter.constants import nwKeyWords, nwStyles from novelwriter.constants import nwKeyWords, nwStyles
@@ -237,6 +238,10 @@ class IndexHeading:
def title(self) -> str: def title(self) -> str:
return self._title return self._title
@property
def mainCount(self) -> int:
return self._counts[0 if CONFIG.useCharCount else 1]
@property @property
def charCount(self) -> int: def charCount(self) -> int:
return self._counts[0] return self._counts[0]
+1 -1
View File
@@ -331,7 +331,7 @@ class ProjectModel(QAbstractItemModel):
return QModelIndex() return QModelIndex()
def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex: def index(self, row: int, column: int, parent: QModelIndex = QModelIndex()) -> QModelIndex:
"""get the index of a child item of a parent.""" """Get the index of a child item of a parent."""
if self.hasIndex(row, column, parent): if self.hasIndex(row, column, parent):
node: ProjectNode = parent.internalPointer() if parent.isValid() else self._root node: ProjectNode = parent.internalPointer() if parent.isValid() else self._root
if child := node.child(row): if child := node.child(row):
+100
View File
@@ -0,0 +1,100 @@
"""
novelWriter Novel Model
=========================
File History:
Created: 2025-02-22 [2.7b1] NovelModel
This file is a part of novelWriter
Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations
import logging
from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt6.QtGui import QIcon, QPixmap
from novelwriter import SHARED
from novelwriter.constants import nwStyles
from novelwriter.core.indexdata import IndexNode
from novelwriter.core.item import NWItem
from novelwriter.types import QtAlignRight
logger = logging.getLogger(__name__)
C_FACTOR = 0x0100
C_TITLE_TEXT = 0x0000 | Qt.ItemDataRole.DisplayRole
C_TITLE_ICON = 0x0000 | Qt.ItemDataRole.DecorationRole
C_COUNT_TEXT = 0x0100 | Qt.ItemDataRole.DisplayRole
C_COUNT_ALIGN = 0x0100 | Qt.ItemDataRole.TextAlignmentRole
C_EXTRA_TEXT = 0x0200 | Qt.ItemDataRole.DisplayRole
C_EXTRA_TIP = 0x0200 | Qt.ItemDataRole.ToolTipRole
C_MORE_ICON = 0x0300 | Qt.ItemDataRole.DecorationRole
T_NodeData = str | QIcon | QPixmap | Qt.AlignmentFlag | None
class NovelModel(QAbstractTableModel):
def __init__(self, rootItem: NWItem) -> None:
super().__init__()
self._root = rootItem
self._rows: list[tuple[str, str, dict]] = []
self._more = SHARED.theme.getIcon("more_arrow")
return
def __del__(self) -> None: # pragma: no cover
logger.debug("Delete: NovelModel")
return
##
# Model Interface
##
def rowCount(self, index: QModelIndex) -> int:
"""Return the number of rows for an entry."""
return len(self._rows)
def columnCount(self, index: QModelIndex) -> int:
"""Return the number of columns for an entry."""
return 4
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][2].get(C_FACTOR*index.column() | role)
return None
##
# Data Methods
##
def append(self, node: IndexNode) -> None:
"""Append a node to the model."""
handle = node.handle
for key, head in node.items():
if key != "T0000":
iLevel = nwStyles.H_LEVEL.get(head.level, 0)
data = {}
data[C_TITLE_TEXT] = head.title
data[C_TITLE_ICON] = SHARED.theme.getHeaderDecoration(iLevel)
data[C_COUNT_TEXT] = f"{head.mainCount:n}"
data[C_COUNT_ALIGN] = QtAlignRight
data[C_MORE_ICON] = self._more
self._rows.append((handle, key, data))
return
+335 -314
View File
@@ -28,26 +28,22 @@ from __future__ import annotations
import logging import logging
from enum import Enum from enum import Enum
from time import time
from PyQt6.QtCore import QModelIndex, QPoint, Qt, pyqtSignal, pyqtSlot from PyQt6.QtCore import pyqtSignal, pyqtSlot
from PyQt6.QtGui import QActionGroup, QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent from PyQt6.QtGui import QActionGroup, QFont, QPalette
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView, QFrame, QHBoxLayout, QInputDialog, QMenu, QToolTip, QFrame, QHBoxLayout, QMenu, QTreeView, QTreeWidgetItem, QVBoxLayout,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import minmax, qtAddAction, qtAddMenu, qtLambda from novelwriter.common import qtAddAction, qtAddMenu, qtLambda
from novelwriter.constants import nwKeyWords, nwLabels, nwStyles, trConst from novelwriter.enum import nwChange, nwItemClass
from novelwriter.core.indexdata import IndexHeading
from novelwriter.enum import nwChange, nwDocMode, nwItemClass, nwOutline
from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.modified import NIconToolButton
from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
from novelwriter.types import ( from novelwriter.types import (
QtAlignRight, QtDecoration, QtHeaderStretch, QtHeaderToContents, QtHeaderStretch, QtHeaderToContents, QtScrollAlwaysOff, QtScrollAsNeeded,
QtMouseLeft, QtMouseMiddle, QtScrollAlwaysOff, QtScrollAsNeeded,
QtSizeExpanding, QtUserRole QtSizeExpanding, QtUserRole
) )
@@ -124,9 +120,9 @@ class GuiNovelView(QWidget):
lastCol = SHARED.project.options.getEnum( lastCol = SHARED.project.options.getEnum(
"GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.HIDDEN "GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.HIDDEN
) )
lastColSize = SHARED.project.options.getInt( # lastColSize = SHARED.project.options.getInt(
"GuiNovelView", "lastColSize", 25 # "GuiNovelView", "lastColSize", 25
) # )
self.clearNovelView() self.clearNovelView()
self.novelBar.buildNovelRootMenu() self.novelBar.buildNovelRootMenu()
@@ -134,18 +130,18 @@ class GuiNovelView(QWidget):
self.novelBar.setCurrentRoot(lastNovel) self.novelBar.setCurrentRoot(lastNovel)
self.novelBar.setEnabled(True) self.novelBar.setEnabled(True)
self.novelTree.setLastColSize(lastColSize) # self.novelTree.setLastColSize(lastColSize)
return return
def closeProjectTasks(self) -> None: def closeProjectTasks(self) -> None:
"""Run closing project tasks.""" """Run closing project tasks."""
lastColType = self.novelTree.lastColType # lastColType = self.novelTree.lastColType
lastColSize = self.novelTree.lastColSize # lastColSize = self.novelTree.lastColSize
logger.debug("Saving State: GuiNovelView") # logger.debug("Saving State: GuiNovelView")
pOptions = SHARED.project.options # pOptions = SHARED.project.options
pOptions.setValue("GuiNovelView", "lastCol", lastColType) # pOptions.setValue("GuiNovelView", "lastCol", lastColType)
pOptions.setValue("GuiNovelView", "lastColSize", lastColSize) # pOptions.setValue("GuiNovelView", "lastColSize", lastColSize)
self.clearNovelView() self.clearNovelView()
return return
@@ -162,16 +158,24 @@ class GuiNovelView(QWidget):
# Public Slots # Public Slots
## ##
@pyqtSlot(str)
def setCurrentNovel(self, rootHandle: str | None) -> None:
"""Set the current novel to display."""
if rootHandle and (model := SHARED.project.index.getNovelModel(rootHandle)):
self.novelTree.setModel(model)
self.novelTree.resizeColumsn()
return
@pyqtSlot(str) @pyqtSlot(str)
def setActiveHandle(self, tHandle: str) -> None: def setActiveHandle(self, tHandle: str) -> None:
"""Highlight the rows associated with a given handle.""" """Highlight the rows associated with a given handle."""
self.novelTree.setActiveHandle(tHandle) # self.novelTree.setActiveHandle(tHandle)
return return
@pyqtSlot() @pyqtSlot()
def refreshTree(self) -> None: def refreshTree(self) -> None:
"""Refresh the current tree.""" """Refresh the current tree."""
self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree")) # self.novelTree.refreshTree(rootHandle=SHARED.project.data.getLastHandle("novelTree"))
return return
@pyqtSlot(str, Enum) @pyqtSlot(str, Enum)
@@ -185,7 +189,7 @@ class GuiNovelView(QWidget):
"""The meta data of a novel item has changed, and the tree item """The meta data of a novel item has changed, and the tree item
needs to be refreshed. needs to be refreshed.
""" """
self.novelTree.refreshHandle(tHandle) # self.novelTree.refreshHandle(tHandle)
return return
@@ -301,13 +305,14 @@ class GuiNovelToolBar(QWidget):
def setCurrentRoot(self, rootHandle: str | None) -> None: def setCurrentRoot(self, rootHandle: str | None) -> None:
"""Set the current active root handle.""" """Set the current active root handle."""
self.novelValue.setHandle(rootHandle) self.novelValue.setHandle(rootHandle)
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True) SHARED.project.data.setLastHandle(rootHandle, "novelTree")
self.novelView.setCurrentNovel(rootHandle)
return return
def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None: def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None:
"""Set the last column type.""" """Set the last column type."""
self.aLastCol[colType].setChecked(True) # self.aLastCol[colType].setChecked(True)
self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh) # self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh)
return return
## ##
@@ -317,20 +322,19 @@ class GuiNovelToolBar(QWidget):
@pyqtSlot() @pyqtSlot()
def _refreshNovelTree(self) -> None: def _refreshNovelTree(self) -> None:
"""Rebuild the current tree.""" """Rebuild the current tree."""
rootHandle = SHARED.project.data.getLastHandle("novelTree") self.novelView.setCurrentNovel(SHARED.project.data.getLastHandle("novelTree"))
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return return
@pyqtSlot() @pyqtSlot()
def _selectLastColumnSize(self) -> None: def _selectLastColumnSize(self) -> None:
"""Set the maximum width for the last column.""" """Set the maximum width for the last column."""
oldSize = self.novelView.novelTree.lastColSize # oldSize = self.novelView.novelTree.lastColSize
newSize, isOk = QInputDialog.getInt( # newSize, isOk = QInputDialog.getInt(
self, self.tr("Column Size"), self.tr("Maximum column size in %"), oldSize, 15, 75, 5 # self, self.tr("Column Size"), self.tr("Maximum column size in %"), oldSize, 15, 75, 5
) # )
if isOk: # if isOk:
self.novelView.novelTree.setLastColSize(newSize) # self.novelView.novelTree.setLastColSize(newSize)
self._refreshNovelTree() # self._refreshNovelTree()
return return
## ##
@@ -347,7 +351,7 @@ class GuiNovelToolBar(QWidget):
return return
class GuiNovelTree(QTreeWidget): class GuiNovelTree(QTreeView):
C_DATA = 0 C_DATA = 0
C_TITLE = 0 C_TITLE = 0
@@ -375,55 +379,61 @@ class GuiNovelTree(QTreeWidget):
self._treeMap: dict[str, QTreeWidgetItem] = {} self._treeMap: dict[str, QTreeWidgetItem] = {}
# Cached Strings # Cached Strings
self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]) # self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]) # self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]) # self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
# Build GUI # Build GUI
# ========= # =========
iPx = SHARED.theme.baseIconHeight # iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
self.setIconSize(iSz) self.setIconSize(SHARED.theme.baseIconSize)
self.setFrameStyle(QFrame.Shape.NoFrame) self.setFrameStyle(QFrame.Shape.NoFrame)
self.setUniformRowHeights(True) self.setUniformRowHeights(True)
self.setAllColumnsShowFocus(True) self.setAllColumnsShowFocus(True)
self.setHeaderHidden(True) self.setHeaderHidden(True)
self.setIndentation(2) self.setIndentation(2)
self.setColumnCount(4)
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) # self.setIconSize(iSz)
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) # self.setFrameStyle(QFrame.Shape.NoFrame)
self.setExpandsOnDoubleClick(False) # self.setUniformRowHeights(True)
self.setDragEnabled(False) # self.setAllColumnsShowFocus(True)
# self.setHeaderHidden(True)
# self.setIndentation(2)
# self.setColumnCount(4)
# self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
# self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
# self.setExpandsOnDoubleClick(False)
# self.setDragEnabled(False)
# Lock the column sizes # Lock the column sizes
if header := self.header(): # if header := self.header():
header.setStretchLastSection(False) # header.setStretchLastSection(False)
header.setMinimumSectionSize(iPx + 6) # header.setMinimumSectionSize(iPx + 6)
header.setSectionResizeMode(self.C_TITLE, QtHeaderStretch) # header.setSectionResizeMode(self.C_TITLE, QtHeaderStretch)
header.setSectionResizeMode(self.C_WORDS, QtHeaderToContents) # header.setSectionResizeMode(self.C_WORDS, QtHeaderToContents)
header.setSectionResizeMode(self.C_EXTRA, QtHeaderToContents) # header.setSectionResizeMode(self.C_EXTRA, QtHeaderToContents)
header.setSectionResizeMode(self.C_MORE, QtHeaderToContents) # header.setSectionResizeMode(self.C_MORE, QtHeaderToContents)
# Pre-Generate Tree Formatting # Pre-Generate Tree Formatting
fH1 = self.font() # fH1 = self.font()
fH1.setBold(True) # fH1.setBold(True)
fH1.setUnderline(True) # fH1.setUnderline(True)
fH2 = self.font() # fH2 = self.font()
fH2.setBold(True) # fH2.setBold(True)
self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()] # self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()]
# Connect signals # Connect signals
self.clicked.connect(self._treeItemClicked) # 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)
# Set custom settings # Set custom settings
self.initSettings() self.initSettings()
self.updateTheme() # self.updateTheme()
logger.debug("Ready: GuiNovelTree") logger.debug("Ready: GuiNovelTree")
@@ -446,21 +456,21 @@ class GuiNovelTree(QTreeWidget):
def updateTheme(self) -> None: def updateTheme(self) -> None:
"""Update theme elements.""" """Update theme elements."""
iPx = SHARED.theme.baseIconHeight # iPx = SHARED.theme.baseIconHeight
self._pMore = SHARED.theme.getPixmap("more_arrow", (iPx, iPx)) # self._pMore = SHARED.theme.getPixmap("more_arrow", (iPx, iPx))
return return
## ##
# Properties # Properties
## ##
@property # @property
def lastColType(self) -> NovelTreeColumn: # def lastColType(self) -> NovelTreeColumn:
return self._lastCol # return self._lastCol
@property # @property
def lastColSize(self) -> int: # def lastColSize(self) -> int:
return int(self._lastColSize * 100) # return int(self._lastColSize * 100)
## ##
# Class Methods # Class Methods
@@ -468,302 +478,313 @@ class GuiNovelTree(QTreeWidget):
def clearContent(self) -> None: def clearContent(self) -> None:
"""Clear the GUI content and the related maps.""" """Clear the GUI content and the related maps."""
self.clear() # self.clear()
self._treeMap = {} # self._treeMap = {}
self._lastBuild = 0 # self._lastBuild = 0
return return
def refreshTree(self, rootHandle: str | None = None, overRide: bool = False) -> None: def resizeColumsn(self) -> None:
"""Refresh the tree if it has been changed.""" """Set the correct column sizes."""
logger.debug("Requesting refresh of the novel tree") if header := self.header():
if rootHandle is None: header.setStretchLastSection(False)
rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL) header.setMinimumSectionSize(SHARED.theme.baseIconHeight + 6)
header.setSectionResizeMode(self.C_TITLE, QtHeaderStretch)
titleKey = None header.setSectionResizeMode(self.C_WORDS, QtHeaderToContents)
if selItems := self.selectedItems(): header.setSectionResizeMode(self.C_EXTRA, QtHeaderToContents)
titleKey = selItems[0].data(self.C_DATA, self.D_KEY) header.setSectionResizeMode(self.C_MORE, QtHeaderToContents)
self._populateTree(rootHandle)
SHARED.project.data.setLastHandle(rootHandle, "novelTree")
if titleKey is not None and titleKey in self._treeMap:
self._treeMap[titleKey].setSelected(True)
return return
def refreshHandle(self, tHandle: str) -> None: # def refreshTree(self, rootHandle: str | None = None, overRide: bool = False) -> None:
"""Refresh the data for a given handle.""" # """Refresh the tree if it has been changed."""
if idxData := SHARED.project.index.getItemData(tHandle): # logger.debug("Requesting refresh of the novel tree")
logger.debug("Refreshing meta data for item '%s'", tHandle) # if rootHandle is None:
for sTitle, tHeading in idxData.items(): # rootHandle = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
sKey = f"{tHandle}:{sTitle}"
if trItem := self._treeMap.get(sKey, None): # titleKey = None
self._updateTreeItemValues(trItem, tHeading, tHandle, sTitle) # if selItems := self.selectedItems():
else: # titleKey = selItems[0].data(self.C_DATA, self.D_KEY)
logger.debug("Heading '%s' not in novel tree", sKey)
self.refreshTree() # self._populateTree(rootHandle)
return # SHARED.project.data.setLastHandle(rootHandle, "novelTree")
return
# if titleKey is not None and titleKey in self._treeMap:
# self._treeMap[titleKey].setSelected(True)
# return
# def refreshHandle(self, tHandle: str) -> None:
# """Refresh the data for a given handle."""
# if idxData := SHARED.project.index.getItemData(tHandle):
# logger.debug("Refreshing meta data for item '%s'", tHandle)
# for sTitle, tHeading in idxData.items():
# sKey = f"{tHandle}:{sTitle}"
# if trItem := self._treeMap.get(sKey, None):
# self._updateTreeItemValues(trItem, tHeading, tHandle, sTitle)
# else:
# logger.debug("Heading '%s' not in novel tree", sKey)
# self.refreshTree()
# return
# return
def getSelectedHandle(self) -> tuple[str | None, str | None]: def getSelectedHandle(self) -> tuple[str | None, str | None]:
"""Get the currently selected or active handle. If multiple """Get the currently selected or active handle. If multiple
items are selected, return the first. items are selected, return the first.
""" """
selList = self.selectedItems() # selList = self.selectedItems()
trItem = selList[0] if selList else self.currentItem() # trItem = selList[0] if selList else self.currentItem()
if isinstance(trItem, QTreeWidgetItem): # if isinstance(trItem, QTreeWidgetItem):
tHandle = trItem.data(self.C_DATA, self.D_HANDLE) # tHandle = trItem.data(self.C_DATA, self.D_HANDLE)
sTitle = trItem.data(self.C_DATA, self.D_TITLE) # sTitle = trItem.data(self.C_DATA, self.D_TITLE)
return tHandle, sTitle # return tHandle, sTitle
return None, None return None, None
def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None: # def setLastColType(self, colType: NovelTreeColumn, doRefresh: bool = True) -> None:
"""Change the content type of the last column and rebuild.""" # """Change the content type of the last column and rebuild."""
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_EXTRA, colType == NovelTreeColumn.HIDDEN) # self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
if doRefresh: # if doRefresh:
lastNovel = SHARED.project.data.getLastHandle("novelTree") # lastNovel = SHARED.project.data.getLastHandle("novelTree")
self.refreshTree(rootHandle=lastNovel, overRide=True) # self.refreshTree(rootHandle=lastNovel, overRide=True)
return # return
def setLastColSize(self, colSize: int) -> None: # def setLastColSize(self, colSize: int) -> None:
"""Set the column size in integer values between 15 and 75.""" # """Set the column size in integer values between 15 and 75."""
self._lastColSize = minmax(colSize, 15, 75)/100.0 # self._lastColSize = minmax(colSize, 15, 75)/100.0
return # return
def setActiveHandle(self, tHandle: str | None) -> None: # def setActiveHandle(self, tHandle: str | None) -> None:
"""Highlight the rows associated with a given handle.""" # """Highlight the rows associated with a given handle."""
didScroll = False # didScroll = False
brushOn = self.palette().alternateBase() # brushOn = self.palette().alternateBase()
brushOff = self.palette().base() # brushOff = self.palette().base()
if pHandle := self._actHandle: # if pHandle := self._actHandle:
for key, item in self._treeMap.items(): # for key, item in self._treeMap.items():
if key.startswith(pHandle): # if key.startswith(pHandle):
for i in range(self.columnCount()): # for i in range(self.columnCount()):
item.setBackground(i, brushOff) # item.setBackground(i, brushOff)
if tHandle: # if tHandle:
for key, item in self._treeMap.items(): # for key, item in self._treeMap.items():
if key.startswith(tHandle): # if key.startswith(tHandle):
for i in range(self.columnCount()): # for i in range(self.columnCount()):
item.setBackground(i, brushOn) # item.setBackground(i, brushOn)
if not didScroll: # if not didScroll:
self.scrollToItem(item, QAbstractItemView.ScrollHint.PositionAtCenter) # self.scrollToItem(item, QAbstractItemView.ScrollHint.PositionAtCenter)
didScroll = True # didScroll = True
self._actHandle = tHandle or None # self._actHandle = tHandle or None
return # return
## # ##
# Events # # Events
## # ##
def mousePressEvent(self, event: QMouseEvent) -> None: # def mousePressEvent(self, event: QMouseEvent) -> None:
"""Overload mousePressEvent to clear selection if clicking the # """Overload mousePressEvent to clear selection if clicking the
mouse in a blank area of the tree view, and to load a document # mouse in a blank area of the tree view, and to load a document
for viewing if the user middle-clicked. # for viewing if the user middle-clicked.
""" # """
super().mousePressEvent(event) # super().mousePressEvent(event)
if event.button() == QtMouseLeft: # if event.button() == QtMouseLeft:
selItem = self.indexAt(event.pos()) # selItem = self.indexAt(event.pos())
if not selItem.isValid(): # if not selItem.isValid():
self.clearSelection() # self.clearSelection()
elif event.button() == QtMouseMiddle: # elif event.button() == QtMouseMiddle:
selItem = self.itemAt(event.pos()) # selItem = self.itemAt(event.pos())
if not isinstance(selItem, QTreeWidgetItem): # if not isinstance(selItem, QTreeWidgetItem):
return # return
tHandle, sTitle = self.getSelectedHandle() # tHandle, sTitle = self.getSelectedHandle()
if tHandle is None: # if tHandle is None:
return # return
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle or "", False) # self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, sTitle or "", False)
return # return
def focusOutEvent(self, event: QFocusEvent) -> None: # def focusOutEvent(self, event: QFocusEvent) -> None:
"""Clear the selection when the tree no longer has focus.""" # """Clear the selection when the tree no longer has focus."""
super().focusOutEvent(event) # super().focusOutEvent(event)
self.clearSelection() # self.clearSelection()
return # return
def resizeEvent(self, event: QResizeEvent) -> None: # def resizeEvent(self, event: QResizeEvent) -> None:
"""Elide labels in the extra column.""" # """Elide labels in the extra column."""
super().resizeEvent(event) # super().resizeEvent(event)
newW = event.size().width() # newW = event.size().width()
oldW = event.oldSize().width() # oldW = event.oldSize().width()
if newW != oldW: # if newW != oldW:
eliW = int(self._lastColSize * newW) # eliW = int(self._lastColSize * newW)
fMetric = self.fontMetrics() # fMetric = self.fontMetrics()
for i in range(self.topLevelItemCount()): # for i in range(self.topLevelItemCount()):
trItem = self.topLevelItem(i) # trItem = self.topLevelItem(i)
if isinstance(trItem, QTreeWidgetItem): # if isinstance(trItem, QTreeWidgetItem):
lastText = trItem.data(self.C_DATA, self.D_EXTRA) # lastText = trItem.data(self.C_DATA, self.D_EXTRA)
trItem.setText( # trItem.setText(
self.C_EXTRA, # self.C_EXTRA,
fMetric.elidedText(lastText, Qt.TextElideMode.ElideRight, eliW) # fMetric.elidedText(lastText, Qt.TextElideMode.ElideRight, eliW)
) # )
return # return
## ##
# Private Slots # Private Slots
## ##
@pyqtSlot("QModelIndex") # @pyqtSlot("QModelIndex")
def _treeItemClicked(self, index: QModelIndex) -> None: # def _treeItemClicked(self, index: QModelIndex) -> None:
"""The user clicked on an item in the tree.""" # """The user clicked on an item in the tree."""
if index.column() == self.C_MORE: # if index.column() == self.C_MORE:
tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE) # tHandle = index.siblingAtColumn(self.C_DATA).data(self.D_HANDLE)
sTitle = index.siblingAtColumn(self.C_DATA).data(self.D_TITLE) # sTitle = index.siblingAtColumn(self.C_DATA).data(self.D_TITLE)
tipPos = self.mapToGlobal(self.visualRect(index).topRight()) # tipPos = self.mapToGlobal(self.visualRect(index).topRight())
self._popMetaBox(tipPos, tHandle, sTitle) # self._popMetaBox(tipPos, tHandle, sTitle)
return # return
@pyqtSlot() # @pyqtSlot()
def _treeSelectionChange(self) -> None: # def _treeSelectionChange(self) -> None:
"""Extract the handle and line number of the currently selected # """Extract the handle and line number of the currently selected
title, and send it to the tree meta panel. # title, and send it to the tree meta panel.
""" # """
tHandle, _ = self.getSelectedHandle() # tHandle, _ = self.getSelectedHandle()
if tHandle is not None: # if tHandle is not None:
self.novelView.selectedItemChanged.emit(tHandle) # self.novelView.selectedItemChanged.emit(tHandle)
return # return
@pyqtSlot("QTreeWidgetItem*", int) # @pyqtSlot("QTreeWidgetItem*", int)
def _treeDoubleClick(self, item: QTreeWidgetItem, column: int) -> None: # def _treeDoubleClick(self, item: QTreeWidgetItem, column: int) -> None:
"""Extract the handle and line number of the title double- # """Extract the handle and line number of the title double-
clicked, and send it to the main gui class for opening in the # clicked, and send it to the main gui class for opening in the
document editor. # document editor.
""" # """
tHandle, sTitle = self.getSelectedHandle() # tHandle, sTitle = self.getSelectedHandle()
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "", True) # self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "", True)
return # return
## ##
# Internal Functions # Internal Functions
## ##
def _populateTree(self, rootHandle: str | None) -> None: # def _populateTree(self, rootHandle: str | None) -> None:
"""Build the tree based on the project index.""" # """Build the tree based on the project index."""
self.clearContent() # self.clearContent()
tStart = time() # tStart = time()
logger.debug("Building novel tree for root item '%s'", rootHandle) # logger.debug("Building novel tree for root item '%s'", rootHandle)
novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, activeOnly=True) # novStruct = SHARED.project.index.novelStructure(rootHandle=rootHandle, activeOnly=True)
for tKey, tHandle, sTitle, novIdx in novStruct: # for tKey, tHandle, sTitle, novIdx in novStruct:
if novIdx.level == "H0": # if novIdx.level == "H0":
continue # continue
newItem = QTreeWidgetItem() # newItem = QTreeWidgetItem()
newItem.setData(self.C_DATA, self.D_HANDLE, tHandle) # newItem.setData(self.C_DATA, self.D_HANDLE, tHandle)
newItem.setData(self.C_DATA, self.D_TITLE, sTitle) # newItem.setData(self.C_DATA, self.D_TITLE, sTitle)
newItem.setData(self.C_DATA, self.D_KEY, tKey) # newItem.setData(self.C_DATA, self.D_KEY, tKey)
newItem.setTextAlignment(self.C_WORDS, QtAlignRight) # newItem.setTextAlignment(self.C_WORDS, QtAlignRight)
self._updateTreeItemValues(newItem, novIdx, tHandle, sTitle) # self._updateTreeItemValues(newItem, novIdx, tHandle, sTitle)
self._treeMap[tKey] = newItem # self._treeMap[tKey] = newItem
self.addTopLevelItem(newItem) # self.addTopLevelItem(newItem)
self.setActiveHandle(self._actHandle) # self.setActiveHandle(self._actHandle)
logger.debug("Novel Tree built in %.3f ms", (time() - tStart)*1000) # logger.debug("Novel Tree built in %.3f ms", (time() - tStart)*1000)
self._lastBuild = time() # self._lastBuild = time()
return # return
def _updateTreeItemValues( # def _updateTreeItemValues(
self, trItem: QTreeWidgetItem, idxItem: IndexHeading, tHandle: str, sTitle: str # self, trItem: QTreeWidgetItem, idxItem: IndexHeading, tHandle: str, sTitle: str
) -> None: # ) -> None:
"""Set the tree item values from the index entry.""" # """Set the tree item values from the index entry."""
iLevel = nwStyles.H_LEVEL.get(idxItem.level, 0) # iLevel = nwStyles.H_LEVEL.get(idxItem.level, 0)
hDec = SHARED.theme.getHeaderDecoration(iLevel) # hDec = SHARED.theme.getHeaderDecoration(iLevel)
trItem.setData(self.C_TITLE, QtDecoration, hDec) # trItem.setData(self.C_TITLE, QtDecoration, hDec)
trItem.setText(self.C_TITLE, idxItem.title) # trItem.setText(self.C_TITLE, idxItem.title)
trItem.setFont(self.C_TITLE, self._hFonts[iLevel]) # trItem.setFont(self.C_TITLE, self._hFonts[iLevel])
trItem.setText(self.C_WORDS, f"{idxItem.wordCount:n}") # trItem.setText(self.C_WORDS, f"{idxItem.wordCount:n}")
trItem.setData(self.C_MORE, QtDecoration, self._pMore) # trItem.setData(self.C_MORE, QtDecoration, self._pMore)
# Custom column # # Custom column
viewport = self.viewport() # viewport = self.viewport()
mW = int(self._lastColSize * (viewport.width() if viewport else 100)) # mW = int(self._lastColSize * (viewport.width() if viewport else 100))
lastText, toolTip = self._getLastColumnText(tHandle, sTitle) # lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
elideText = self.fontMetrics().elidedText(lastText, Qt.TextElideMode.ElideRight, mW) # elideText = self.fontMetrics().elidedText(lastText, Qt.TextElideMode.ElideRight, mW)
trItem.setText(self.C_EXTRA, elideText) # trItem.setText(self.C_EXTRA, elideText)
trItem.setData(self.C_DATA, self.D_EXTRA, lastText) # trItem.setData(self.C_DATA, self.D_EXTRA, lastText)
trItem.setToolTip(self.C_EXTRA, toolTip) # trItem.setToolTip(self.C_EXTRA, toolTip)
return # return
def _getLastColumnText(self, tHandle: str, sTitle: str) -> tuple[str, str]: # def _getLastColumnText(self, tHandle: str, sTitle: str) -> tuple[str, str]:
"""Generate text for the last column based on user settings.""" # """Generate text for the last column based on user settings."""
if self._lastCol == NovelTreeColumn.HIDDEN: # if self._lastCol == NovelTreeColumn.HIDDEN:
return "", "" # return "", ""
refData = [] # refData = []
refName = "" # refName = ""
refs = SHARED.project.index.getReferences(tHandle, sTitle) # refs = SHARED.project.index.getReferences(tHandle, sTitle)
if self._lastCol == NovelTreeColumn.POV: # if self._lastCol == NovelTreeColumn.POV:
refData = refs[nwKeyWords.POV_KEY] # refData = refs[nwKeyWords.POV_KEY]
refName = self._povLabel # refName = self._povLabel
elif self._lastCol == NovelTreeColumn.FOCUS: # elif self._lastCol == NovelTreeColumn.FOCUS:
refData = refs[nwKeyWords.FOCUS_KEY] # refData = refs[nwKeyWords.FOCUS_KEY]
refName = self._focLabel # refName = self._focLabel
elif self._lastCol == NovelTreeColumn.PLOT: # elif self._lastCol == NovelTreeColumn.PLOT:
refData = refs[nwKeyWords.PLOT_KEY] # refData = refs[nwKeyWords.PLOT_KEY]
refName = self._pltLabel # refName = self._pltLabel
if refData: # if refData:
toolText = ", ".join(refData) # toolText = ", ".join(refData)
return toolText, f"{refName}: {toolText}" # return toolText, f"{refName}: {toolText}"
return "", "" # return "", ""
def _popMetaBox(self, qPos: QPoint, tHandle: str, sTitle: str) -> None: # def _popMetaBox(self, qPos: QPoint, tHandle: str, sTitle: str) -> None:
"""Show the novel meta data box.""" # """Show the novel meta data box."""
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle) # logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
pIndex = SHARED.project.index # pIndex = SHARED.project.index
novIdx = pIndex.getItemHeading(tHandle, sTitle) # novIdx = pIndex.getItemHeading(tHandle, sTitle)
refTags = pIndex.getReferences(tHandle, sTitle) # refTags = pIndex.getReferences(tHandle, sTitle)
if not novIdx: # if not novIdx:
return # return
synopText = novIdx.synopsis # synopText = novIdx.synopsis
if synopText: # if synopText:
synopLabel = trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP]) # synopLabel = trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP])
synopText = f"<p><b>{synopLabel}</b>: {synopText}</p>" # synopText = f"<p><b>{synopLabel}</b>: {synopText}</p>"
refLines = [] # refLines = []
refLines = self._appendMetaTag(refTags, nwKeyWords.POV_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.POV_KEY, refLines)
refLines = self._appendMetaTag(refTags, nwKeyWords.FOCUS_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.FOCUS_KEY, refLines)
refLines = self._appendMetaTag(refTags, nwKeyWords.CHAR_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.CHAR_KEY, refLines)
refLines = self._appendMetaTag(refTags, nwKeyWords.PLOT_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.PLOT_KEY, refLines)
refLines = self._appendMetaTag(refTags, nwKeyWords.TIME_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.TIME_KEY, refLines)
refLines = self._appendMetaTag(refTags, nwKeyWords.WORLD_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.WORLD_KEY, refLines)
refLines = self._appendMetaTag(refTags, nwKeyWords.OBJECT_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.OBJECT_KEY, refLines)
refLines = self._appendMetaTag(refTags, nwKeyWords.ENTITY_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.ENTITY_KEY, refLines)
refLines = self._appendMetaTag(refTags, nwKeyWords.CUSTOM_KEY, refLines) # refLines = self._appendMetaTag(refTags, nwKeyWords.CUSTOM_KEY, refLines)
refText = "" # refText = ""
if refLines: # if refLines:
refList = "<br>".join(refLines) # refList = "<br>".join(refLines)
refText = f"<p>{refList}</p>" # refText = f"<p>{refList}</p>"
ttText = refText + synopText or self.tr("No meta data") # ttText = refText + synopText or self.tr("No meta data")
if ttText: # if ttText:
QToolTip.showText(qPos, ttText) # QToolTip.showText(qPos, ttText)
return # return
@staticmethod # @staticmethod
def _appendMetaTag(refs: dict, key: str, lines: list[str]) -> list[str]: # def _appendMetaTag(refs: dict, key: str, lines: list[str]) -> list[str]:
"""Generate a reference list for a given reference key.""" # """Generate a reference list for a given reference key."""
tags = ", ".join(refs.get(key, [])) # tags = ", ".join(refs.get(key, []))
if tags: # if tags:
lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}</b>: {tags}") # lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}</b>: {tags}")
return lines # return lines
+2 -1
View File
@@ -1,5 +1,5 @@
[Meta] [Meta]
timestamp = 2025-02-06 11:05:16 timestamp = 2025-02-22 19:57:47
[Main] [Main]
font = font =
@@ -13,6 +13,7 @@ hidevscroll = False
hidehscroll = False hidehscroll = False
lastnotes = 0x0 lastnotes = 0x0
nativefont = True nativefont = True
usecharcount = False
[Sizes] [Sizes]
mainwindow = 1200, 650 mainwindow = 1200, 650