Add item duplication functionality to project tree
This commit is contained in:
+53
-13
@@ -24,11 +24,13 @@ General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from time import time
|
from time import time
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtGui import QPalette
|
from PyQt5.QtGui import QPalette
|
||||||
from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
|
||||||
@@ -42,12 +44,15 @@ from novelwriter import CONFIG
|
|||||||
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwWidget
|
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwWidget
|
||||||
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
||||||
from novelwriter.core.item import NWItem
|
from novelwriter.core.item import NWItem
|
||||||
from novelwriter.core.coretools import DocMerger, DocSplitter
|
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
|
||||||
from novelwriter.dialogs.docmerge import GuiDocMerge
|
from novelwriter.dialogs.docmerge import GuiDocMerge
|
||||||
from novelwriter.dialogs.docsplit import GuiDocSplit
|
from novelwriter.dialogs.docsplit import GuiDocSplit
|
||||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||||
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
from novelwriter.dialogs.projsettings import GuiProjectSettings
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from novelwriter.guimain import GuiMain
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -69,7 +74,7 @@ class GuiProjectView(QWidget):
|
|||||||
# Requests for the main GUI
|
# Requests for the main GUI
|
||||||
projectSettingsRequest = pyqtSignal(int)
|
projectSettingsRequest = pyqtSignal(int)
|
||||||
|
|
||||||
def __init__(self, mainGui):
|
def __init__(self, mainGui: GuiMain):
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=mainGui)
|
||||||
|
|
||||||
self.mainGui = mainGui
|
self.mainGui = mainGui
|
||||||
@@ -452,7 +457,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
D_HANDLE = Qt.ItemDataRole.UserRole
|
D_HANDLE = Qt.ItemDataRole.UserRole
|
||||||
D_WORDS = Qt.ItemDataRole.UserRole + 1
|
D_WORDS = Qt.ItemDataRole.UserRole + 1
|
||||||
|
|
||||||
def __init__(self, projView):
|
def __init__(self, projView: GuiProjectView):
|
||||||
super().__init__(parent=projView)
|
super().__init__(parent=projView)
|
||||||
|
|
||||||
logger.debug("Create: GuiProjectTree")
|
logger.debug("Create: GuiProjectTree")
|
||||||
@@ -1308,8 +1313,8 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
aSplit1 = mTrans.addAction(self.tr("Split Document by Headers"))
|
aSplit1 = mTrans.addAction(self.tr("Split Document by Headers"))
|
||||||
aSplit1.triggered.connect(lambda: self._splitDocument(tHandle))
|
aSplit1.triggered.connect(lambda: self._splitDocument(tHandle))
|
||||||
|
|
||||||
# Expand/Collapse/Delete
|
# Expand/Collapse/Delete/Duplicate
|
||||||
# ======================
|
# ================================
|
||||||
|
|
||||||
ctxMenu.addSeparator()
|
ctxMenu.addSeparator()
|
||||||
|
|
||||||
@@ -1318,6 +1323,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
aExpand.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, True))
|
aExpand.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, True))
|
||||||
aCollapse = ctxMenu.addAction(self.tr("Collapse All"))
|
aCollapse = ctxMenu.addAction(self.tr("Collapse All"))
|
||||||
aCollapse.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, False))
|
aCollapse.triggered.connect(lambda: self.setExpandedFromHandle(tHandle, False))
|
||||||
|
aDuplicate = ctxMenu.addAction(self.tr("Duplicate from Here"))
|
||||||
|
aDuplicate.triggered.connect(lambda: self._duplicateFromHandle(tHandle))
|
||||||
|
elif isFile:
|
||||||
|
aDuplicate = ctxMenu.addAction(self.tr("Duplicate Document"))
|
||||||
|
aDuplicate.triggered.connect(lambda: self._duplicateFromHandle(tHandle))
|
||||||
|
|
||||||
if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
if tItem.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
|
||||||
aDelete = ctxMenu.addAction(self.tr("Delete Permanently"))
|
aDelete = ctxMenu.addAction(self.tr("Delete Permanently"))
|
||||||
@@ -1532,8 +1542,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _mergeDocuments(self, tHandle, newFile):
|
def _mergeDocuments(self, tHandle, newFile):
|
||||||
"""Merge an item's child documents into a single document.
|
"""Merge an item's child documents into a single document."""
|
||||||
"""
|
|
||||||
logger.info("Request to merge items under handle '%s'", tHandle)
|
logger.info("Request to merge items under handle '%s'", tHandle)
|
||||||
itemList = self.getTreeFromHandle(tHandle)
|
itemList = self.getTreeFromHandle(tHandle)
|
||||||
|
|
||||||
@@ -1608,8 +1617,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _splitDocument(self, tHandle):
|
def _splitDocument(self, tHandle):
|
||||||
"""Split a document into multiple documents.
|
"""Split a document into multiple documents."""
|
||||||
"""
|
|
||||||
logger.info("Request to split items with handle '%s'", tHandle)
|
logger.info("Request to split items with handle '%s'", tHandle)
|
||||||
|
|
||||||
tItem = self.theProject.tree[tHandle]
|
tItem = self.theProject.tree[tHandle]
|
||||||
@@ -1660,7 +1668,39 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _scanChildren(self, theList, tItem, tIndex):
|
def _duplicateFromHandle(self, tHandle: str) -> bool:
|
||||||
|
"""Duplicate the item hierarchy from a given item."""
|
||||||
|
itemTree = self.getTreeFromHandle(tHandle)
|
||||||
|
nItems = len(itemTree)
|
||||||
|
if nItems == 0:
|
||||||
|
return False
|
||||||
|
elif nItems == 1:
|
||||||
|
qTitle = self.tr("Duplicate Document")
|
||||||
|
qText = self.tr("Do you want to duplicate this document?")
|
||||||
|
else:
|
||||||
|
qTitle = self.tr("Duplicate from Here")
|
||||||
|
qText = self.tr("Do you want to duplicate this item and all child items?")
|
||||||
|
|
||||||
|
if not self.mainGui.askQuestion(qTitle, qText):
|
||||||
|
return False
|
||||||
|
|
||||||
|
docDup = DocDuplicator(self.theProject)
|
||||||
|
dupCount = 0
|
||||||
|
for dHandle, nHandle in docDup.duplicate(itemTree):
|
||||||
|
print(dHandle, nHandle)
|
||||||
|
self.theProject.index.reIndexHandle(dHandle)
|
||||||
|
self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
|
||||||
|
self._alertTreeChange(dHandle, flush=False)
|
||||||
|
dupCount += 1
|
||||||
|
|
||||||
|
if dupCount != nItems:
|
||||||
|
self.mainGui.makeAlert(self.tr("Could not duplicate all items."), nwAlert.ERROR)
|
||||||
|
|
||||||
|
self.saveTreeOrder()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _scanChildren(self, itemList: list, tItem: QTreeWidgetItem, tIndex: int):
|
||||||
"""This is a recursive function returning all items in a tree
|
"""This is a recursive function returning all items in a tree
|
||||||
starting at a given QTreeWidgetItem.
|
starting at a given QTreeWidgetItem.
|
||||||
"""
|
"""
|
||||||
@@ -1673,11 +1713,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
nwItem.setExpanded(tItem.isExpanded() and cCount > 0)
|
nwItem.setExpanded(tItem.isExpanded() and cCount > 0)
|
||||||
nwItem.setOrder(tIndex)
|
nwItem.setOrder(tIndex)
|
||||||
|
|
||||||
theList.append(tHandle)
|
itemList.append(tHandle)
|
||||||
for i in range(cCount):
|
for i in range(cCount):
|
||||||
self._scanChildren(theList, tItem.child(i), i)
|
self._scanChildren(itemList, tItem.child(i), i)
|
||||||
|
|
||||||
return theList
|
return itemList
|
||||||
|
|
||||||
def _addTreeItem(self, nwItem, nHandle=None):
|
def _addTreeItem(self, nwItem, nHandle=None):
|
||||||
"""Create a QTreeWidgetItem from an NWItem and add it to the
|
"""Create a QTreeWidgetItem from an NWItem and add it to the
|
||||||
|
|||||||
Reference in New Issue
Block a user