Allow refreshing a novel model node

This commit is contained in:
Veronica Berglyd Olsen
2025-03-22 22:37:03 +01:00
parent ef652355bf
commit 848027856d
2 changed files with 84 additions and 25 deletions
+18 -13
View File
@@ -178,9 +178,9 @@ class Index:
return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime) return self._rootChange.get(rootHandle, self._indexChange) > float(checkTime)
return False return False
def refreshNovelModel(self, tHandle: str) -> None: def refreshNovelModel(self, tHandle: str | None) -> None:
"""Refresh a novel model.""" """Refresh a novel model."""
if model := self.getNovelModel(tHandle): if tHandle and (model := self.getNovelModel(tHandle)):
logger.info("Refreshing novel model '%s'", tHandle) logger.info("Refreshing novel model '%s'", tHandle)
model.beginResetModel() model.beginResetModel()
model.clear() model.clear()
@@ -188,6 +188,19 @@ class Index:
model.endResetModel() model.endResetModel()
return return
def updateNovelModelData(self, nwItem: NWItem) -> bool:
"""Refresh a novel model."""
if (
(rHandle := nwItem.itemRoot)
and (model := self._novelModels.get(rHandle))
and (node := self._itemIndex[nwItem.itemHandle])
and node.item.isDocumentLayout()
and node.item.isActive
):
logger.info("Updating novel model data '%s'", nwItem.itemHandle)
return model.refresh(node)
return False
## ##
# Load and Save Index to/from File # Load and Save Index to/from File
## ##
@@ -285,7 +298,6 @@ class Index:
# Keep a record of existing tags, and create a new item entry # Keep a record of existing tags, and create a new item entry
itemTags = dict.fromkeys(self._itemIndex.allItemTags(tHandle), False) itemTags = dict.fromkeys(self._itemIndex.allItemTags(tHandle), False)
itemHeadings = self._itemIndex.headingCount(tHandle)
self._itemIndex.add(tHandle, tItem) self._itemIndex.add(tHandle, tItem)
# Run word counter for the whole text # Run word counter for the whole text
@@ -309,10 +321,9 @@ class Index:
else: else:
self._scanActive(tHandle, tItem, text, itemTags) self._scanActive(tHandle, tItem, text, itemTags)
if itemHeadings == self._itemIndex.headingCount(tHandle): if tItem.itemClass == nwItemClass.NOVEL and not blockSignal:
pass if not self.updateNovelModelData(tItem):
else: self.refreshNovelModel(tItem.itemRoot)
tItem.notifyNovelStructureChange()
# Update timestamps for index changes # Update timestamps for index changes
nowTime = time() nowTime = time()
@@ -915,12 +926,6 @@ class ItemIndex:
self._items[tHandle] = IndexNode(tHandle, nwItem) self._items[tHandle] = IndexNode(tHandle, nwItem)
return return
def headingCount(self, tHandle: str) -> int:
"""Return the number of headings in this item."""
if tHandle in self._items:
return len(self._items[tHandle])
return 0
def allItemTags(self, tHandle: str) -> list[str]: def allItemTags(self, tHandle: str) -> list[str]:
"""Get all tags set for headings of an item.""" """Get all tags set for headings of an item."""
if tHandle in self._items: if tHandle in self._items:
+66 -12
View File
@@ -30,7 +30,8 @@ from PyQt6.QtGui import QIcon, QPixmap
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.constants import nwStyles from novelwriter.constants import nwStyles
from novelwriter.core.indexdata import IndexNode from novelwriter.core.indexdata import IndexHeading, IndexNode
from novelwriter.error import logException
from novelwriter.types import QtAlignRight from novelwriter.types import QtAlignRight
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -121,15 +122,68 @@ class NovelModel(QAbstractTableModel):
handle = node.handle handle = node.handle
for key, head in node.items(): for key, head in node.items():
if key != "T0000": if key != "T0000":
iLevel = nwStyles.H_LEVEL.get(head.level, 0) self._rows.append(self._generateEntry(handle, key, head))
more = self._columns - 1
data = {}
data[C_FACTOR*0 | R_TEXT] = head.title
data[C_FACTOR*0 | R_ICON] = SHARED.theme.getHeaderDecoration(iLevel)
data[C_FACTOR*1 | R_TEXT] = f"{head.mainCount:n}"
data[C_FACTOR*1 | R_ALIGN] = QtAlignRight
data[C_FACTOR*more | R_ICON] = self._more
data[R_HANDLE] = handle
data[R_KEY] = key
self._rows.append(data)
return return
def refresh(self, node: IndexNode) -> bool:
"""Refresh an index node."""
handle = node.handle
current = []
for i, row in enumerate(self._rows):
if row.get(R_HANDLE) == handle:
current.append(i)
if current == []:
return False
cols = self._columns - 1
first = current[0]
last = current[-1]
remains = []
try:
for key, head in node.items():
if key != "T0000":
if current:
j = current.pop(0)
self._rows[j] = self._generateEntry(handle, key, head)
else:
remains.append((key, head))
self.dataChanged.emit(self.createIndex(first, 0), self.createIndex(last, cols))
if remains:
self.beginInsertRows(QModelIndex(), last, last + len(remains) - 1)
for k, (key, head) in enumerate(remains, last + 1):
self._rows.insert(k, self._generateEntry(handle, key, head))
self.endInsertRows()
elif current:
self.beginRemoveRows(QModelIndex(), current[0], current[-1])
del self._rows[current[0]:current[-1] + 1]
self.endRemoveRows()
except Exception:
# This is faster than to check for index boundaries.
# We definitely don't want to cause a crash.
logException()
return False
return True
##
# Internal Functions
##
def _generateEntry(self, handle: str, key: str, heading: IndexHeading) -> dict:
"""Generate a cache entry."""
iLevel = nwStyles.H_LEVEL.get(heading.level, 0)
more = self._columns - 1
data = {}
data[C_FACTOR*0 | R_TEXT] = heading.title
data[C_FACTOR*0 | R_ICON] = SHARED.theme.getHeaderDecoration(iLevel)
data[C_FACTOR*1 | R_TEXT] = f"{heading.mainCount:n}"
data[C_FACTOR*1 | R_ALIGN] = QtAlignRight
data[C_FACTOR*more | R_ICON] = self._more
data[R_HANDLE] = handle
data[R_KEY] = key
return data