Add back create new project item functionality

This commit is contained in:
Veronica Berglyd Olsen
2024-11-19 00:22:59 +01:00
parent a5575992d6
commit 53e70c118c
5 changed files with 279 additions and 332 deletions
+51 -27
View File
@@ -32,6 +32,7 @@ from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt
from PyQt5.QtGui import QIcon
from novelwriter import SHARED
from novelwriter.common import minmax
from novelwriter.core.item import NWItem
from novelwriter.types import QtAlignRight
@@ -164,36 +165,44 @@ class ProjectNode:
# Data Edit
##
def addChild(self, child: ProjectNode) -> None:
def addChild(self, child: ProjectNode, pos: int = -1) -> None:
"""Add a child item to this item."""
child._parent = self
child._row = len(self._children)
self._children.append(child)
if 0 <= pos < len(self._children):
self._children.insert(pos, child)
self._refreshChildrenPos()
else:
child._row = len(self._children)
self._children.append(child)
self.refresh()
return
def moveChild(self, source: int, step: int) -> int:
def moveChild(self, source: int, target: int) -> None:
"""Move a child internally."""
count = len(self._children)
if 0 <= source < count:
target = max(min(source + step, count - 1), 0)
if source != target:
node = self._children.pop(source)
self._children.insert(target, node)
for n, child in enumerate(self._children):
child._row = n
return target + 1 if target > source else target
return -1
if (source != target) and (0 <= source < count) and (0 <= target <= count):
node = self._children.pop(source)
self._children.insert(target, node)
self._refreshChildrenPos()
return
##
# Internal Functions
##
def _recursiveAppendChildren(self, children: list[ProjectNode]) -> None:
"""Recursively add all nodes to a list."""
for node in self._children:
children.append(node)
node._recursiveAppendChildren(children)
return
def _refreshChildrenPos(self) -> None:
"""Update the row value on all children."""
for n, child in enumerate(self._children):
child._row = n
return
class ProjectModel(QAbstractItemModel):
@@ -258,18 +267,17 @@ class ProjectModel(QAbstractItemModel):
node: ProjectNode = index.internalPointer()
return node.data(index.column(), role)
# def addChild(self, node: ProjectNode, parent: QModelIndex) -> None:
# if parent.isValid():
# item = parent.internalPointer()
# else:
# item = self._root
# item.addChild(node)
# return
##
# Data Access
##
def row(self, index: QModelIndex) -> int:
"""Return the row number of the index."""
if index.isValid():
node: ProjectNode = index.internalPointer()
return node.row()
return -1
def node(self, index: QModelIndex) -> ProjectNode | None:
"""Return the node for a given model index."""
if index.isValid():
@@ -286,25 +294,41 @@ class ProjectModel(QAbstractItemModel):
"""Get the index representing a node in the model."""
return self.createIndex(node.row(), 0, node)
def rootIndex(self) -> QModelIndex:
"""Get the index representing the root."""
return self.createIndex(0, 0, self._root)
##
# Model Edit
##
def insertChild(self, child: ProjectNode, parent: QModelIndex, pos: int) -> None:
"""Insert a node into the model at a given position."""
if parent.isValid():
node: ProjectNode = parent.internalPointer()
else:
node = self._root
count = node.childCount()
row = minmax(pos, 0, count) if pos >= 0 else count
self.beginInsertRows(parent, row, row)
node.addChild(child, row)
self.endInsertRows()
return
def internalMove(self, index: QModelIndex, step: int) -> None:
"""Move an item internally among its siblings."""
if index.isValid():
node: ProjectNode = index.internalPointer()
if parent := node.parent():
pos = index.row()
if (new := parent.moveChild(index.row(), step)) > -1:
self.beginMoveRows(index.parent(), pos, pos, index.parent(), new)
new = minmax(pos + step, 0, parent.childCount() - 1)
if new != pos:
end = new if new < pos else new + 1
self.beginMoveRows(index.parent(), pos, pos, index.parent(), end)
parent.moveChild(pos, new)
self.endMoveRows()
return
def moveRows(self, indices: list[QModelIndex], destination: QModelIndex, row: int) -> bool:
"""Move indices to destination."""
return False
##
# Other Methods
##
+12 -24
View File
@@ -154,20 +154,20 @@ class NWProject:
# Item Methods
##
def newRoot(self, itemClass: nwItemClass, label: str | None = None) -> str:
def newRoot(self, itemClass: nwItemClass, pos: int = -1) -> str | None:
"""Add a new root folder to the project. If label is not set,
use the class label.
"""
label = label or trConst(nwLabels.CLASS_NAME[itemClass])
return self._tree.create(label, None, nwItemType.ROOT, itemClass)
label = trConst(nwLabels.CLASS_NAME[itemClass])
return self._tree.create(label, None, nwItemType.ROOT, itemClass=itemClass, pos=pos)
def newFolder(self, label: str, parent: str) -> str | None:
def newFolder(self, label: str, parent: str, pos: int = -1) -> str | None:
"""Add a new folder with a given label and parent item."""
return self._tree.create(label, parent, nwItemType.FOLDER)
return self._tree.create(label, parent, nwItemType.FOLDER, pos=pos)
def newFile(self, label: str, parent: str) -> str | None:
def newFile(self, label: str, parent: str, pos: int = -1) -> str | None:
"""Add a new file with a given label and parent item."""
return self._tree.create(label, parent, nwItemType.FILE)
return self._tree.create(label, parent, nwItemType.FILE, pos=pos)
def writeNewFile(self, tHandle: str, hLevel: int, isDocument: bool, text: str = "") -> bool:
"""Write content to a new document after it is created. This
@@ -233,11 +233,11 @@ class NWProject:
def trashFolder(self) -> str:
"""Add the special trash root folder to the project."""
trashHandle = self._tree.trashRoot
if trashHandle is None:
label = trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH])
return self._tree.create(label, None, nwItemType.ROOT, nwItemClass.TRASH)
return trashHandle
# trashHandle = self._tree.trashRoot
# if trashHandle is None:
# label = trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH])
# return self._tree.create(label, None, nwItemType.ROOT, nwItemClass.TRASH)
return ""
##
# Project Methods
@@ -329,7 +329,6 @@ class NWProject:
# ============
self._tree.unpack(projContent)
self._tree.buildModel()
self._options.loadSettings()
self._loadProjectLocalisation()
@@ -490,17 +489,6 @@ class NWProject:
self.setProjectChanged(True)
return
def setTreeOrder(self, order: list[str]) -> None:
"""A list representing the linear/flattened order of project
items in the GUI project tree. The user can rearrange the order
by drag-and-drop. Forwarded to the NWTree class.
"""
if len(self._tree) != len(order):
logger.warning("Sizes of new and old tree order do not match")
self._tree.setOrder(order)
self.setProjectChanged(True)
return
def setProjectChanged(self, status: bool) -> bool:
"""Toggle the project changed flag, and propagate the
information to the GUI statusbar.
+116 -165
View File
@@ -28,10 +28,12 @@ import random
from collections.abc import Iterable, Iterator
from pathlib import Path
from typing import TYPE_CHECKING, Literal, overload
from typing import TYPE_CHECKING
from PyQt5.QtCore import QModelIndex
from novelwriter import SHARED
from novelwriter.constants import nwFiles
from novelwriter.constants import nwFiles, nwLabels, trConst
from novelwriter.core.item import NWItem
from novelwriter.core.itemmodel import ProjectModel, ProjectNode
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
@@ -73,8 +75,7 @@ class NWTree:
self._model = ProjectModel(self)
self._items: dict[str, NWItem] = {}
self._nodes: dict[str, ProjectNode] = {}
self._trash = None # The handle of the trash root folder
self._changed = False # True if tree structure has changed
self._trash = None
logger.debug("Ready: NWTree")
return
@@ -86,10 +87,10 @@ class NWTree:
# Properties
##
@property
def trashRoot(self) -> str | None:
"""Return the handle of the trash folder, or None."""
return self._trash
# @property
# def trashRoot(self) -> str | None:
# """Return the handle of the trash folder, or None."""
# return self._trash
@property
def model(self) -> ProjectModel:
@@ -110,7 +111,6 @@ class NWTree:
self._items = {}
self._nodes = {}
self._trash = None
self._changed = False
oldModel.deleteLater()
del oldModel
return
@@ -119,74 +119,58 @@ class NWTree:
"""Returns a copy of the list of all the active handles."""
return list(self._items.keys())
@overload # pragma: no cover
def create(self, label: str, parent: None, itemType: Literal[nwItemType.ROOT],
itemClass: nwItemClass) -> str:
pass
def add(self, item: NWItem, pos: int = -1) -> bool:
"""Add a project item into the project tree."""
if pHandle := item.itemParent:
if parent := self._nodes.get(pHandle):
node = ProjectNode(item)
index = self._model.indexFromNode(parent)
self._model.insertChild(node, index, pos)
self._nodes[item.itemHandle] = node
self._items[item.itemHandle] = item
else:
logger.error("Could not locate parent of '%s'", item.itemHandle)
return False
elif item.isRootType():
node = ProjectNode(item)
self._model.insertChild(node, QModelIndex(), pos)
self._nodes[item.itemHandle] = node
self._items[item.itemHandle] = item
else:
logger.error("Invalid project item '%s'", item.itemHandle)
return False
return True
@overload # pragma: no cover
def create(self, label: str, parent: str | None, itemType: nwItemType,
itemClass: nwItemClass = nwItemClass.NO_CLASS) -> str | None:
pass
def create(self, label, parent, itemType, itemClass=nwItemClass.NO_CLASS) -> str | None:
def create(
self, label: str, parent: str | None, itemType: nwItemType,
itemClass: nwItemClass = nwItemClass.NO_CLASS, pos: int = -1,
) -> str | None:
"""Create a new item in the project tree, and return its handle.
If the item cannot be added to the project because of an invalid
parent, None is returned. For root elements, this cannot occur.
"""
# parent = None if itemType == nwItemType.ROOT else parent
# if parent is None or parent in self._order:
# tHandle = self._makeHandle()
# newItem = NWItem(self._project, tHandle)
# newItem.setName(label)
# newItem.setParent(parent)
# newItem.setType(itemType)
# newItem.setClass(itemClass)
# self.append(newItem)
# self.updateItemData(tHandle)
# return tHandle
parent = None if itemType == nwItemType.ROOT else parent
if parent is None or parent in self._nodes:
tHandle = self._makeHandle()
nwItem = NWItem(self._project, tHandle)
nwItem.setName(label)
nwItem.setParent(parent)
nwItem.setType(itemType)
nwItem.setClass(itemClass)
if self.add(nwItem, pos):
self.updateItemData(tHandle)
self._project.setProjectChanged(True)
return tHandle
return None
def append(self, nwItem: NWItem) -> bool:
"""Add a new item to the end of the tree."""
# tHandle = nwItem.itemHandle
# pHandle = nwItem.itemParent
# if not isHandle(tHandle):
# logger.warning("Invalid item handle '%s' detected, skipping", tHandle)
# return False
# if tHandle in self._tree:
# logger.warning("Duplicate handle '%s' detected, skipping", tHandle)
# return False
# logger.debug("Adding item '%s' with parent '%s'", str(tHandle), str(pHandle))
# if nwItem.isRootType():
# logger.debug("Item '%s' is a root item", str(tHandle))
# self._roots[tHandle] = nwItem
# if nwItem.itemClass == nwItemClass.TRASH:
# if self._trash is None:
# logger.debug("Item '%s' is the trash folder", str(tHandle))
# self._trash = tHandle
# else:
# logger.error("Only one trash folder allowed")
# return False
# self._tree[tHandle] = nwItem
# self._order.append(tHandle)
# self._setTreeChanged(True)
return True
def duplicate(self, sHandle: str) -> NWItem | None:
"""Duplicate an item and set a new handle."""
sItem = self.__getitem__(sHandle)
if isinstance(sItem, NWItem):
nItem = NWItem.duplicate(sItem, self._makeHandle())
if self.append(nItem):
logger.info("Duplicated item '%s' -> '%s'", sHandle, nItem.itemHandle)
return nItem
# sItem = self.__getitem__(sHandle)
# if isinstance(sItem, NWItem):
# nItem = NWItem.duplicate(sItem, self._makeHandle())
# if self.append(nItem):
# logger.info("Duplicated item '%s' -> '%s'", sHandle, nItem.itemHandle)
# return nItem
return None
def pack(self) -> list[dict]:
@@ -206,28 +190,22 @@ class NWTree:
project tree.
"""
self.clear()
items: dict[str, NWItem] = self._items.copy()
for item in data:
nwItem = NWItem(self._project, "")
if nwItem.unpack(item):
self._items[nwItem.itemHandle] = nwItem
if nwItem.itemClass == nwItemClass.TRASH:
logger.debug("Item '%s' is the trash folder", str(nwItem.itemHandle))
self._trash = nwItem.itemHandle
return
items[nwItem.itemHandle] = nwItem
def buildModel(self) -> None:
""""""
later = items
self._model.beginInsertRows(self._model.index(0, 0), 0, 0)
later: dict[str, NWItem] = self._items.copy()
for _ in range(999):
later = self._buildTree(later)
later = self._addItems(later)
if len(later) == 0:
break
else:
logger.error("Not all items could be added to project tree")
for item in later.values():
item.setParent(None)
self._trash = self._getTrashNode()
self._model.endInsertRows()
self._model.layoutChanged.emit()
@@ -248,30 +226,9 @@ class NWTree:
else:
for index in indices:
self._model.dataChanged.emit(index, index)
self._project.setProjectChanged(len(indices) > 0)
return
def _buildTree(self, items: dict[str, NWItem]) -> dict[str, NWItem]:
""""""
remains: dict[str, NWItem] = {}
for handle, item in items.items():
if pHandle := item.itemParent:
if parent := self._nodes.get(pHandle):
node = ProjectNode(item)
parent.addChild(node)
self._nodes[handle] = node
elif pHandle in items:
remains[handle] = item
logger.warning("Item '%s' found before its parent", handle)
else:
item.setParent(None)
logger.error("Item '%s' has no parent in current tree", handle)
elif item.isRootType():
node = ProjectNode(item)
self._model.root.addChild(node)
self._nodes[handle] = node
return remains
def checkConsistency(self, prefix: str) -> tuple[int, int]:
"""Check the project tree consistency. Also check the content
folder and add back files that were discovered but were not
@@ -324,7 +281,7 @@ class NWTree:
newItem.setType(nwItemType.FILE)
newItem.setClass(oClass)
newItem.setLayout(oLayout)
if self.append(newItem):
if self.add(newItem):
self.updateItemData(cHandle)
recovered += 1
@@ -393,22 +350,20 @@ class NWTree:
"""Update the root item handle of a given item. Returns True if
a root was found and data updated, otherwise False.
"""
tItem = self.__getitem__(tHandle)
if tItem is None:
return False
iItem = tItem
for _ in range(MAX_DEPTH):
if iItem.itemParent is None:
tItem.setRoot(iItem.itemHandle)
tItem.setClassDefaults(iItem.itemClass)
return True
if tItem := self._items.get(tHandle):
iItem = tItem
for _ in range(MAX_DEPTH):
if iItem.itemParent is None:
tItem.setRoot(iItem.itemHandle)
tItem.setClassDefaults(iItem.itemClass)
return True
else:
iItem = self.__getitem__(iItem.itemParent)
if iItem is None:
return False
else:
iItem = self.__getitem__(iItem.itemParent)
if iItem is None:
return False
else:
raise RecursionError("Critical internal error")
raise RecursionError("Critical internal error")
return False
def checkType(self, tHandle: str, itemType: nwItemType) -> bool:
"""Check if item exists and is of the specified item type."""
@@ -463,18 +418,18 @@ class NWTree:
def isTrash(self, tHandle: str) -> bool:
"""Check if an item is in or is the trash folder."""
tItem = self.__getitem__(tHandle)
if tItem is None:
return True
if tItem.itemClass == nwItemClass.TRASH:
return True
if self._trash is not None:
if tHandle == self._trash:
return True
elif tItem.itemParent == self._trash:
return True
elif tItem.itemRoot == self._trash:
return True
# tItem = self.__getitem__(tHandle)
# if tItem is None:
# return True
# if tItem.itemClass == nwItemClass.TRASH:
# return True
# if self._trash is not None:
# if tHandle == self._trash:
# return True
# elif tItem.itemParent == self._trash:
# return True
# elif tItem.itemRoot == self._trash:
# return True
return False
def findRoot(self, itemClass: nwItemClass | None) -> str | None:
@@ -484,29 +439,6 @@ class NWTree:
return node.item.itemHandle
return None
##
# Setters
##
def setOrder(self, newOrder: list[str]) -> None:
"""Reorders the tree based on a list of items."""
# tmpOrder = [tHandle for tHandle in newOrder if tHandle in self._tree]
# if not (len(tmpOrder) == len(newOrder) == len(self._order)):
# # Something is wrong, so let's debug it
# for tHandle in newOrder:
# if tHandle not in self._tree:
# logger.error("Handle '%s' in new tree order is not in old order", tHandle)
# for tHandle in self._order:
# if tHandle not in tmpOrder:
# logger.warning("Handle '%s' in old tree order is not in new order", tHandle)
# # Save the temp list
# self._order = tmpOrder
# self._setTreeChanged(True)
# logger.debug("Project tree order updated")
return
##
# Special Methods
##
@@ -542,8 +474,6 @@ class NWTree:
# if tHandle == self._trash:
# self._trash = None
# self._setTreeChanged(True)
return
def __contains__(self, tHandle: str) -> bool:
@@ -554,24 +484,45 @@ class NWTree:
"""Iterate through project items."""
for node in self._model.root.allChildren():
yield node.item
# for tHandle in self._order:
# tItem = self._tree.get(tHandle)
# if isinstance(tItem, NWItem):
# yield tItem
return
##
# Internal Functions
##
def _setTreeChanged(self, state: bool) -> None:
"""Set the changed flag to state, and if being set to True,
propagate that state change to the parent NWProject class.
def _getTrashNode(self) -> ProjectNode | None:
"""Get the trash node. If it doesn't exist, create it."""
for node in self._model.root.children:
if node.item.itemClass == nwItemClass.TRASH:
return node
label = trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH])
if handle := self.create(label, None, nwItemType.ROOT, nwItemClass.TRASH):
return self._nodes.get(handle)
return None
def _addItems(self, items: dict[str, NWItem]) -> dict[str, NWItem]:
"""Add a dictionary of items to the project tree. Returns a new
dictionary of items that could not be added yet, but can be.
"""
self._changed = state
if state:
self._project.setProjectChanged(True)
return
remains: dict[str, NWItem] = {}
for handle, item in items.items():
if pHandle := item.itemParent:
if parent := self._nodes.get(pHandle):
node = ProjectNode(item)
parent.addChild(node)
self._items[handle] = item
self._nodes[handle] = node
elif pHandle in items:
remains[handle] = item
logger.warning("Item '%s' found before its parent", handle)
else:
logger.error("Item '%s' has no parent in current tree", handle)
elif item.isRootType():
node = ProjectNode(item)
self._model.root.addChild(node)
self._items[handle] = item
self._nodes[handle] = node
return remains
def _makeHandle(self) -> str:
"""Generate a unique item handle. In the event that the key
+96 -112
View File
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.common import qtLambda
from novelwriter.constants import nwLabels, nwUnicode, trConst
from novelwriter.constants import nwLabels, nwStyles, nwUnicode, trConst
from novelwriter.core.item import NWItem
from novelwriter.core.itemmodel import ProjectModel, ProjectNode
from novelwriter.dialogs.editlabel import GuiEditLabel
@@ -568,8 +568,8 @@ class GuiProjectTree(QTreeView):
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
# Connect signals
self.clicked.connect(self._treeSingleClick)
self.doubleClicked.connect(self._treeDoubleClick)
self.clicked.connect(self._onSingleClick)
self.doubleClicked.connect(self._onDoubleClick)
# Auto Scroll
# self._scrollMargin = SHARED.theme.baseIconHeight
@@ -639,13 +639,19 @@ class GuiProjectTree(QTreeView):
treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg)
treeHeader.resizeSection(self.C_STATUS, iPx + cMg)
self.blockSignals(True)
for index in SHARED.project.tree.model.allExpanded():
self.setExpanded(index, True)
self.blockSignals(False)
self.restoreExpandedState()
return
def restoreExpandedState(self) -> None:
"""Expand all nodes that were previously expanded."""
if model := self._getModel():
self.blockSignals(True)
for index in model.allExpanded():
self.setExpanded(index, True)
self.blockSignals(False)
return
def setSelectedHandle(self, tHandle: str | None, doScroll: bool = False) -> None:
"""Set a specific handle as the selected item."""
if (model := self._getModel()) and (index := model.indexFromHandle(tHandle)).isValid():
@@ -655,6 +661,87 @@ class GuiProjectTree(QTreeView):
self.projView.selectedItemChanged.emit(tHandle)
return
def newTreeItem(
self, itemType: nwItemType, itemClass: nwItemClass | None = None,
hLevel: int = 1, isNote: bool = False, copyDoc: str | None = None,
) -> None:
"""Add new item to the tree, with a given itemType (and
itemClass if Root), and attach it to the selected handle. Also
make sure the item is added in a place it can be added, and that
other meta data is set correctly to ensure a valid project tree.
"""
if not SHARED.hasProject:
logger.error("No project open")
return
tHandle = None
if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass):
pos = -1
if (node := self._getNode(self.currentIndex())) and (itemRoot := node.item.itemRoot):
if root := SHARED.project.tree.nodes.get(itemRoot):
pos = root.row() + 1
SHARED.project.newRoot(itemClass, pos)
self.restoreExpandedState()
elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
if not ((model := self._getModel()) and (node := model.node(self.currentIndex()))):
SHARED.error(self.tr("Did not find anywhere to add the file or folder!"))
return
if node.item.itemClass == nwItemClass.TRASH:
SHARED.error(self.tr("Cannot add new files or folders to the Trash folder."))
return
# Collect some information about the selected item
sLevel = nwStyles.H_LEVEL.get(node.item.mainHeading, 0)
sIsParent = node.childCount() > 0
# Set default label and determine if new item is to be added
# as child or sibling to the selected item
if itemType == nwItemType.FILE:
if copyDoc and (cItem := SHARED.project.tree[copyDoc]):
newLabel = cItem.itemName
asChild = sIsParent and node.item.isDocumentLayout()
elif isNote:
newLabel = self.tr("New Note")
asChild = sIsParent
elif hLevel == 2:
newLabel = self.tr("New Chapter")
asChild = sIsParent and node.item.isDocumentLayout() and sLevel < 2
elif hLevel == 3:
newLabel = self.tr("New Scene")
asChild = sIsParent and node.item.isDocumentLayout() and sLevel < 3
else:
newLabel = self.tr("New Document")
asChild = sIsParent and node.item.isDocumentLayout()
else:
newLabel = self.tr("New Folder")
asChild = False
pos = -1
sHandle = None
if not (asChild or node.item.isFolderType() or node.item.isRootType()):
pos = node.row() + 1
sHandle = node.item.itemParent
sHandle = sHandle or node.item.itemHandle
newLabel, dlgOk = GuiEditLabel.getLabel(self, text=newLabel)
if dlgOk:
# Add the file or folder
if itemType == nwItemType.FILE:
tHandle = SHARED.project.newFile(newLabel, sHandle, pos)
if tHandle and copyDoc:
SHARED.project.copyFileContent(tHandle, copyDoc)
elif tHandle and hLevel > 0:
SHARED.project.writeNewFile(tHandle, hLevel, not isNote)
else:
tHandle = SHARED.project.newFolder(newLabel, sHandle, pos)
return
##
# Events
##
@@ -736,14 +823,14 @@ class GuiProjectTree(QTreeView):
##
@pyqtSlot(QModelIndex)
def _treeSingleClick(self, index: QModelIndex) -> None:
def _onSingleClick(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:
def _onDoubleClick(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.
"""
@@ -789,109 +876,6 @@ class GuiProjectTree(QTreeView):
# self.revealNewTreeItem(tHandle, wordCount=True)
return
def newTreeItem(self, itemType: nwItemType, itemClass: nwItemClass | None = None,
hLevel: int = 1, isNote: bool = False, copyDoc: str | None = None) -> bool:
"""Add new item to the tree, with a given itemType (and
itemClass if Root), and attach it to the selected handle. Also
make sure the item is added in a place it can be added, and that
other meta data is set correctly to ensure a valid project tree.
"""
# if not SHARED.hasProject:
# logger.error("No project open")
# return False
# nHandle = None
# tHandle = None
# if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass):
# tHandle = SHARED.project.newRoot(itemClass)
# sHandle = self.getSelectedHandle()
# pItem = SHARED.project.tree[sHandle] if sHandle else None
# nHandle = pItem.itemRoot if pItem else None
# elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
# sHandle = self.getSelectedHandle()
# pItem = SHARED.project.tree[sHandle] if sHandle else None
# if sHandle is None or pItem is None:
# SHARED.error(self.tr("Did not find anywhere to add the file or folder!"))
# return False
# # Collect some information about the selected item
# qItem = self._getTreeItem(sHandle)
# sLevel = nwStyles.H_LEVEL.get(pItem.mainHeading, 0)
# sIsParent = False if qItem is None else qItem.childCount() > 0
# if SHARED.project.tree.isTrash(sHandle):
# SHARED.error(self.tr("Cannot add new files or folders to the Trash folder."))
# return False
# # Set default label and determine if new item is to be added
# # as child or sibling to the selected item
# if itemType == nwItemType.FILE:
# if copyDoc and (cItem := SHARED.project.tree[copyDoc]):
# newLabel = cItem.itemName
# asChild = sIsParent and pItem.isDocumentLayout()
# elif isNote:
# newLabel = self.tr("New Note")
# asChild = sIsParent
# elif hLevel == 2:
# newLabel = self.tr("New Chapter")
# asChild = sIsParent and pItem.isDocumentLayout() and sLevel < 2
# elif hLevel == 3:
# newLabel = self.tr("New Scene")
# asChild = sIsParent and pItem.isDocumentLayout() and sLevel < 3
# else:
# newLabel = self.tr("New Document")
# asChild = sIsParent and pItem.isDocumentLayout()
# else:
# newLabel = self.tr("New Folder")
# asChild = False
# if not (asChild or pItem.isFolderType() or pItem.isRootType()):
# # Move to the parent item so that the new item is added
# # as a sibling instead
# nHandle = sHandle
# sHandle = pItem.itemParent
# if sHandle is None:
# # Bug: We have a condition that is unhandled
# logger.error("Internal error")
# return False
# # Ask for label
# newLabel, dlgOk = GuiEditLabel.getLabel(self, text=newLabel)
# if not dlgOk:
# logger.info("New item creation cancelled by user")
# return False
# # Add the file or folder
# if itemType == nwItemType.FILE:
# tHandle = SHARED.project.newFile(newLabel, sHandle)
# else:
# tHandle = SHARED.project.newFolder(newLabel, sHandle)
# else:
# logger.error("Failed to add new item")
# return False
# # If there is no handle set, return here. This is a bug.
# if tHandle is None: # pragma: no cover
# logger.error("Internal error")
# return True
# # Handle new file creation
# if itemType == nwItemType.FILE and copyDoc:
# SHARED.project.copyFileContent(tHandle, copyDoc)
# elif itemType == nwItemType.FILE and hLevel > 0:
# SHARED.project.writeNewFile(tHandle, hLevel, not isNote)
# # Add the new item to the project tree
# self.revealNewTreeItem(tHandle, nHandle=nHandle, wordCount=True)
# self.projView.setTreeFocus() # See issue #1376
return True
def revealNewTreeItem(self, tHandle: str | None, nHandle: str | None = None,
wordCount: bool = False) -> bool:
"""Reveal a newly added project item in the project tree."""
+4 -4
View File
@@ -987,25 +987,25 @@ def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mockRnd)
# Try to open a file with nothings selected
projTree.clearSelection()
projTree._treeDoubleClick(QTreeWidgetItem(), 0)
projTree._onDoubleClick(QTreeWidgetItem(), 0)
assert nwGUI.docEditor.docHandle is None
# When the item cannot be found
projTree._getTreeItem(C.hTitlePage).setSelected(True) # type: ignore
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.tree.NWTree.__getitem__", lambda *a: None)
projTree._treeDoubleClick(QTreeWidgetItem(), 0)
projTree._onDoubleClick(QTreeWidgetItem(), 0)
assert nwGUI.docEditor.docHandle is None
# Successfully open a file
projTree._treeDoubleClick(projTree._getTreeItem(C.hTitlePage), 0)
projTree._onDoubleClick(projTree._getTreeItem(C.hTitlePage), 0)
assert nwGUI.docEditor.docHandle == C.hTitlePage
projTree._getTreeItem(C.hTitlePage).setSelected(False) # type: ignore
# A non-file item should be expanded instead
projTree._getTreeItem(C.hNovelRoot).setExpanded(False) # type: ignore
projTree._getTreeItem(C.hNovelRoot).setSelected(True) # type: ignore
projTree._treeDoubleClick(projTree._getTreeItem(C.hNovelRoot), 1)
projTree._onDoubleClick(projTree._getTreeItem(C.hNovelRoot), 1)
assert nwGUI.docEditor.docHandle == C.hTitlePage
assert projTree._getTreeItem(C.hNovelRoot).isExpanded() is True # type: ignore