Annotate and clean up project tree source

This commit is contained in:
Veronica Berglyd Olsen
2023-07-29 20:45:12 +02:00
parent db892f633a
commit aa64fdbfc3
2 changed files with 106 additions and 150 deletions
+14 -18
View File
@@ -76,7 +76,7 @@ class NWIndex:
a rebuild of the index data.
"""
def __init__(self, project: NWProject):
def __init__(self, project: NWProject) -> None:
self._project = project
@@ -91,7 +91,7 @@ class NWIndex:
return
def __repr__(self):
def __repr__(self) -> str:
return f"<NWIndex project='{self._project.data.name}'>"
##
@@ -99,14 +99,14 @@ class NWIndex:
##
@property
def indexBroken(self):
def indexBroken(self) -> bool:
return self._indexBroken
##
# Public Methods
##
def clearIndex(self):
def clearIndex(self) -> None:
"""Clear the index dictionaries and time stamps."""
self._tagsIndex.clear()
self._itemIndex.clear()
@@ -114,7 +114,7 @@ class NWIndex:
self._rootChange = {}
return
def rebuildIndex(self):
def rebuildIndex(self) -> None:
"""Rebuild the entire index from scratch."""
self.clearIndex()
for nwItem in self._project.tree:
@@ -125,22 +125,20 @@ class NWIndex:
self._indexBroken = False
return
def deleteHandle(self, tHandle: str):
def deleteHandle(self, tHandle: str) -> None:
"""Delete all entries of a given document handle."""
logger.debug("Removing item '%s' from the index", tHandle)
for tTag in self._itemIndex.allItemTags(tHandle):
del self._tagsIndex[tTag]
del self._itemIndex[tHandle]
return
def reIndexHandle(self, tHandle: str) -> bool:
def reIndexHandle(self, tHandle: str | None) -> bool:
"""Put a file back into the index. This is used when files are
moved from the archive or trash folders back into the active
project.
"""
if not self._project.tree.checkType(tHandle, nwItemType.FILE):
if tHandle is None or not self._project.tree.checkType(tHandle, nwItemType.FILE):
return False
logger.debug("Re-indexing item '%s'", tHandle)
@@ -163,9 +161,8 @@ class NWIndex:
# Load and Save Index to/from File
##
def loadIndex(self):
"""Load index from last session from the project meta folder.
"""
def loadIndex(self) -> bool:
"""Load index from last session from the project meta folder."""
indexFile = self._project.storage.getMetaFile(nwFiles.INDEX_FILE)
if not isinstance(indexFile, Path):
return False
@@ -292,7 +289,7 @@ class NWIndex:
# Internal Indexer Helpers
##
def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict):
def _scanActive(self, tHandle: str, nwItem: NWItem, text: str, tags: dict) -> None:
"""Scan an active document for meta data."""
nTitle = 0 # Line Number of the previous title
cTitle = TT_NONE # Tag of the current title
@@ -355,7 +352,7 @@ class NWIndex:
return
def _scanInactive(self, nwItem: NWItem, text: str):
def _scanInactive(self, nwItem: NWItem, text: str) -> None:
"""Scan an inactive document for meta data."""
for aLine in text.splitlines():
if aLine.startswith("#"):
@@ -387,9 +384,8 @@ class NWIndex:
self._itemIndex.setHeadingCounts(tHandle, sTitle, cC, wC, pC)
return
def _indexKeyword(
self, tHandle: str, line: str, sTitle: str, itemClass: nwItemClass, tags: dict
):
def _indexKeyword(self, tHandle: str, line: str, sTitle: str,
itemClass: nwItemClass, tags: dict) -> None:
"""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
of active tags is updated so that no longer used tags can be
+92 -132
View File
@@ -32,8 +32,8 @@ from enum import Enum
from time import time
from typing import TYPE_CHECKING
from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDropEvent, QMouseEvent, QPalette
from PyQt5.QtCore import QPoint, Qt, QSize, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView, QLabel,
QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem,
@@ -74,7 +74,7 @@ class GuiProjectView(QWidget):
# Requests for the main GUI
projectSettingsRequest = pyqtSignal(int)
def __init__(self, mainGui: GuiMain):
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
self.mainGui = mainGui
@@ -132,83 +132,71 @@ class GuiProjectView(QWidget):
# Methods
##
def updateTheme(self):
"""Update theme elements.
"""
def updateTheme(self) -> None:
"""Update theme elements."""
self.projBar.updateTheme()
self.populateTree()
return
def initSettings(self):
"""Initialise GUI elements that depend on specific settings.
"""
def initSettings(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
self.projTree.initSettings()
return
def clearProject(self):
"""Clear project-related GUI content.
"""
def clearProject(self) -> None:
"""Clear project-related GUI content."""
self.projBar.clearContent()
self.projBar.setEnabled(False)
self.projTree.clearTree()
return
def openProjectTasks(self):
"""Run open project tasks.
"""
def openProjectTasks(self) -> None:
"""Run open project tasks."""
self.projBar.buildQuickLinkMenu()
self.projBar.setEnabled(True)
return
def saveProjectTasks(self):
"""Run save project tasks.
"""
def saveProjectTasks(self) -> None:
"""Run save project tasks."""
self.projTree.saveTreeOrder()
return
def populateTree(self):
"""Build the tree structure from project data.
"""
def populateTree(self) -> None:
"""Build the tree structure from project data."""
self.projTree.buildTree()
return
def setFocus(self):
"""Forward the set focus call to the tree widget.
"""
def setFocus(self) -> None:
"""Forward the set focus call to the tree widget."""
self.projTree.setFocus()
return
def treeHasFocus(self):
"""Check if the project tree has focus.
"""
def treeHasFocus(self) -> bool:
"""Check if the project tree has focus."""
return self.projTree.hasFocus()
def renameTreeItem(self, tHandle=None):
def renameTreeItem(self, tHandle: str | None = None) -> bool:
"""External request to rename an item or the currently selected
item. This is triggered by the global menu or keyboard shortcut.
"""
if tHandle is None:
tHandle = self.projTree.getSelectedHandle()
if tHandle:
return self.projTree.renameTreeItem(tHandle)
return
return self.projTree.renameTreeItem(tHandle) if tHandle else False
##
# Public Slots
##
@pyqtSlot(str, int, int, int)
def updateCounts(self, tHandle, cCount, wCount, pCount):
"""Slot for updating the word count of a specific item.
"""
def updateCounts(self, tHandle: str, cCount: int, wCount: int, pCount: int) -> None:
"""Slot for updating the word count of a specific item."""
self.projTree.propagateCount(tHandle, wCount, countChildren=True)
self.wordCountsChanged.emit()
return
@pyqtSlot(str)
def updateRootItem(self, tHandle):
"""If any root item changes, rebuild the quick link root menu.
"""
def updateRootItem(self, tHandle: str) -> None:
"""If any root item changes, rebuild the quick link menu."""
self.projBar.buildQuickLinkMenu()
return
@@ -217,7 +205,7 @@ class GuiProjectView(QWidget):
class GuiProjectToolBar(QWidget):
def __init__(self, projView):
def __init__(self, projView: GuiProjectView) -> None:
super().__init__(parent=projView)
logger.debug("Create: GuiProjectToolBar")
@@ -237,7 +225,7 @@ class GuiProjectToolBar(QWidget):
# Widget Label
self.viewLabel = QLabel("<b>%s</b>" % self.tr("Project Content"))
self.viewLabel.setContentsMargins(0, 0, 0, 0)
self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
# Quick Links
self.mQuick = QMenu()
@@ -341,9 +329,8 @@ class GuiProjectToolBar(QWidget):
# Methods
##
def updateTheme(self):
"""Update theme elements.
"""
def updateTheme(self) -> None:
"""Update theme elements."""
qPalette = self.palette()
qPalette.setBrush(QPalette.Window, qPalette.base())
self.setPalette(qPalette)
@@ -376,17 +363,14 @@ class GuiProjectToolBar(QWidget):
return
def clearContent(self):
"""Clear dynamic content on the tool bar.
"""
def clearContent(self) -> None:
"""Clear dynamic content on the tool bar."""
self.mQuick.clear()
return
def buildQuickLinkMenu(self):
"""Build the quick link menu.
"""
def buildQuickLinkMenu(self) -> None:
"""Build the quick link menu."""
logger.debug("Rebuilding quick links menu")
self.mQuick.clear()
for n, (tHandle, nwItem) in enumerate(self.theProject.tree.iterRoots(None)):
aRoot = self.mQuick.addAction(nwItem.itemName)
@@ -395,16 +379,14 @@ class GuiProjectToolBar(QWidget):
aRoot.triggered.connect(
lambda n, tHandle=tHandle: self.projView.setSelectedHandle(tHandle, doScroll=True)
)
return
##
# Internal Functions
##
def _buildRootMenu(self):
"""Build the rood folder menu.
"""
def _buildRootMenu(self) -> None:
"""Build the rood folder menu."""
def addClass(itemClass):
aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass]))
aNew.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[itemClass]))
@@ -431,7 +413,7 @@ class GuiProjectToolBar(QWidget):
##
@pyqtSlot(str)
def _treeSelectionChanged(self, tHandle):
def _treeSelectionChanged(self, tHandle: str) -> None:
"""Toggle the visibility of the new item entries for novel
documents. They should only be visible if novel documents can
actually be added.
@@ -470,7 +452,7 @@ class GuiProjectTree(QTreeWidget):
# Internal Variables
self._treeMap = {}
self._lastMove = {}
self._timeChanged = 0
self._timeChanged = 0.0
# Build GUI
# =========
@@ -553,7 +535,7 @@ class GuiProjectTree(QTreeWidget):
self.clear()
self._treeMap = {}
self._lastMove = {}
self._timeChanged = 0
self._timeChanged = 0.0
return
def newTreeItem(self, itemType: nwItemType, itemClass: nwItemClass | None = None,
@@ -658,11 +640,11 @@ class GuiProjectTree(QTreeWidget):
return True
def revealNewTreeItem(self, tHandle: str, nHandle: str | None = None,
def revealNewTreeItem(self, tHandle: str | None, nHandle: str | None = None,
wordCount: bool = False) -> bool:
"""Reveal a newly added project item in the project tree."""
nwItem = self.theProject.tree[tHandle]
if not nwItem:
nwItem = self.theProject.tree[tHandle] if tHandle else None
if tHandle is None or nwItem is None:
return False
trItem = self._addTreeItem(nwItem, nHandle)
@@ -1129,17 +1111,15 @@ class GuiProjectTree(QTreeWidget):
return
def openContextOnSelected(self):
"""Open the context menu on the current selected item.
"""
"""Open the context menu on the current selected item."""
selItem = self.selectedItems()
if selItem:
pos = self.visualItemRect(selItem[0]).center()
return self._openContextMenu(pos)
return False
def changedSince(self, checkTime):
"""Check if the tree has changed since a given time.
"""
def changedSince(self, checkTime: float) -> bool:
"""Check if the tree has changed since a given time."""
return self._timeChanged > checkTime
##
@@ -1147,16 +1127,15 @@ class GuiProjectTree(QTreeWidget):
##
@pyqtSlot()
def _treeSelectionChange(self):
"""The user changed which item is selected.
"""
def _treeSelectionChange(self) -> None:
"""The user changed which item is selected."""
tHandle = self.getSelectedHandle()
if tHandle is not None:
self.projView.selectedItemChanged.emit(tHandle)
return
@pyqtSlot("QTreeWidgetItem*", int)
def _treeDoubleClick(self, trItem, colNo):
def _treeDoubleClick(self, trItem: QTreeWidgetItem, colNo: int) -> None:
"""Capture a double-click event and either request the document
for editing if it is a file, or expand/close the node it is not.
"""
@@ -1176,7 +1155,7 @@ class GuiProjectTree(QTreeWidget):
return
@pyqtSlot("QPoint")
def _openContextMenu(self, clickPos):
def _openContextMenu(self, clickPos: QPoint) -> bool:
"""The user right clicked an element in the project tree, so we
open a context menu in-place.
"""
@@ -1343,20 +1322,20 @@ class GuiProjectTree(QTreeWidget):
# Events
##
def mousePressEvent(self, theEvent):
def mousePressEvent(self, event: QMouseEvent) -> None:
"""Overload mousePressEvent to clear selection if clicking the
mouse in a blank area of the tree view, and to load a document
for viewing if the user middle-clicked.
"""
super().mousePressEvent(theEvent)
super().mousePressEvent(event)
if theEvent.button() == Qt.LeftButton:
selItem = self.indexAt(theEvent.pos())
if event.button() == Qt.LeftButton:
selItem = self.indexAt(event.pos())
if not selItem.isValid():
self.clearSelection()
elif theEvent.button() == Qt.MiddleButton:
selItem = self.itemAt(theEvent.pos())
elif event.button() == Qt.MiddleButton:
selItem = self.itemAt(event.pos())
if not isinstance(selItem, QTreeWidgetItem):
return
@@ -1370,36 +1349,31 @@ class GuiProjectTree(QTreeWidget):
return
def dropEvent(self, theEvent):
def dropEvent(self, event: QDropEvent) -> None:
"""Overload the drop item event to ensure relevant data has been
updated.
"""
sHandle = self.getSelectedHandle()
if sHandle is None:
sItem = self._getTreeItem(sHandle) if sHandle else None
if sHandle is None or sItem is None:
logger.error("Invalid drag and drop event")
return
logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
sItem = self._getTreeItem(sHandle)
isExpanded = False
if sItem is not None:
isExpanded = sItem.isExpanded()
isExpanded = sItem.isExpanded()
pItem = sItem.parent()
pIndex = 0
if pItem is not None:
pIndex = pItem.indexOfChild(sItem)
pIndex = pItem.indexOfChild(sItem) if pItem else 0
wCount = self._getItemWordCount(sHandle)
self.propagateCount(sHandle, 0)
super().dropEvent(theEvent)
super().dropEvent(event)
self._postItemMove(sHandle, wCount)
self._recordLastMove(sItem, pItem, pIndex)
self._alertTreeChange(sHandle, flush=True)
if sItem is not None:
sItem.setExpanded(isExpanded)
sItem.setExpanded(isExpanded)
return
@@ -1407,13 +1381,12 @@ class GuiProjectTree(QTreeWidget):
# Internal Functions
##
def _postItemMove(self, tHandle, wCount):
"""Run various maintenance tasks for a moved item.
"""
def _postItemMove(self, tHandle: str, wCount: int) -> bool:
"""Run various maintenance tasks for a moved item."""
trItemS = self._getTreeItem(tHandle)
nwItemS = self.theProject.tree[tHandle]
trItemP = trItemS.parent()
if trItemP is None:
trItemP = trItemS.parent() if trItemS else None
if trItemP is None or nwItemS is None:
logger.error("Failed to find new parent item of '%s'", tHandle)
return False
@@ -1443,21 +1416,17 @@ class GuiProjectTree(QTreeWidget):
return True
def _getItemWordCount(self, tHandle):
"""Retrun the word count of a given item handle.
"""
def _getItemWordCount(self, tHandle: str) -> int:
"""Return the word count of a given item handle."""
tItem = self._getTreeItem(tHandle)
if tItem is None:
return 0
return int(tItem.data(self.C_DATA, self.D_WORDS))
return int(tItem.data(self.C_DATA, self.D_WORDS)) if tItem else 0
def _getTreeItem(self, tHandle: str | None) -> QTreeWidgetItem | None:
"""Return the QTreeWidgetItem of a given item handle."""
return self._treeMap.get(tHandle, None) if tHandle else None
def _toggleItemActive(self, tHandle):
"""Toggle the active status of an item.
"""
def _toggleItemActive(self, tHandle: str) -> None:
"""Toggle the active status of an item."""
tItem = self.theProject.tree[tHandle]
if tItem is not None:
tItem.setActive(not tItem.isActive)
@@ -1465,7 +1434,7 @@ class GuiProjectTree(QTreeWidget):
self._alertTreeChange(tHandle, flush=False)
return
def _recursiveSetExpanded(self, trItem, isExpanded):
def _recursiveSetExpanded(self, trItem: QTreeWidgetItem, isExpanded: bool) -> None:
"""Recursive function to set expanded status starting from (and
not including) a given item.
"""
@@ -1477,9 +1446,8 @@ class GuiProjectTree(QTreeWidget):
self._recursiveSetExpanded(chItem, isExpanded)
return
def _changeItemStatus(self, tHandle, tStatus):
"""Set a new status value of an item.
"""
def _changeItemStatus(self, tHandle: str, tStatus: str) -> None:
"""Set a new status value of an item."""
tItem = self.theProject.tree[tHandle]
if tItem is not None:
tItem.setStatus(tStatus)
@@ -1487,9 +1455,8 @@ class GuiProjectTree(QTreeWidget):
self._alertTreeChange(tHandle, flush=False)
return
def _changeItemImport(self, tHandle, tImport):
"""Set a new importance value of an item.
"""
def _changeItemImport(self, tHandle: str, tImport: str) -> None:
"""Set a new importance value of an item."""
tItem = self.theProject.tree[tHandle]
if tItem is not None:
tItem.setImport(tImport)
@@ -1497,9 +1464,8 @@ class GuiProjectTree(QTreeWidget):
self._alertTreeChange(tHandle, flush=False)
return
def _changeItemLayout(self, tHandle, itemLayout):
"""Set a new item layout value of an item.
"""
def _changeItemLayout(self, tHandle: str, itemLayout: nwItemLayout) -> None:
"""Set a new item layout value of an item."""
tItem = self.theProject.tree[tHandle]
if tItem is not None:
if itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
@@ -1512,9 +1478,8 @@ class GuiProjectTree(QTreeWidget):
self._alertTreeChange(tHandle, flush=False)
return
def _covertFolderToFile(self, tHandle, itemLayout):
"""Convert a folder to a note or document.
"""
def _covertFolderToFile(self, tHandle: str, itemLayout: nwItemLayout) -> None:
"""Convert a folder to a note or document."""
tItem = self.theProject.tree[tHandle]
if tItem is not None and tItem.isFolderType():
msgYes = self.mainGui.askQuestion(
@@ -1538,7 +1503,7 @@ class GuiProjectTree(QTreeWidget):
logger.info("Folder conversion cancelled")
return
def _mergeDocuments(self, tHandle, newFile):
def _mergeDocuments(self, tHandle: str, newFile: bool) -> bool:
"""Merge an item's child documents into a single document."""
logger.info("Request to merge items under handle '%s'", tHandle)
itemList = self.getTreeFromHandle(tHandle)
@@ -1613,7 +1578,7 @@ class GuiProjectTree(QTreeWidget):
return True
def _splitDocument(self, tHandle):
def _splitDocument(self, tHandle: str) -> bool:
"""Split a document into multiple documents."""
logger.info("Request to split items with handle '%s'", tHandle)
@@ -1621,8 +1586,8 @@ class GuiProjectTree(QTreeWidget):
if tItem is None:
return False
if not tItem.isFileType():
logger.error("Only documents can be split")
if not tItem.isFileType() or tItem.itemParent is None:
logger.error("Only valid document items can be split")
return False
dlgSplit = GuiDocSplit(self.mainGui, tHandle)
@@ -1715,9 +1680,8 @@ class GuiProjectTree(QTreeWidget):
return itemList
def _addTreeItem(
self, nwItem: NWItem | None, nHandle: str | None = None
) -> QTreeWidgetItem | None:
def _addTreeItem(self, nwItem: NWItem | None,
nHandle: str | None = None) -> QTreeWidgetItem | None:
"""Create a QTreeWidgetItem from an NWItem and add it to the
project tree. Returns the widget if the item is valid, otherwise
a None is returned.
@@ -1768,7 +1732,7 @@ class GuiProjectTree(QTreeWidget):
return newItem
def _addTrashRoot(self):
def _addTrashRoot(self) -> QTreeWidgetItem | None:
"""Adds the trash root folder if it doesn't already exist in the
project tree.
"""
@@ -1785,7 +1749,7 @@ class GuiProjectTree(QTreeWidget):
return trItem
def _alertTreeChange(self, tHandle, flush=False):
def _alertTreeChange(self, tHandle: str | None, flush: bool = False) -> None:
"""Update information on tree change state, and emit necessary
signals. A flush is only needed if an item is moved, created or
deleted.
@@ -1795,10 +1759,7 @@ class GuiProjectTree(QTreeWidget):
if flush:
self.saveTreeOrder()
if tHandle is None:
return
if tHandle not in self.theProject.tree:
if tHandle is None or tHandle not in self.theProject.tree:
return
tItem = self.theProject.tree[tHandle]
@@ -1809,9 +1770,9 @@ class GuiProjectTree(QTreeWidget):
return
def _recordLastMove(self, srcItem, parItem, parIndex):
"""Record the last action so that it can be undone.
"""
def _recordLastMove(self, srcItem: QTreeWidgetItem,
parItem: QTreeWidgetItem, parIndex: int) -> None:
"""Record the last action so that it can be undone."""
prevItem = self._lastMove.get("item", None)
if prevItem is None or srcItem != prevItem:
self._lastMove = {
@@ -1819,7 +1780,6 @@ class GuiProjectTree(QTreeWidget):
"parent": parItem,
"index": parIndex,
}
return
# END Class GuiProjectTree