Added document split dialog and associated code
This commit is contained in:
@@ -9,6 +9,7 @@ from nw.gui.theme import GuiTheme
|
||||
# Dialogs
|
||||
from nw.gui.dialogs.configeditor import GuiConfigEditor
|
||||
from nw.gui.dialogs.docmerge import GuiDocMerge
|
||||
from nw.gui.dialogs.docsplit import GuiDocSplit
|
||||
from nw.gui.dialogs.export import GuiExport
|
||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
||||
@@ -35,6 +36,7 @@ __all__ = [
|
||||
"GuiTheme",
|
||||
"GuiConfigEditor",
|
||||
"GuiDocMerge",
|
||||
"GuiDocSplit",
|
||||
"GuiExport",
|
||||
"GuiItemEditor",
|
||||
"GuiProjectEditor",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from nw.gui.dialogs.configeditor import GuiConfigEditor
|
||||
from nw.gui.dialogs.docmerge import GuiDocMerge
|
||||
from nw.gui.dialogs.docsplit import GuiDocSplit
|
||||
from nw.gui.dialogs.export import GuiExport
|
||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
||||
@@ -11,6 +12,7 @@ from nw.gui.dialogs.timelineview import GuiTimeLineView
|
||||
__all__ = [
|
||||
"GuiConfigEditor",
|
||||
"GuiDocMerge",
|
||||
"GuiDocSplit",
|
||||
"GuiExport",
|
||||
"GuiItemEditor",
|
||||
"GuiProjectEditor",
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter GUI Doc Split
|
||||
|
||||
novelWriter – GUI Doc Split
|
||||
=============================
|
||||
Tool for splitting a single document into multiple documents
|
||||
|
||||
File History:
|
||||
Created: 2020-02-01 [0.4.3]
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import nw
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QPushButton, QComboBox,
|
||||
QListWidget, QAbstractItemView, QListWidgetItem
|
||||
)
|
||||
from nw.constants import nwAlert, nwItemType, nwItemClass, nwItemLayout
|
||||
from nw.project import NWDoc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiDocSplit(QDialog):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QDialog.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiDocSplit ...")
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.sourceItem = None
|
||||
|
||||
self.outerBox = QHBoxLayout()
|
||||
self.innerBox = QVBoxLayout()
|
||||
self.setWindowTitle("Split Document")
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
self.guiDeco = self.theParent.theTheme.loadDecoration("split",(64,64))
|
||||
|
||||
self.outerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
|
||||
self.outerBox.addLayout(self.innerBox)
|
||||
|
||||
self.doMergeForm = QGridLayout()
|
||||
self.doMergeForm.setContentsMargins(10,5,0,10)
|
||||
|
||||
self.listBox = QListWidget()
|
||||
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
|
||||
|
||||
self.splitLevel = QComboBox(self)
|
||||
self.splitLevel.addItem("Split on Title (Level 1)", 1)
|
||||
self.splitLevel.addItem("Split on Chapter (Level 2)", 2)
|
||||
self.splitLevel.addItem("Split on Scene (Level 3)", 3)
|
||||
self.splitLevel.addItem("Split on Section (Level 4)", 4)
|
||||
self.splitLevel.setCurrentIndex(2)
|
||||
self.splitLevel.currentIndexChanged.connect(self._populateList)
|
||||
|
||||
self.splitButton = QPushButton("Split")
|
||||
self.splitButton.clicked.connect(self._doSplit)
|
||||
|
||||
self.closeButton = QPushButton("Close")
|
||||
self.closeButton.clicked.connect(self._doClose)
|
||||
|
||||
self.doMergeForm.addWidget(self.listBox, 0, 0, 1, 3)
|
||||
self.doMergeForm.addWidget(self.splitLevel, 1, 0, 1, 2)
|
||||
self.doMergeForm.addWidget(self.splitButton, 2, 1)
|
||||
self.doMergeForm.addWidget(self.closeButton, 2, 2)
|
||||
|
||||
self.innerBox.addLayout(self.doMergeForm)
|
||||
|
||||
self.rejected.connect(self._doClose)
|
||||
self.show()
|
||||
|
||||
self._populateList()
|
||||
|
||||
logger.debug("GuiDocSplit initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Buttons
|
||||
##
|
||||
|
||||
def _doSplit(self):
|
||||
"""Perform the split of the file, create a new folder in the
|
||||
same parent folder, and multiple files depending on split level
|
||||
settings. The old file is not removed in the merge process, and
|
||||
must be deleted manually.
|
||||
"""
|
||||
|
||||
logger.verbose("GuiDocSplit split button clicked")
|
||||
|
||||
if self.sourceItem is None:
|
||||
self.theParent.makeAlert((
|
||||
"No source document selected. Nothing to do."
|
||||
), nwAlert.ERROR)
|
||||
return
|
||||
|
||||
srcItem = self.theProject.getItem(self.sourceItem)
|
||||
if srcItem is None:
|
||||
self.theParent.makeAlert((
|
||||
"Could not parse source document."
|
||||
), nwAlert.ERROR)
|
||||
return
|
||||
|
||||
theDoc = NWDoc(self.theProject, self.theParent)
|
||||
theText = theDoc.openDocument(self.sourceItem, False)
|
||||
theLines = theText.splitlines()
|
||||
nLines = len(theLines)
|
||||
theLines.insert(0, "%Split Doc")
|
||||
|
||||
finalOrder = []
|
||||
for i in range(self.listBox.count()):
|
||||
listItem = self.listBox.item(i)
|
||||
wTitle = listItem.text()
|
||||
lineNo = listItem.data(Qt.UserRole)
|
||||
finalOrder.append([wTitle, lineNo, nLines])
|
||||
if i > 0:
|
||||
finalOrder[i-1][2] = lineNo
|
||||
|
||||
if len(finalOrder) == 0:
|
||||
self.theParent.makeAlert((
|
||||
"No headers found. Nothing to do."
|
||||
), nwAlert.ERROR)
|
||||
return
|
||||
|
||||
fHandle = self.theProject.newFolder(srcItem.itemName, srcItem.itemClass, srcItem.parHandle)
|
||||
self.theParent.treeView.revealTreeItem(fHandle)
|
||||
|
||||
for wTitle, iStart, iEnd in finalOrder:
|
||||
|
||||
itemLayout = nwItemLayout.NOTE
|
||||
if srcItem.itemClass == nwItemClass.NOVEL:
|
||||
if wTitle.startswith("# "):
|
||||
itemLayout = nwItemLayout.PARTITION
|
||||
elif wTitle.startswith("## "):
|
||||
itemLayout = nwItemLayout.CHAPTER
|
||||
elif wTitle.startswith("### "):
|
||||
itemLayout = nwItemLayout.SCENE
|
||||
elif wTitle.startswith("#### "):
|
||||
itemLayout = nwItemLayout.PAGE
|
||||
|
||||
wTitle = wTitle.lstrip("#")
|
||||
wTitle = wTitle.strip()
|
||||
print(wTitle, iStart, iEnd)
|
||||
|
||||
nHandle = self.theProject.newFile(wTitle, srcItem.itemClass, fHandle)
|
||||
newItem = self.theProject.getItem(nHandle)
|
||||
newItem.setLayout(itemLayout)
|
||||
|
||||
theText = "\n".join(theLines[iStart:iEnd])
|
||||
theDoc.openDocument(nHandle, False)
|
||||
theDoc.saveDocument(theText)
|
||||
theDoc.clearDocument()
|
||||
self.theParent.treeView.revealTreeItem(nHandle)
|
||||
|
||||
# theDoc = NWDoc(self.theProject, self.theParent)
|
||||
# theText = ""
|
||||
# for tHandle in finalOrder:
|
||||
# theText += theDoc.openDocument(tHandle, False).rstrip()
|
||||
# theText += "\n\n"
|
||||
|
||||
# if self.sourceItem is None:
|
||||
# self.theParent.makeAlert((
|
||||
# "Cannot parse source item."
|
||||
# ), nwAlert.ERROR)
|
||||
# return
|
||||
|
||||
# srcItem = self.theProject.getItem(self.sourceItem)
|
||||
# nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.parHandle)
|
||||
# self.theParent.treeView.revealTreeItem(nHandle)
|
||||
# theDoc.openDocument(nHandle, False)
|
||||
# theDoc.saveDocument(theText)
|
||||
# self.theParent.openDocument(nHandle)
|
||||
|
||||
self.close()
|
||||
|
||||
return
|
||||
|
||||
def _doClose(self):
|
||||
"""Close the dialog window without doing anything.
|
||||
"""
|
||||
logger.verbose("GuiDocSplit close button clicked")
|
||||
self.close()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _populateList(self):
|
||||
"""Get the item selected in the tree, check that it is a folder,
|
||||
and try to find all files associated with it. The valid files
|
||||
are then added to the list view in order. The list itself can be
|
||||
reordered by the user.
|
||||
"""
|
||||
|
||||
if self.sourceItem is None:
|
||||
self.sourceItem = self.theParent.treeView.getSelectedHandle()
|
||||
|
||||
if self.sourceItem is None:
|
||||
return
|
||||
|
||||
nwItem = self.theProject.getItem(self.sourceItem)
|
||||
if nwItem is None:
|
||||
return
|
||||
if nwItem.itemType is not nwItemType.FILE:
|
||||
self.theParent.makeAlert((
|
||||
"Element selected in the project tree must be a file."
|
||||
), nwAlert.ERROR)
|
||||
return
|
||||
|
||||
self.listBox.clear()
|
||||
theDoc = NWDoc(self.theProject, self.theParent)
|
||||
theText = theDoc.openDocument(self.sourceItem, False)
|
||||
|
||||
spLevel = self.splitLevel.currentData()
|
||||
logger.debug("Scanning document %s for headings level <= %d" % (self.sourceItem, spLevel))
|
||||
|
||||
lineNo = 0
|
||||
for aLine in theText.splitlines():
|
||||
|
||||
lineNo += 1
|
||||
onLine = 0
|
||||
|
||||
if aLine.startswith("# ") and spLevel >= 1:
|
||||
onLine = lineNo
|
||||
elif aLine.startswith("## ") and spLevel >= 2:
|
||||
onLine = lineNo
|
||||
elif aLine.startswith("### ") and spLevel >= 3:
|
||||
onLine = lineNo
|
||||
elif aLine.startswith("#### ") and spLevel >= 4:
|
||||
onLine = lineNo
|
||||
|
||||
if onLine > 0:
|
||||
newItem = QListWidgetItem()
|
||||
newItem.setText(aLine.strip())
|
||||
newItem.setData(Qt.UserRole, onLine)
|
||||
self.listBox.addItem(newItem)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiDocSplit
|
||||
+6
-1
@@ -372,10 +372,15 @@ class GuiMainMenu(QMenuBar):
|
||||
# Document > Merge Documents
|
||||
self.aMergeDocs = QAction("Merge Folder to Document", self)
|
||||
self.aMergeDocs.setStatusTip("Merge a folder of documents to a single document")
|
||||
# self.aMergeDocs.setShortcut("Ctrl+Shift+I")
|
||||
self.aMergeDocs.triggered.connect(self.theParent.mergeDocuments)
|
||||
self.docuMenu.addAction(self.aMergeDocs)
|
||||
|
||||
# Document > Split Document
|
||||
self.aSplitDoc = QAction("Split Document to Folder", self)
|
||||
self.aSplitDoc.setStatusTip("Split a document into a folder of multiple documents")
|
||||
self.aSplitDoc.triggered.connect(self.theParent.splitDocument)
|
||||
self.docuMenu.addAction(self.aSplitDoc)
|
||||
|
||||
return
|
||||
|
||||
def _buildViewMenu(self):
|
||||
|
||||
+13
-8
@@ -24,10 +24,10 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from nw.gui import (
|
||||
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor,
|
||||
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar,
|
||||
GuiDocViewDetails, GuiConfigEditor, GuiProjectEditor, GuiExport,
|
||||
GuiItemEditor, GuiTimeLineView, GuiSessionLogView, GuiDocMerge
|
||||
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor, GuiExport,
|
||||
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
|
||||
GuiConfigEditor, GuiProjectEditor, GuiItemEditor, GuiTimeLineView,
|
||||
GuiSessionLogView, GuiDocMerge, GuiDocSplit
|
||||
)
|
||||
from nw.project import NWProject, NWDoc, NWItem, NWIndex, NWBackup
|
||||
from nw.tools import countWords
|
||||
@@ -83,7 +83,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
# Assemble Main Window
|
||||
self.treePane = QFrame()
|
||||
self.treeBox = QVBoxLayout()
|
||||
self.treeBox = QVBoxLayout()
|
||||
self.treeBox.setContentsMargins(0,0,0,0)
|
||||
self.treeBox.addWidget(self.treeView)
|
||||
self.treeBox.addWidget(self.treeMeta)
|
||||
@@ -461,12 +461,17 @@ class GuiMain(QMainWindow):
|
||||
def mergeDocuments(self):
|
||||
"""Merge multiple documents to one single new document.
|
||||
"""
|
||||
|
||||
if self.mainConf.showGUI:
|
||||
dlgMerge = GuiDocMerge(self, self.theProject)
|
||||
if dlgMerge.exec_():
|
||||
pass
|
||||
dlgMerge.exec_()
|
||||
return True
|
||||
|
||||
def splitDocument(self):
|
||||
"""Split a single document into multiple documents.
|
||||
"""
|
||||
if self.mainConf.showGUI:
|
||||
dlgSplit = GuiDocSplit(self, self.theProject)
|
||||
dlgSplit.exec_()
|
||||
return True
|
||||
|
||||
def passDocumentAction(self, theAction):
|
||||
|
||||
@@ -563,6 +563,11 @@ class NWProject():
|
||||
"""This only removes the item from the order list, but not from
|
||||
the project tree.
|
||||
"""
|
||||
if tHandle not in self.treeOrder:
|
||||
logger.warning(
|
||||
"Could not remove item %s from treeOrder as it does not exist" % tHandle
|
||||
)
|
||||
return False
|
||||
self.treeOrder.remove(tHandle)
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user