Re-enable item selection
This commit is contained in:
@@ -154,13 +154,19 @@ class ProjectNode:
|
|||||||
|
|
||||||
class ProjectModel(QAbstractItemModel):
|
class ProjectModel(QAbstractItemModel):
|
||||||
|
|
||||||
__slots__ = ("_root", "_map")
|
__slots__ = ("_tree", "_root")
|
||||||
|
|
||||||
def __init__(self, tree: NWTree) -> None:
|
def __init__(self, tree: NWTree) -> None:
|
||||||
super().__init__(None)
|
super().__init__(None)
|
||||||
|
logger.debug("Create: ProjectModel")
|
||||||
|
self._tree = tree
|
||||||
self._root = ProjectNode(NWItem(tree._project, ""))
|
self._root = ProjectNode(NWItem(tree._project, ""))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def __del__(self) -> None:
|
||||||
|
logger.debug("Delete: ProjectModel")
|
||||||
|
return
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def root(self) -> ProjectNode:
|
def root(self) -> ProjectNode:
|
||||||
return self._root
|
return self._root
|
||||||
@@ -220,6 +226,16 @@ class ProjectModel(QAbstractItemModel):
|
|||||||
return index.internalPointer()
|
return index.internalPointer()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def indexFromHandle(self, handle: str | None) -> QModelIndex:
|
||||||
|
"""Get the index representing a node in the model."""
|
||||||
|
if handle and (node := self._tree.nodes.get(handle)):
|
||||||
|
return self.createIndex(node.row(), 0, node)
|
||||||
|
return QModelIndex()
|
||||||
|
|
||||||
|
def indexFromNode(self, node: ProjectNode) -> QModelIndex:
|
||||||
|
"""Get the index representing a node in the model."""
|
||||||
|
return self.createIndex(node.row(), 0, node)
|
||||||
|
|
||||||
##
|
##
|
||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ class NWTree:
|
|||||||
def model(self) -> ProjectModel:
|
def model(self) -> ProjectModel:
|
||||||
return self._model
|
return self._model
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nodes(self) -> dict[str, ProjectNode]:
|
||||||
|
return self._nodes
|
||||||
|
|
||||||
##
|
##
|
||||||
# Class Methods
|
# Class Methods
|
||||||
##
|
##
|
||||||
|
|||||||
+131
-80
@@ -41,7 +41,7 @@ from novelwriter import CONFIG, SHARED
|
|||||||
from novelwriter.common import qtLambda
|
from novelwriter.common import qtLambda
|
||||||
from novelwriter.constants import nwLabels, nwUnicode, trConst
|
from novelwriter.constants import nwLabels, nwUnicode, trConst
|
||||||
from novelwriter.core.item import NWItem
|
from novelwriter.core.item import NWItem
|
||||||
from novelwriter.core.itemmodel import ProjectModel
|
from novelwriter.core.itemmodel import ProjectModel, ProjectNode
|
||||||
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
||||||
from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
|
from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
|
||||||
from novelwriter.extensions.modified import NIconToolButton
|
from novelwriter.extensions.modified import NIconToolButton
|
||||||
@@ -165,13 +165,12 @@ class GuiProjectView(QWidget):
|
|||||||
|
|
||||||
def saveProjectTasks(self) -> None:
|
def saveProjectTasks(self) -> None:
|
||||||
"""Run save project tasks."""
|
"""Run save project tasks."""
|
||||||
self.projTree.saveTreeOrder()
|
# self.projTree.saveTreeOrder()
|
||||||
return
|
return
|
||||||
|
|
||||||
def populateTree(self) -> None:
|
def populateTree(self) -> None:
|
||||||
"""Build the tree structure from project data."""
|
"""Build the tree structure from project data."""
|
||||||
self.projTree.loadModel()
|
self.projTree.loadModel()
|
||||||
# self.projTree.buildTree()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def setTreeFocus(self) -> None:
|
def setTreeFocus(self) -> None:
|
||||||
@@ -562,8 +561,6 @@ class GuiProjectTree(QTreeView):
|
|||||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
|
|
||||||
# Connect signals
|
# Connect signals
|
||||||
# self.itemDoubleClicked.connect(self._treeDoubleClick)
|
|
||||||
# self.itemSelectionChanged.connect(self._treeSelectionChange)
|
|
||||||
self.clicked.connect(self._treeSingleClick)
|
self.clicked.connect(self._treeSingleClick)
|
||||||
self.doubleClicked.connect(self._treeDoubleClick)
|
self.doubleClicked.connect(self._treeDoubleClick)
|
||||||
|
|
||||||
@@ -595,10 +592,26 @@ class GuiProjectTree(QTreeView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Class Methods
|
# External Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def getSelectedHandle(self) -> str | None:
|
||||||
|
"""Get the currently selected handle."""
|
||||||
|
if (indexes := self.selectedIndexes()) and (node := self._getNode(indexes[0])):
|
||||||
|
return node.item.itemHandle
|
||||||
|
return None
|
||||||
|
|
||||||
|
##
|
||||||
|
# Module Internal Methods
|
||||||
|
##
|
||||||
|
|
||||||
|
def clearTree(self) -> None:
|
||||||
|
"""Clear the tree view."""
|
||||||
|
self.setModel(None)
|
||||||
|
return
|
||||||
|
|
||||||
def loadModel(self) -> None:
|
def loadModel(self) -> None:
|
||||||
|
"""Load and prepare a new project model."""
|
||||||
self.setModel(SHARED.project.tree.model)
|
self.setModel(SHARED.project.tree.model)
|
||||||
|
|
||||||
# Lock the column sizes
|
# Lock the column sizes
|
||||||
@@ -622,12 +635,64 @@ class GuiProjectTree(QTreeView):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def clearTree(self) -> None:
|
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
|
||||||
"""Clear the GUI content and the related map."""
|
"""Set a specific handle as the selected item."""
|
||||||
# self.clear()
|
if (model := self._getModel()) and (index := model.indexFromHandle(tHandle)).isValid():
|
||||||
# self._treeMap = {}
|
self.setCurrentIndex(index)
|
||||||
|
if doScroll:
|
||||||
|
self.scrollTo(index, QAbstractItemView.ScrollHint.PositionAtCenter)
|
||||||
|
self.projView.selectedItemChanged.emit(tHandle)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Private Slots
|
||||||
|
##
|
||||||
|
|
||||||
|
##
|
||||||
|
# Private Slots
|
||||||
|
##
|
||||||
|
|
||||||
|
@pyqtSlot(QModelIndex)
|
||||||
|
def _treeSingleClick(self, index: QModelIndex) -> None:
|
||||||
|
"""The user changed which item is selected."""
|
||||||
|
if node := self._getNode(index):
|
||||||
|
self.projView.selectedItemChanged.emit(node.item.itemHandle)
|
||||||
|
return
|
||||||
|
|
||||||
|
@pyqtSlot(QModelIndex)
|
||||||
|
def _treeDoubleClick(self, index: QModelIndex) -> None:
|
||||||
|
"""Capture a double-click event and either request the document
|
||||||
|
for editing if it is a file, or expand/close the node if not.
|
||||||
|
"""
|
||||||
|
if node := self._getNode(index):
|
||||||
|
if node.item.isFileType():
|
||||||
|
self.projView.openDocumentRequest.emit(
|
||||||
|
node.item.itemHandle, nwDocMode.EDIT, "", True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.setExpanded(index, not self.isExpanded(index))
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Internal Functions
|
||||||
|
##
|
||||||
|
|
||||||
|
def _getModel(self) -> ProjectModel | None:
|
||||||
|
"""Return a project node corresponding to a model index."""
|
||||||
|
if isinstance(model := self.model(), ProjectModel):
|
||||||
|
return model
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _getNode(self, index: QModelIndex) -> ProjectNode | None:
|
||||||
|
"""Return a project node corresponding to a model index."""
|
||||||
|
if isinstance(model := self.model(), ProjectModel) and (node := model.node(index)):
|
||||||
|
return node
|
||||||
|
return None
|
||||||
|
|
||||||
|
# =========================================================================================== #
|
||||||
|
# Old Code
|
||||||
|
# =========================================================================================== #
|
||||||
|
|
||||||
def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
|
def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
|
||||||
"""Create a new note. This function is used by the document
|
"""Create a new note. This function is used by the document
|
||||||
editor to create note files for unknown tags.
|
editor to create note files for unknown tags.
|
||||||
@@ -838,20 +903,20 @@ class GuiProjectTree(QTreeView):
|
|||||||
# self._alertTreeChange(tHandle, flush=False)
|
# self._alertTreeChange(tHandle, flush=False)
|
||||||
return
|
return
|
||||||
|
|
||||||
def saveTreeOrder(self) -> None:
|
# def saveTreeOrder(self) -> None:
|
||||||
"""Build a list of the items in the project tree and send them
|
# """Build a list of the items in the project tree and send them
|
||||||
to the project class. This syncs up the two versions of the
|
# to the project class. This syncs up the two versions of the
|
||||||
project structure, and must be called before any code that
|
# project structure, and must be called before any code that
|
||||||
depends on this order to be up to date.
|
# depends on this order to be up to date.
|
||||||
"""
|
# """
|
||||||
# items = []
|
# # items = []
|
||||||
# for i in range(self.topLevelItemCount()):
|
# # for i in range(self.topLevelItemCount()):
|
||||||
# item = self.topLevelItem(i)
|
# # item = self.topLevelItem(i)
|
||||||
# if isinstance(item, QTreeWidgetItem):
|
# # if isinstance(item, QTreeWidgetItem):
|
||||||
# items = self._scanChildren(items, item, i)
|
# # items = self._scanChildren(items, item, i)
|
||||||
# logger.debug("Saving project tree item order")
|
# # logger.debug("Saving project tree item order")
|
||||||
# SHARED.project.setTreeOrder(items)
|
# # SHARED.project.setTreeOrder(items)
|
||||||
return
|
# return
|
||||||
|
|
||||||
def getTreeFromHandle(self, tHandle: str) -> list[str]:
|
def getTreeFromHandle(self, tHandle: str) -> list[str]:
|
||||||
"""Recursively return all the child items starting from a given
|
"""Recursively return all the child items starting from a given
|
||||||
@@ -1140,35 +1205,21 @@ class GuiProjectTree(QTreeView):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def buildTree(self) -> None:
|
# def getSelectedHandle(self) -> str | None:
|
||||||
"""Build the entire project tree from scratch. This depends on
|
# """Get the currently selected handle. If multiple items are
|
||||||
the save project item iterator in the project class which will
|
# selected, return the first.
|
||||||
always make sure items with a parent have had their parent item
|
# """
|
||||||
sent first.
|
# # if items := self.selectedItems():
|
||||||
"""
|
# # return items[0].data(self.C_DATA, self.D_HANDLE)
|
||||||
# logger.debug("Building the project tree ...")
|
# return None
|
||||||
# self.clearTree()
|
|
||||||
# for nwItem in SHARED.project.iterProjectItems():
|
|
||||||
# self._addTreeItem(nwItem)
|
|
||||||
# self.setActiveHandle(self._actHandle)
|
|
||||||
# logger.info("%d item(s) added to the project tree", len(self._treeMap))
|
|
||||||
return
|
|
||||||
|
|
||||||
def getSelectedHandle(self) -> str | None:
|
# def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
|
||||||
"""Get the currently selected handle. If multiple items are
|
# """Set a specific handle as the selected item."""
|
||||||
selected, return the first.
|
# # if tHandle in self._treeMap:
|
||||||
"""
|
# # self.setCurrentItem(self._treeMap[tHandle])
|
||||||
# if items := self.selectedItems():
|
# # if (indexes := self.selectedIndexes()) and doScroll:
|
||||||
# return items[0].data(self.C_DATA, self.D_HANDLE)
|
# # self.scrollTo(indexes[0], QAbstractItemView.ScrollHint.PositionAtCenter)
|
||||||
return None
|
# return
|
||||||
|
|
||||||
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
|
|
||||||
"""Set a specific handle as the selected item."""
|
|
||||||
# if tHandle in self._treeMap:
|
|
||||||
# self.setCurrentItem(self._treeMap[tHandle])
|
|
||||||
# if (indexes := self.selectedIndexes()) and doScroll:
|
|
||||||
# self.scrollTo(indexes[0], QAbstractItemView.ScrollHint.PositionAtCenter)
|
|
||||||
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."""
|
||||||
@@ -1239,35 +1290,35 @@ class GuiProjectTree(QTreeView):
|
|||||||
# Private Slots
|
# Private Slots
|
||||||
##
|
##
|
||||||
|
|
||||||
@pyqtSlot(QModelIndex)
|
# @pyqtSlot(QModelIndex)
|
||||||
def _treeSingleClick(self, index: QModelIndex) -> None:
|
# def _treeSingleClick(self, index: QModelIndex) -> None:
|
||||||
"""The user changed which item is selected."""
|
# """The user changed which item is selected."""
|
||||||
if isinstance(model := self.model(), ProjectModel) and (node := model.node(index)):
|
# if isinstance(model := self.model(), ProjectModel) and (node := model.node(index)):
|
||||||
self.projView.selectedItemChanged.emit(node.item.itemHandle)
|
# self.projView.selectedItemChanged.emit(node.item.itemHandle)
|
||||||
|
|
||||||
# # When selecting multiple items, don't allow including root
|
# # # When selecting multiple items, don't allow including root
|
||||||
# # items in the selection and instead deselect them
|
# # # items in the selection and instead deselect them
|
||||||
# items = self.selectedItems()
|
# # items = self.selectedItems()
|
||||||
# if items and len(items) > 1:
|
# # if items and len(items) > 1:
|
||||||
# for item in items:
|
# # for item in items:
|
||||||
# if item.parent() is None:
|
# # if item.parent() is None:
|
||||||
# item.setSelected(False)
|
# # item.setSelected(False)
|
||||||
|
|
||||||
return
|
# return
|
||||||
|
|
||||||
@pyqtSlot(QModelIndex)
|
# @pyqtSlot(QModelIndex)
|
||||||
def _treeDoubleClick(self, index: QModelIndex) -> None:
|
# def _treeDoubleClick(self, index: QModelIndex) -> None:
|
||||||
"""Capture a double-click event and either request the document
|
# """Capture a double-click event and either request the document
|
||||||
for editing if it is a file, or expand/close the node if not.
|
# for editing if it is a file, or expand/close the node if not.
|
||||||
"""
|
# """
|
||||||
if isinstance(model := self.model(), ProjectModel) and (node := model.node(index)):
|
# if isinstance(model := self.model(), ProjectModel) and (node := model.node(index)):
|
||||||
if node.item.isFileType():
|
# if node.item.isFileType():
|
||||||
self.projView.openDocumentRequest.emit(
|
# self.projView.openDocumentRequest.emit(
|
||||||
node.item.itemHandle, nwDocMode.EDIT, "", True
|
# node.item.itemHandle, nwDocMode.EDIT, "", True
|
||||||
)
|
# )
|
||||||
else:
|
# else:
|
||||||
self.setExpanded(index, not self.isExpanded(index))
|
# self.setExpanded(index, not self.isExpanded(index))
|
||||||
return
|
# return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _doAutoScroll(self) -> None:
|
def _doAutoScroll(self) -> None:
|
||||||
@@ -2007,7 +2058,7 @@ class _TreeContextMenu(QMenu):
|
|||||||
for tItem in self._items:
|
for tItem in self._items:
|
||||||
if tItem.isFileType() and tItem.itemClass != nwItemClass.TRASH:
|
if tItem.isFileType() and tItem.itemClass != nwItemClass.TRASH:
|
||||||
self.projTree.moveItemToTrash(tItem.itemHandle, askFirst=False, flush=False)
|
self.projTree.moveItemToTrash(tItem.itemHandle, askFirst=False, flush=False)
|
||||||
self.projTree.saveTreeOrder()
|
# self.projTree.saveTreeOrder()
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -2017,7 +2068,7 @@ class _TreeContextMenu(QMenu):
|
|||||||
for tItem in self._items:
|
for tItem in self._items:
|
||||||
if tItem.isFileType() and tItem.itemClass == nwItemClass.TRASH:
|
if tItem.isFileType() and tItem.itemClass == nwItemClass.TRASH:
|
||||||
self.projTree.permDeleteItem(tItem.itemHandle, askFirst=False, flush=False)
|
self.projTree.permDeleteItem(tItem.itemHandle, askFirst=False, flush=False)
|
||||||
self.projTree.saveTreeOrder()
|
# self.projTree.saveTreeOrder()
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
|||||||
Reference in New Issue
Block a user