Re-implement project item duplication
This commit is contained in:
@@ -258,29 +258,27 @@ class DocDuplicator:
|
||||
# Methods
|
||||
##
|
||||
|
||||
def duplicate(self, items: list[str]) -> Iterable[tuple[str, str | None]]:
|
||||
def duplicate(self, items: list[str]) -> list[str]:
|
||||
"""Run through a list of items, duplicate them, and copy the
|
||||
text content if they are documents.
|
||||
"""
|
||||
result = []
|
||||
after = True
|
||||
if items:
|
||||
nHandle = items[0]
|
||||
hMap: dict[str, str | None] = {t: None for t in items}
|
||||
for tHandle in items:
|
||||
newItem = self._project.tree.duplicate(tHandle)
|
||||
if newItem is None:
|
||||
return
|
||||
hMap[tHandle] = newItem.itemHandle
|
||||
if newItem.itemParent in hMap:
|
||||
newItem.setParent(hMap[newItem.itemParent])
|
||||
self._project.tree.updateItemData(newItem.itemHandle)
|
||||
if newItem.isFileType():
|
||||
newDoc = self._project.storage.getDocument(newItem.itemHandle)
|
||||
if newDoc.fileExists():
|
||||
return
|
||||
newDoc.writeDocument(self._project.storage.getDocumentText(tHandle))
|
||||
yield newItem.itemHandle, nHandle
|
||||
nHandle = None
|
||||
return
|
||||
if oldItem := self._project.tree[tHandle]:
|
||||
pHandle = hMap.get(oldItem.itemParent or "") or oldItem.itemParent
|
||||
if newItem := self._project.tree.duplicate(tHandle, pHandle, after):
|
||||
hMap[tHandle] = newItem.itemHandle
|
||||
if newItem.isFileType():
|
||||
self._project.copyFileContent(newItem.itemHandle, tHandle)
|
||||
newItem.notifyToRefresh()
|
||||
result.append(newItem.itemHandle)
|
||||
after = False
|
||||
else:
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
class DocSearch:
|
||||
@@ -509,8 +507,10 @@ class ProjectBuilder:
|
||||
f"%Short: {bfNote}\n\n"
|
||||
)
|
||||
|
||||
# Also add the archive folder
|
||||
# Also add the archive and trash folders
|
||||
project.newRoot(nwItemClass.ARCHIVE)
|
||||
project.tree.trash # Triggers the creation of Trash
|
||||
|
||||
project.saveProject()
|
||||
project.closeProject()
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ class ProjectNode:
|
||||
else:
|
||||
child.item.setParent(None)
|
||||
child.item.setRoot(child.item.itemHandle)
|
||||
child.item.setClassDefaults(child.item.itemClass)
|
||||
return
|
||||
|
||||
|
||||
|
||||
@@ -26,12 +26,10 @@ from __future__ import annotations
|
||||
import json
|
||||
import logging
|
||||
|
||||
from collections.abc import Iterable
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from time import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
@@ -51,9 +49,6 @@ from novelwriter.core.tree import NWTree
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||
from novelwriter.error import logException
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from novelwriter.core.item import NWItem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -209,7 +204,7 @@ class NWProject:
|
||||
text = self._storage.getDocumentText(sHandle)
|
||||
self._storage.getDocument(tHandle).writeDocument(text)
|
||||
sItem.setLayout(tItem.itemLayout)
|
||||
self._index.scanText(tHandle, text)
|
||||
self._index.reIndexHandle(tHandle)
|
||||
|
||||
return True
|
||||
|
||||
@@ -494,47 +489,6 @@ class NWProject:
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def iterProjectItems(self) -> Iterable[NWItem]:
|
||||
"""This function ensures that the item tree loaded is sent to
|
||||
the GUI tree view in such a way that the tree can be built. That
|
||||
is, the parent item must be sent before its child. In principle,
|
||||
a proper XML file will already ensure that, but in the event the
|
||||
order has been altered, or a file is orphaned, this function is
|
||||
capable of handling it.
|
||||
"""
|
||||
sentItems = set()
|
||||
iterItems = self._tree.handles()
|
||||
n = 0
|
||||
nMax = min(len(iterItems), 10000)
|
||||
while n < nMax:
|
||||
tHandle = iterItems[n]
|
||||
tItem = self._tree[tHandle]
|
||||
n += 1
|
||||
if tItem is None:
|
||||
# Technically a bug
|
||||
continue
|
||||
elif tItem.itemParent is None:
|
||||
# Item is a root, or already been identified as orphaned
|
||||
sentItems.add(tHandle)
|
||||
yield tItem
|
||||
elif tItem.itemParent in sentItems:
|
||||
# Item's parent has been sent, so all is fine
|
||||
sentItems.add(tHandle)
|
||||
yield tItem
|
||||
elif tItem.itemParent in iterItems:
|
||||
# Item's parent exists, but hasn't been sent yet, so add
|
||||
# it again to the end, but make sure this doesn't get
|
||||
# out hand, so we cap at 10000 items
|
||||
logger.warning("Item '%s' found before its parent", tHandle)
|
||||
iterItems.append(tHandle)
|
||||
nMax = min(len(iterItems), 10000)
|
||||
else:
|
||||
# Item is orphaned
|
||||
logger.error("Item '%s' has no parent in current tree", tHandle)
|
||||
tItem.setParent(None)
|
||||
yield tItem
|
||||
return
|
||||
|
||||
def updateWordCounts(self) -> None:
|
||||
"""Update the total word count values."""
|
||||
novel, notes = self._tree.sumWords()
|
||||
|
||||
@@ -115,10 +115,6 @@ class NWTree:
|
||||
del oldModel
|
||||
return
|
||||
|
||||
def handles(self) -> list[str]:
|
||||
"""Returns a copy of the list of all the active handles."""
|
||||
return list(self._items.keys())
|
||||
|
||||
def add(self, item: NWItem, pos: int = -1) -> bool:
|
||||
"""Add a project item into the project tree."""
|
||||
if pHandle := item.itemParent:
|
||||
@@ -128,6 +124,7 @@ class NWTree:
|
||||
self._model.insertChild(node, index, pos)
|
||||
self._nodes[item.itemHandle] = node
|
||||
self._items[item.itemHandle] = item
|
||||
self._project.setProjectChanged(True)
|
||||
else:
|
||||
logger.error("Could not locate parent of '%s'", item.itemHandle)
|
||||
return False
|
||||
@@ -136,6 +133,7 @@ class NWTree:
|
||||
self._model.insertChild(node, QModelIndex(), pos)
|
||||
self._nodes[item.itemHandle] = node
|
||||
self._items[item.itemHandle] = item
|
||||
self._project.setProjectChanged(True)
|
||||
else:
|
||||
logger.error("Invalid project item '%s'", item.itemHandle)
|
||||
return False
|
||||
@@ -181,18 +179,17 @@ class NWTree:
|
||||
nwItem.setType(itemType)
|
||||
nwItem.setClass(itemClass)
|
||||
if self.add(nwItem, pos):
|
||||
self._project.setProjectChanged(True)
|
||||
return tHandle
|
||||
return None
|
||||
|
||||
def duplicate(self, sHandle: str) -> NWItem | None:
|
||||
def duplicate(self, sHandle: str, pHandle: str | None, putAfter: bool) -> 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
|
||||
if sNode := self._nodes.get(sHandle):
|
||||
nItem = NWItem.duplicate(sNode.item, self._makeHandle())
|
||||
nItem.setParent(pHandle)
|
||||
if self.add(nItem, (sNode.row() + 1) if putAfter else -1):
|
||||
logger.info("Duplicated item '%s' -> '%s'", sHandle, nItem.itemHandle)
|
||||
return nItem
|
||||
return None
|
||||
|
||||
def pack(self) -> list[dict]:
|
||||
@@ -416,22 +413,6 @@ class NWTree:
|
||||
yield node.item.itemHandle, node.item
|
||||
return
|
||||
|
||||
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
|
||||
return False
|
||||
|
||||
def findRoot(self, itemClass: nwItemClass | None) -> str | None:
|
||||
"""Find the first root item for a given class."""
|
||||
for node in self._model.root.children:
|
||||
|
||||
Reference in New Issue
Block a user