Add handlers for document label change

This commit is contained in:
Veronica Berglyd Olsen
2023-11-15 20:13:23 +01:00
parent 8508248f12
commit 6b9d92e75a
5 changed files with 72 additions and 48 deletions
+18 -18
View File
@@ -36,7 +36,7 @@ from typing import TYPE_CHECKING, ItemsView, Iterable, Iterator
from pathlib import Path from pathlib import Path
from novelwriter import SHARED from novelwriter import SHARED
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout, nwTrinary from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import checkInt, isHandle, isItemClass, isTitleTag, jsonEncode from novelwriter.common import checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
from novelwriter.constants import nwFiles, nwKeyWords, nwRegEx, nwUnicode, nwHeaders from novelwriter.constants import nwFiles, nwKeyWords, nwRegEx, nwUnicode, nwHeaders
@@ -255,7 +255,7 @@ class NWIndex:
return False return False
# 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), nwTrinary.NEGATIVE) itemTags = dict.fromkeys(self._itemIndex.allItemTags(tHandle), False)
self._itemIndex.add(tHandle, tItem) self._itemIndex.add(tHandle, tItem)
# Run word counter for the whole text # Run word counter for the whole text
@@ -290,8 +290,7 @@ class NWIndex:
# Internal Indexer Helpers # Internal Indexer Helpers
## ##
def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict[str, bool]) -> None:
tags: dict[str, nwTrinary]) -> None:
"""Scan an active document for meta data.""" """Scan an active document for meta data."""
nTitle = 0 # Line Number of the previous title nTitle = 0 # Line Number of the previous title
cTitle = TT_NONE # Tag of the current title cTitle = TT_NONE # Tag of the current title
@@ -347,20 +346,18 @@ class NWIndex:
self._indexWordCounts(tHandle, text, cTitle) self._indexWordCounts(tHandle, text, cTitle)
# Prune no longer used tags # Prune no longer used tags
for tTag, tStatus in tags.items(): for tTag, isActive in tags.items():
added = [] updated = []
deleted = [] deleted = []
if tStatus == nwTrinary.NEGATIVE: if isActive:
logger.debug("Added/updated tag '%s'", tTag)
updated.append(tTag)
else:
logger.debug("Removed tag '%s'", tTag) logger.debug("Removed tag '%s'", tTag)
del self._tagsIndex[tTag] del self._tagsIndex[tTag]
deleted.append(tTag) deleted.append(tTag)
elif tStatus == nwTrinary.POSITIVE: if updated or deleted:
logger.debug("Added new tag '%s'", tTag) SHARED.indexUpdatedTags(updated, deleted)
added.append(tTag)
else:
logger.debug("Unchanged tag '%s'", tTag)
if added or deleted:
SHARED.indexUpdatedTags(added, deleted)
return return
@@ -397,7 +394,7 @@ class NWIndex:
return return
def _indexKeyword(self, tHandle: str, line: str, sTitle: str, def _indexKeyword(self, tHandle: str, line: str, sTitle: str,
itemClass: nwItemClass, tags: dict[str, nwTrinary]) -> None: itemClass: nwItemClass, tags: dict[str, bool]) -> None:
"""Validate and save the information about a reference to a tag """Validate and save the information about a reference to a tag
in another file, or the setting of a tag in the file. A record in another file, or the setting of a tag in the file. A record
of active tags is updated so that no longer used tags can be of active tags is updated so that no longer used tags can be
@@ -414,10 +411,9 @@ class NWIndex:
if tBits[0] == nwKeyWords.TAG_KEY: if tBits[0] == nwKeyWords.TAG_KEY:
tagName = tBits[1] tagName = tBits[1]
tagKey = tagName.lower()
self._tagsIndex.add(tagName, tHandle, sTitle, itemClass) self._tagsIndex.add(tagName, tHandle, sTitle, itemClass)
self._itemIndex.setHeadingTag(tHandle, sTitle, tagName) self._itemIndex.setHeadingTag(tHandle, sTitle, tagName)
tags[tagKey] = nwTrinary.NEUTRAL if tagKey in tags else nwTrinary.POSITIVE tags[tagName.lower()] = True
else: else:
self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0]) self._itemIndex.addHeadingRef(tHandle, sTitle, tBits[1:], tBits[0])
@@ -634,7 +630,11 @@ class NWIndex:
sTitle = self._tagsIndex.tagHeading(tagKey) sTitle = self._tagsIndex.tagHeading(tagKey)
return tHandle, sTitle return tHandle, sTitle
def getTags(self, itemClass: nwItemClass) -> list[str]: def getDocumentTags(self, tHandle: str | None) -> list[str]:
"""Return all tags used by a specific document."""
return self._itemIndex.allItemTags(tHandle) if tHandle else []
def getClassTags(self, itemClass: nwItemClass) -> list[str]:
"""Return all tags based on itemClass.""" """Return all tags based on itemClass."""
return self._tagsIndex.filterTagNames(itemClass.name) return self._tagsIndex.filterTagNames(itemClass.name)
+1 -1
View File
@@ -2054,7 +2054,7 @@ class MetaCompleter(QMenu):
length = len(lookup) length = len(lookup)
suffix = "" suffix = ""
options = list(filter( options = list(filter(
lambda x: lookup in x.lower(), SHARED.project.index.getTags( lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
nwKeyWords.KEY_CLASS.get(kw.strip(), nwItemClass.NO_CLASS) nwKeyWords.KEY_CLASS.get(kw.strip(), nwItemClass.NO_CLASS)
) )
))[:15] ))[:15]
+50 -25
View File
@@ -102,8 +102,8 @@ class GuiDocViewerPanel(QWidget):
"""Run open project tasks.""" """Run open project tasks."""
self.clearClassTabs() self.clearClassTabs()
for key, name, tClass, iItem, hItem in SHARED.project.index.getTagsData(): for key, name, tClass, iItem, hItem in SHARED.project.index.getTagsData():
if tClass in self.kwTabs: if tClass in self.kwTabs and iItem and hItem:
self.kwTabs[tClass].addEntry(key, name, iItem, hItem) self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
self._updateTabVisibility() self._updateTabVisibility()
return return
@@ -123,6 +123,16 @@ class GuiDocViewerPanel(QWidget):
# Public Slots # Public Slots
## ##
@pyqtSlot(str)
def projectItemChanged(self, tHandle: str) -> None:
"""Update meta data for project item."""
self.tabBackRefs.refreshDocument(tHandle)
for key in SHARED.project.index.getDocumentTags(tHandle):
name, tClass, iItem, hItem = SHARED.project.index.getSingleTag(key)
if tClass in self.kwTabs and iItem and hItem:
self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
return
@pyqtSlot(str) @pyqtSlot(str)
def updateHandle(self, tHandle: str | None) -> None: def updateHandle(self, tHandle: str | None) -> None:
"""Update the document handle.""" """Update the document handle."""
@@ -131,22 +141,19 @@ class GuiDocViewerPanel(QWidget):
return return
@pyqtSlot(list, list) @pyqtSlot(list, list)
def updateChangedTags(self, added: list[str], deleted: list[str]) -> None: def updateChangedTags(self, updated: list[str], deleted: list[str]) -> None:
"""Forward tags changes to the lists.""" """Forward tags changes to the lists."""
for key in added: for key in updated:
name, tClass, iItem, hItem = SHARED.project.index.getSingleTag(key) name, tClass, iItem, hItem = SHARED.project.index.getSingleTag(key)
if tClass in self.kwTabs: if tClass in self.kwTabs and iItem and hItem:
self.kwTabs[tClass].addEntry(key, name, iItem, hItem) self.kwTabs[tClass].addUpdateEntry(key, name, iItem, hItem)
for key in deleted: for key in deleted:
for cTab in self.kwTabs.values(): for cTab in self.kwTabs.values():
if cTab.removeEntry(key): if cTab.removeEntry(key):
break break
else: else:
logger.warning("Could not remove tag '%s' from view panel", key) logger.warning("Could not remove tag '%s' from view panel", key)
self._updateTabVisibility() self._updateTabVisibility()
return return
## ##
@@ -180,9 +187,7 @@ class _ViewPanelBackRefs(QTreeWidget):
cMg = CONFIG.pxInt(6) cMg = CONFIG.pxInt(6)
# Content # Content
self.setHeaderLabels([ self.setHeaderLabels([self.tr("Heading"), "", "", self.tr("Document")])
self.tr("Heading"), "", "", self.tr("Document")
])
self.setIndentation(0) self.setIndentation(0)
self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.setIconSize(QSize(iPx, iPx)) self.setIconSize(QSize(iPx, iPx))
@@ -233,6 +238,21 @@ class _ViewPanelBackRefs(QTreeWidget):
self.addTopLevelItem(trItem) self.addTopLevelItem(trItem)
return return
def refreshDocument(self, tHandle: str) -> None:
"""Refresh document meta data."""
nwItem = SHARED.project.tree[tHandle]
if nwItem:
docIcon = SHARED.theme.getItemIcon(
nwItem.itemType, nwItem.itemClass,
nwItem.itemLayout, nwItem.mainHeading
)
for i in range(self.topLevelItemCount()):
trItem = self.topLevelItem(i)
if trItem and trItem.data(self.C_DATA, self.D_HANDLE) == tHandle:
trItem.setIcon(self.C_DOCUMENT, docIcon)
trItem.setText(self.C_DOCUMENT, nwItem.itemName)
return
# END Class _ViewPanelBackRefs # END Class _ViewPanelBackRefs
@@ -250,15 +270,13 @@ class _ViewPanelKeyWords(QTreeWidget):
def __init__(self, parent: QWidget, itemClass: nwItemClass) -> None: def __init__(self, parent: QWidget, itemClass: nwItemClass) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self._itemClass = nwItemClass
self._tagMap: dict[str, QTreeWidgetItem] = {} self._tagMap: dict[str, QTreeWidgetItem] = {}
iPx = SHARED.theme.baseIconSize iPx = SHARED.theme.baseIconSize
cMg = CONFIG.pxInt(6) cMg = CONFIG.pxInt(6)
self.setHeaderLabels([ self.setHeaderLabels([self.tr("Tag"), "", "", self.tr("Heading"), self.tr("Document")])
self.tr("Tag"), "", "", self.tr("Heading"), self.tr("Document")
])
self.setIndentation(0) self.setIndentation(0)
self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.setIconSize(QSize(iPx, iPx)) self.setIconSize(QSize(iPx, iPx))
@@ -266,6 +284,7 @@ class _ViewPanelKeyWords(QTreeWidget):
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.sortByColumn(self.C_NAME, Qt.SortOrder.AscendingOrder) self.sortByColumn(self.C_NAME, Qt.SortOrder.AscendingOrder)
# Set Header Sizes
treeHeader = self.header() treeHeader = self.header()
treeHeader.setStretchLastSection(True) treeHeader.setStretchLastSection(True)
treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.ResizeMode.ResizeToContents) treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.ResizeMode.ResizeToContents)
@@ -274,6 +293,7 @@ class _ViewPanelKeyWords(QTreeWidget):
treeHeader.resizeSection(self.C_EDIT, iPx + cMg) treeHeader.resizeSection(self.C_EDIT, iPx + cMg)
treeHeader.resizeSection(self.C_VIEW, iPx + cMg) treeHeader.resizeSection(self.C_VIEW, iPx + cMg)
# Cache Icons Locally
self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass]) self._classIcon = SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass])
self._editIcon = SHARED.theme.getIcon("edit") self._editIcon = SHARED.theme.getIcon("edit")
self._viewIcon = SHARED.theme.getIcon("view") self._viewIcon = SHARED.theme.getIcon("view")
@@ -281,14 +301,11 @@ class _ViewPanelKeyWords(QTreeWidget):
return return
def count(self) -> int: def count(self) -> int:
"""Return the number of items in the list."""
return self.topLevelItemCount() return self.topLevelItemCount()
def addEntry(self, tag: str, name: str, iItem: IndexItem | None, def addUpdateEntry(self, tag: str, name: str, iItem: IndexItem, hItem: IndexHeading) -> None:
hItem: IndexHeading | None) -> None: """Add a new entry, or update an existing one."""
"""Add a tag entry to the list."""
if not iItem or not hItem:
return
nwItem = iItem.item nwItem = iItem.item
docIcon = SHARED.theme.getItemIcon( docIcon = SHARED.theme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemType, nwItem.itemClass,
@@ -297,7 +314,13 @@ class _ViewPanelKeyWords(QTreeWidget):
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5 iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5
hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel) hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel)
trItem = QTreeWidgetItem() # This can not use a get call to the dictionary as that creates
# some weird issue with Qt, so we need to do this with an if
if tag in self._tagMap:
trItem = self._tagMap[tag]
else:
trItem = QTreeWidgetItem()
trItem.setText(self.C_NAME, name) trItem.setText(self.C_NAME, name)
trItem.setIcon(self.C_NAME, self._classIcon) trItem.setIcon(self.C_NAME, self._classIcon)
trItem.setIcon(self.C_EDIT, self._editIcon) trItem.setIcon(self.C_EDIT, self._editIcon)
@@ -308,8 +331,10 @@ class _ViewPanelKeyWords(QTreeWidget):
trItem.setText(self.C_DOCUMENT, nwItem.itemName) trItem.setText(self.C_DOCUMENT, nwItem.itemName)
trItem.setData(self.C_DATA, self.D_TAG, tag) trItem.setData(self.C_DATA, self.D_TAG, tag)
trItem.setData(self.C_DATA, self.D_HANDLE, iItem.handle) trItem.setData(self.C_DATA, self.D_HANDLE, iItem.handle)
self.addTopLevelItem(trItem)
self._tagMap[tag] = trItem if tag not in self._tagMap:
self.addTopLevelItem(trItem)
self._tagMap[tag] = trItem
return return
+1 -2
View File
@@ -259,6 +259,7 @@ class GuiMain(QMainWindow):
self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo) self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo)
self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo) self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox) self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
self.projView.treeItemChanged.connect(self.docViewerPanel.projectItemChanged)
self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem) self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
self.projView.rootFolderChanged.connect(self.novelView.updateRootItem) self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
self.projView.rootFolderChanged.connect(self.projView.updateRootItem) self.projView.rootFolderChanged.connect(self.projView.updateRootItem)
@@ -788,11 +789,9 @@ class GuiMain(QMainWindow):
if not SHARED.hasProject: if not SHARED.hasProject:
logger.error("No project open") logger.error("No project open")
return False return False
if tHandle is None and (self.docEditor.anyFocus() or self.isFocusMode): if tHandle is None and (self.docEditor.anyFocus() or self.isFocusMode):
tHandle = self.docEditor.docHandle tHandle = self.docEditor.docHandle
self.projView.renameTreeItem(tHandle) self.projView.renameTreeItem(tHandle)
return True return True
def rebuildTrees(self) -> None: def rebuildTrees(self) -> None:
+2 -2
View File
@@ -215,9 +215,9 @@ class SharedData(QObject):
# Call-Back Functions # Call-Back Functions
## ##
def indexUpdatedTags(self, added: list[str], deleted: list[str]) -> None: def indexUpdatedTags(self, updated: list[str], deleted: list[str]) -> None:
"""Emit the index changed tags signal.""" """Emit the index changed tags signal."""
self.indexChangedTags.emit(added, deleted) self.indexChangedTags.emit(updated, deleted)
return return
## ##