Add a basic model for the novel view
This commit is contained in:
+30
-27
@@ -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"] = {
|
||||||
|
|||||||
@@ -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
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user