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
+24
View File
@@ -38,6 +38,7 @@ from novelwriter import SHARED
from novelwriter.common import isHandle, isItemClass, isTitleTag, jsonEncode
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
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.error import logException
from novelwriter.text.comments import processComment
@@ -89,6 +90,9 @@ class Index:
self._itemIndex = ItemIndex(project)
self._indexBroken = False
# Models
self._novelModels: dict[str, NovelModel] = {}
# TimeStamps
self._indexChange = 0.0
self._rootChange = {}
@@ -106,6 +110,26 @@ class Index:
def indexBroken(self) -> bool:
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
##
+5
View File
@@ -31,6 +31,7 @@ import logging
from collections.abc import ItemsView, Sequence
from typing import TYPE_CHECKING, Literal
from novelwriter import CONFIG
from novelwriter.common import checkInt, isListInstance, isTitleTag
from novelwriter.constants import nwKeyWords, nwStyles
@@ -237,6 +238,10 @@ class IndexHeading:
def title(self) -> str:
return self._title
@property
def mainCount(self) -> int:
return self._counts[0 if CONFIG.useCharCount else 1]
@property
def charCount(self) -> int:
return self._counts[0]
+1 -1
View File
@@ -331,7 +331,7 @@ class ProjectModel(QAbstractItemModel):
return 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):
node: ProjectNode = parent.internalPointer() if parent.isValid() else self._root
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