Implement nre doc merge functionality in tree
This commit is contained in:
@@ -25,60 +25,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from novelwriter.core.document import NWDoc
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
class DocMerger:
|
||||||
|
|
||||||
@@ -86,17 +36,84 @@ class DocMerger:
|
|||||||
|
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
|
|
||||||
|
self._error = ""
|
||||||
self._targetDoc = None
|
self._targetDoc = None
|
||||||
|
self._targetText = []
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Methods
|
||||||
|
##
|
||||||
|
|
||||||
|
def getError(self):
|
||||||
|
"""Return any collected errors.
|
||||||
|
"""
|
||||||
|
return self._error
|
||||||
|
|
||||||
def setTargetDoc(self, tHandle):
|
def setTargetDoc(self, tHandle):
|
||||||
|
"""Set the target document for the merging. Calling this
|
||||||
|
function resets the class.
|
||||||
|
"""
|
||||||
|
self._targetDoc = tHandle
|
||||||
|
self._targetText = []
|
||||||
return
|
return
|
||||||
|
|
||||||
def createNewDoc(self, docLabel, pHandle, itemLayout):
|
def newTargetDoc(self, srcHandle, docLabel):
|
||||||
return
|
"""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):
|
newHandle = self.theProject.newFile(docLabel, srcItem.itemParent)
|
||||||
return
|
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
|
# END Class DocMerger
|
||||||
|
|||||||
@@ -292,16 +292,16 @@ class NWItem():
|
|||||||
|
|
||||||
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
|
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
|
"""Return the relevant importance or status label and icon for
|
||||||
the current item based on its class.
|
the current item based on its class.
|
||||||
"""
|
"""
|
||||||
if self.isNovelLike():
|
if self.isNovelLike():
|
||||||
stName = self.theProject.statusItems.name(self._status)
|
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:
|
else:
|
||||||
stName = self.theProject.importItems.name(self._import)
|
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
|
return stName, stIcon
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -88,6 +88,9 @@ class GuiDocMerge(QDialog):
|
|||||||
self.buttonBox.accepted.connect(self.accept)
|
self.buttonBox.accepted.connect(self.accept)
|
||||||
self.buttonBox.rejected.connect(self.reject)
|
self.buttonBox.rejected.connect(self.reject)
|
||||||
|
|
||||||
|
self.resetButton = self.buttonBox.addButton(QDialogButtonBox.Reset)
|
||||||
|
self.resetButton.clicked.connect(self._resetList)
|
||||||
|
|
||||||
# Assemble
|
# Assemble
|
||||||
self.outerBox = QVBoxLayout()
|
self.outerBox = QVBoxLayout()
|
||||||
self.outerBox.setSpacing(0)
|
self.outerBox.setSpacing(0)
|
||||||
@@ -122,6 +125,19 @@ class GuiDocMerge(QDialog):
|
|||||||
|
|
||||||
return self._data
|
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
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|||||||
+64
-11
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QVBoxLayout, QWidget
|
QVBoxLayout, QWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.core import NWDoc
|
from novelwriter.core import NWDoc, DocMerger
|
||||||
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||||
from novelwriter.dialogs import GuiDocMerge, GuiEditLabel
|
from novelwriter.dialogs import GuiDocMerge, GuiEditLabel
|
||||||
from novelwriter.constants import nwHeaders, trConst, nwLabels
|
from novelwriter.constants import nwHeaders, trConst, nwLabels
|
||||||
@@ -770,10 +770,14 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
else:
|
else:
|
||||||
# The item is not already in the trash folder, so we
|
# The item is not already in the trash folder, so we
|
||||||
# move it there.
|
# move it there.
|
||||||
msgYes = self.mainGui.askQuestion(
|
if not alreadyAsked:
|
||||||
self.tr("Delete"),
|
msgYes = self.mainGui.askQuestion(
|
||||||
self.tr("Move '{0}' to Trash?").format(nwItemS.itemName),
|
self.tr("Delete"),
|
||||||
)
|
self.tr("Move '{0}' to Trash?").format(nwItemS.itemName),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
msgYes = True
|
||||||
|
|
||||||
if msgYes:
|
if msgYes:
|
||||||
logger.debug("Moving item '%s' to trash", tHandle)
|
logger.debug("Moving item '%s' to trash", tHandle)
|
||||||
|
|
||||||
@@ -1114,13 +1118,17 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if hasChild:
|
if hasChild:
|
||||||
if isFile:
|
if isFile:
|
||||||
mTrans.addAction(
|
mTrans.addAction(
|
||||||
self.tr("Merge Child Documents"),
|
self.tr("Merge Child Items into Self"),
|
||||||
lambda: self._mergeDocuments(tHandle, isFile)
|
lambda: self._mergeDocuments(tHandle, False)
|
||||||
|
)
|
||||||
|
mTrans.addAction(
|
||||||
|
self.tr("Merge Child Items into New"),
|
||||||
|
lambda: self._mergeDocuments(tHandle, True)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
mTrans.addAction(
|
mTrans.addAction(
|
||||||
self.tr("Combine Documents in Folder"),
|
self.tr("Combine Documents in Folder"),
|
||||||
lambda: self._mergeDocuments(tHandle, isFile)
|
lambda: self._mergeDocuments(tHandle, True)
|
||||||
)
|
)
|
||||||
|
|
||||||
if isFile:
|
if isFile:
|
||||||
@@ -1377,18 +1385,63 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
logger.info("Folder conversion cancelled")
|
logger.info("Folder conversion cancelled")
|
||||||
return
|
return
|
||||||
|
|
||||||
def _mergeDocuments(self, tHandle, isFile):
|
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)
|
||||||
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 = GuiDocMerge(self.mainGui, tHandle, itemList)
|
||||||
dlgMerge.exec_()
|
dlgMerge.exec_()
|
||||||
|
|
||||||
if dlgMerge.result() == QDialog.Accepted:
|
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
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user