diff --git a/novelwriter/core/doctools.py b/novelwriter/core/doctools.py index b85d1c19..75a4c3b7 100644 --- a/novelwriter/core/doctools.py +++ b/novelwriter/core/doctools.py @@ -25,60 +25,10 @@ along with this program. If not, see . import logging +from novelwriter.core.document import NWDoc + logger = logging.getLogger(__name__) -# logger.verbose("GuiDocMerge merge button clicked") - -# finalOrder = [] -# for i in range(self.listBox.count()): -# finalOrder.append(self.listBox.item(i).data(Qt.UserRole)) - -# if len(finalOrder) == 0: -# self.mainGui.makeAlert(self.tr( -# "No source documents found. Nothing to do." -# ), nwAlert.ERROR) -# return False - -# theText = "" -# for tHandle in finalOrder: -# inDoc = NWDoc(self.theProject, tHandle) -# docText = inDoc.readDocument() -# docErr = inDoc.getError() -# if docText is None and docErr: -# self.mainGui.makeAlert([ -# self.tr("Failed to open document file."), docErr -# ], nwAlert.ERROR) -# if docText: -# theText += docText.rstrip("\n")+"\n\n" - -# if self.sourceItem is None: -# self.mainGui.makeAlert(self.tr( -# "No source folder selected. Nothing to do." -# ), nwAlert.ERROR) -# return False - -# srcItem = self.theProject.tree[self.sourceItem] -# if srcItem is None: -# self.mainGui.makeAlert(self.tr("Internal error."), nwAlert.ERROR) -# return False - -# nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemParent) -# newItem = self.theProject.tree[nHandle] -# newItem.setStatus(srcItem.itemStatus) -# newItem.setImport(srcItem.itemImport) - -# outDoc = NWDoc(self.theProject, nHandle) -# if not outDoc.writeDocument(theText): -# self.mainGui.makeAlert([ -# self.tr("Could not save document."), outDoc.getError() -# ], nwAlert.ERROR) -# return False - -# self.mainGui.projView.revealNewTreeItem(nHandle) -# self.mainGui.openDocument(nHandle, doScroll=True) - -# self._doClose() - class DocMerger: @@ -86,17 +36,84 @@ class DocMerger: self.theProject = theProject + self._error = "" self._targetDoc = None + self._targetText = [] return + ## + # Methods + ## + + def getError(self): + """Return any collected errors. + """ + return self._error + def setTargetDoc(self, tHandle): + """Set the target document for the merging. Calling this + function resets the class. + """ + self._targetDoc = tHandle + self._targetText = [] return - def createNewDoc(self, docLabel, pHandle, itemLayout): - return + def newTargetDoc(self, srcHandle, docLabel): + """Create a barnd new target document based on a source handle + and a new doc label. Calling this function resets the class. + """ + srcItem = self.theProject.tree[srcHandle] + if srcItem is None: + return None - def appendDoc(self, tHandle): - return + newHandle = self.theProject.newFile(docLabel, srcItem.itemParent) + newItem = self.theProject.tree[newHandle] + newItem.setLayout(srcItem.itemLayout) + newItem.setStatus(srcItem.itemStatus) + newItem.setImport(srcItem.itemImport) + + self._targetDoc = newHandle + self._targetText = [] + + return newHandle + + def appendText(self, srcHandle, addComment, cmtPrefix): + """Append text from an existing document to the text buffer. + """ + srcItem = self.theProject.tree[srcHandle] + if srcItem is None: + return False + + inDoc = NWDoc(self.theProject, srcHandle) + docText = (inDoc.readDocument() or "").rstrip("\n") + + if addComment: + docInfo = srcItem.describeMe("H0") + docSt, _ = srcItem.getImportStatus(incIcon=False) + cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n" + docText = cmtLine + docText + + self._targetText.append(docText) + + return True + + def writeTargetDoc(self): + """Write the accumulated text into the designated target + document, appending any existing text. + """ + if self._targetDoc is None: + return False + + outDoc = NWDoc(self.theProject, self._targetDoc) + docText = (outDoc.readDocument() or "").rstrip("\n") + if docText: + self._targetText.insert(0, docText) + + status = outDoc.writeDocument("\n\n".join(self._targetText)) + if not status: + self._error = outDoc.getError() + + return status # END Class DocMerger diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index 95418079..3f463a1b 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -292,16 +292,16 @@ class NWItem(): return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, "")) - def getImportStatus(self): + def getImportStatus(self, incIcon=True): """Return the relevant importance or status label and icon for the current item based on its class. """ if self.isNovelLike(): stName = self.theProject.statusItems.name(self._status) - stIcon = self.theProject.statusItems.icon(self._status) + stIcon = self.theProject.statusItems.icon(self._status) if incIcon else None else: stName = self.theProject.importItems.name(self._import) - stIcon = self.theProject.importItems.icon(self._import) + stIcon = self.theProject.importItems.icon(self._import) if incIcon else None return stName, stIcon ## diff --git a/novelwriter/dialogs/docmerge.py b/novelwriter/dialogs/docmerge.py index 2d248200..89149ab8 100644 --- a/novelwriter/dialogs/docmerge.py +++ b/novelwriter/dialogs/docmerge.py @@ -88,6 +88,9 @@ class GuiDocMerge(QDialog): self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) + self.resetButton = self.buttonBox.addButton(QDialogButtonBox.Reset) + self.resetButton.clicked.connect(self._resetList) + # Assemble self.outerBox = QVBoxLayout() self.outerBox.setSpacing(0) @@ -122,6 +125,19 @@ class GuiDocMerge(QDialog): return self._data + ## + # Slots + ## + + def _resetList(self): + """Reset the content of the list box to its original state. + """ + logger.debug("Resetting list box content") + sHandle = self._data.get("sHandle", None) + itemList = self._data.get("origItems", []) + self._loadContent(sHandle, itemList) + return + ## # Internal Functions ## diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index e841375a..a32e0b48 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -39,7 +39,7 @@ from PyQt5.QtWidgets import ( QVBoxLayout, QWidget ) -from novelwriter.core import NWDoc +from novelwriter.core import NWDoc, DocMerger from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert from novelwriter.dialogs import GuiDocMerge, GuiEditLabel from novelwriter.constants import nwHeaders, trConst, nwLabels @@ -770,10 +770,14 @@ class GuiProjectTree(QTreeWidget): else: # The item is not already in the trash folder, so we # move it there. - msgYes = self.mainGui.askQuestion( - self.tr("Delete"), - self.tr("Move '{0}' to Trash?").format(nwItemS.itemName), - ) + if not alreadyAsked: + msgYes = self.mainGui.askQuestion( + self.tr("Delete"), + self.tr("Move '{0}' to Trash?").format(nwItemS.itemName), + ) + else: + msgYes = True + if msgYes: logger.debug("Moving item '%s' to trash", tHandle) @@ -1114,13 +1118,17 @@ class GuiProjectTree(QTreeWidget): if hasChild: if isFile: mTrans.addAction( - self.tr("Merge Child Documents"), - lambda: self._mergeDocuments(tHandle, isFile) + self.tr("Merge Child Items into Self"), + lambda: self._mergeDocuments(tHandle, False) + ) + mTrans.addAction( + self.tr("Merge Child Items into New"), + lambda: self._mergeDocuments(tHandle, True) ) else: mTrans.addAction( self.tr("Combine Documents in Folder"), - lambda: self._mergeDocuments(tHandle, isFile) + lambda: self._mergeDocuments(tHandle, True) ) if isFile: @@ -1377,18 +1385,63 @@ class GuiProjectTree(QTreeWidget): logger.info("Folder conversion cancelled") return - def _mergeDocuments(self, tHandle, isFile): + def _mergeDocuments(self, tHandle, newFile): """Merge an item's child documents into a single document. """ logger.info("Request to merge items under handle '%s'", tHandle) itemList = self.getTreeFromHandle(tHandle) - itemList.remove(tHandle) + + tItem = self.theProject.tree[tHandle] + if tItem is None: + return + + if not newFile: + itemList.remove(tHandle) dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList) dlgMerge.exec_() if dlgMerge.result() == QDialog.Accepted: - print(dlgMerge.getData()) + # Save the open document first, in case it's part of this + self.mainGui.saveDocument() + + # Set up the merge job + mrgData = dlgMerge.getData() + docMerger = DocMerger(self.theProject) + mLabel = self.tr("Merged") + + if newFile: + docLabel = f"[{mLabel}] {tItem.itemName}" + mHandle = docMerger.newTargetDoc(tHandle, docLabel) + elif tItem.isFileType(): + docMerger.setTargetDoc(tHandle) + mHandle = tHandle + else: + return + + for sHandle in mrgData.get("finalItems", []): + docMerger.appendText(sHandle, True, mLabel) + + if not docMerger.writeTargetDoc(): + self.mainGui.makeAlert([ + self.tr("Could not save document."), docMerger.getError() + ], nwAlert.ERROR) + return + + if newFile: + self.mainGui.projView.revealNewTreeItem(mHandle, tHandle) + + self.theProject.index.reIndexHandle(mHandle) + self.mainGui.openDocument(mHandle, doScroll=True) + + if mrgData.get("moveToTrash", False): + for sHandle in reversed(mrgData.get("finalItems", [])): + trItem = self._getTreeItem(sHandle) + if isinstance(trItem, QTreeWidgetItem) and trItem.childCount() == 0: + self.deleteItem(sHandle, alreadyAsked=True, bulkAction=True) + self._alertTreeChange(sHandle, flush=False) + + self._alertTreeChange(mHandle, flush=True) return