Rewrite the GuiDocSplit dialog

This commit is contained in:
Veronica Berglyd Olsen
2022-10-12 22:36:06 +02:00
parent 4f44374f48
commit baafd5f83c
5 changed files with 120 additions and 174 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ VALID_MAP = {
"widthCol3", "sortCol", "sortOrder", "incNovel", "incNotes", "widthCol3", "sortCol", "sortOrder", "incNovel", "incNotes",
"hideZeros", "hideNegative", "groupByDay", "showIdleTime", "histMax" "hideZeros", "hideNegative", "groupByDay", "showIdleTime", "histMax"
}, },
"GuiDocSplit": {"spLevel"}, "GuiDocSplit": {"spLevel", "intoFolder", "docHierarchy"},
"GuiBuildNovel": { "GuiBuildNovel": {
"winWidth", "winHeight", "boxWidth", "docWidth", "hideScene", "winWidth", "winHeight", "boxWidth", "docWidth", "hideScene",
"hideSection", "addNovel", "addNotes", "ignoreFlag", "justifyText", "hideSection", "addNovel", "addNotes", "ignoreFlag", "justifyText",
+1 -1
View File
@@ -75,7 +75,7 @@ class GuiDocMerge(QDialog):
# Merge Options # Merge Options
self.trashLabel = QLabel(self.tr("Move merged items to Trash")) self.trashLabel = QLabel(self.tr("Move merged items to Trash"))
self.trashSwitch = QSwitch() self.trashSwitch = QSwitch(width=2*iPx, height=iPx)
self.optBox = QGridLayout() self.optBox = QGridLayout()
self.optBox.addWidget(self.trashLabel, 0, 0) self.optBox.addWidget(self.trashLabel, 0, 0)
+92 -157
View File
@@ -1,10 +1,11 @@
""" """
novelWriter GUI Doc Split Tool novelWriter GUI Doc Split Dialog
================================ ==================================
GUI class for splitting a single document into multiple documents Custom dialog class for splitting documents.
File History: File History:
Created: 2020-02-01 [0.4.3] Created: 2020-02-01 [0.4.3]
Rewritten: 2022-10-12 [2.0b1]
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen Copyright 20182022, Veronica Berglyd Olsen
@@ -29,19 +30,18 @@ import novelwriter
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QComboBox, QListWidget, QAbstractItemView, QDialog, QVBoxLayout, QComboBox, QListWidget, QAbstractItemView,
QListWidgetItem, QDialogButtonBox, QLabel QListWidgetItem, QDialogButtonBox, QLabel, QGridLayout
) )
from novelwriter.core import NWDoc from novelwriter.core import NWDoc
from novelwriter.enum import nwAlert from novelwriter.gui.custom import QHelpLabel, QSwitch
from novelwriter.gui.custom import QHelpLabel
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class GuiDocSplit(QDialog): class GuiDocSplit(QDialog):
def __init__(self, mainGui): def __init__(self, mainGui, sHandle):
super().__init__(parent=mainGui) super().__init__(parent=mainGui)
logger.debug("Initialising GuiDocSplit ...") logger.debug("Initialising GuiDocSplit ...")
@@ -49,12 +49,12 @@ class GuiDocSplit(QDialog):
self.mainConf = novelwriter.CONFIG self.mainConf = novelwriter.CONFIG
self.mainGui = mainGui self.mainGui = mainGui
self.mainTheme = mainGui.mainTheme
self.theProject = mainGui.theProject self.theProject = mainGui.theProject
self.sourceItem = None self._data = {}
self.sourceText = [] self._text = []
self.outerBox = QVBoxLayout()
self.setWindowTitle(self.tr("Split Document")) self.setWindowTitle(self.tr("Split Document"))
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Document Headers"))) self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Document Headers")))
@@ -63,6 +63,18 @@ class GuiDocSplit(QDialog):
self.mainGui.mainTheme.helpText self.mainGui.mainTheme.helpText
) )
# Values
iPx = self.mainTheme.baseIconSize
hSp = self.mainConf.pxInt(12)
vSp = self.mainConf.pxInt(8)
bSp = self.mainConf.pxInt(12)
pOptions = self.theProject.options
spLevel = pOptions.getInt("GuiDocSplit", "spLevel", 3)
intoFolder = pOptions.getBool("GuiDocSplit", "intoFolder", True)
docHierarchy = pOptions.getBool("GuiDocSplit", "docHierarchy", True)
# Header Selection
self.listBox = QListWidget() self.listBox = QListWidget()
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop) self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
self.listBox.setMinimumWidth(self.mainConf.pxInt(400)) self.listBox.setMinimumWidth(self.mainConf.pxInt(400))
@@ -73,194 +85,117 @@ class GuiDocSplit(QDialog):
self.splitLevel.addItem(self.tr("Split up to Header Level 2 (Chapter)"), 2) self.splitLevel.addItem(self.tr("Split up to Header Level 2 (Chapter)"), 2)
self.splitLevel.addItem(self.tr("Split up to Header Level 3 (Scene)"), 3) self.splitLevel.addItem(self.tr("Split up to Header Level 3 (Scene)"), 3)
self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4) self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4)
spIndex = self.splitLevel.findData( spIndex = self.splitLevel.findData(spLevel)
self.theProject.options.getInt("GuiDocSplit", "spLevel", 3)
)
if spIndex != -1: if spIndex != -1:
self.splitLevel.setCurrentIndex(spIndex) self.splitLevel.setCurrentIndex(spIndex)
self.splitLevel.currentIndexChanged.connect(self._populateList) self.splitLevel.currentIndexChanged.connect(self._reloadList)
# Split Options
self.folderLabel = QLabel(self.tr("Split into a new folder"))
self.folderSwitch = QSwitch(width=2*iPx, height=iPx)
self.folderSwitch.setChecked(intoFolder)
self.hierarchyLabel = QLabel(self.tr("Create document hierarchy"))
self.hierarchySwitch = QSwitch(width=2*iPx, height=iPx)
self.hierarchySwitch.setChecked(docHierarchy)
self.optBox = QGridLayout()
self.optBox.addWidget(self.folderLabel, 0, 0)
self.optBox.addWidget(self.folderSwitch, 0, 1)
self.optBox.addWidget(self.hierarchyLabel, 1, 0)
self.optBox.addWidget(self.hierarchySwitch, 1, 1)
self.optBox.setVerticalSpacing(vSp)
self.optBox.setHorizontalSpacing(hSp)
self.optBox.setColumnStretch(2, 1)
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doSplit) self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self._doClose) self.buttonBox.rejected.connect(self.reject)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.setSpacing(0) self.outerBox.setSpacing(0)
self.outerBox.addWidget(self.headLabel) self.outerBox.addWidget(self.headLabel)
self.outerBox.addWidget(self.helpLabel) self.outerBox.addWidget(self.helpLabel)
self.outerBox.addSpacing(self.mainConf.pxInt(8)) self.outerBox.addSpacing(vSp)
self.outerBox.addWidget(self.listBox) self.outerBox.addWidget(self.listBox)
self.outerBox.addWidget(self.splitLevel) self.outerBox.addWidget(self.splitLevel)
self.outerBox.addSpacing(self.mainConf.pxInt(12)) self.outerBox.addSpacing(vSp)
self.outerBox.addLayout(self.optBox)
self.outerBox.addSpacing(bSp)
self.outerBox.addWidget(self.buttonBox) self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
self.rejected.connect(self._doClose) # Load Content
self._loadContent(sHandle)
self._populateList()
logger.debug("GuiDocSplit initialisation complete") logger.debug("GuiDocSplit initialisation complete")
return return
## def getData(self):
# Buttons """Return the user's choices. Also save the users options for
## the next time the dialog is used.
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 split process, and
must be deleted manually.
""" """
logger.verbose("GuiDocSplit split button clicked") headerList = []
if self.sourceItem is None:
self.mainGui.makeAlert(self.tr(
"No source document selected. Nothing to do."
), nwAlert.ERROR)
return False
srcItem = self.theProject.tree[self.sourceItem]
if srcItem is None:
self.mainGui.makeAlert(self.tr(
"Could not parse source document."
), nwAlert.ERROR)
return False
inDoc = NWDoc(self.theProject, self.sourceItem)
theText = inDoc.readDocument()
docErr = inDoc.getError()
if theText is None and docErr:
self.mainGui.makeAlert([
self.tr("Failed to open document file."), docErr
], nwAlert.ERROR)
if theText is None:
theText = ""
nLines = len(self.sourceText)
logger.debug("Splitting document %s with %d lines", self.sourceItem, nLines)
finalOrder = []
for i in range(self.listBox.count()): for i in range(self.listBox.count()):
listItem = self.listBox.item(i) item = self.listBox.item(i)
wTitle = listItem.text() headerList.append((item.text(), item.data(Qt.UserRole)))
lineNo = listItem.data(Qt.UserRole)
finalOrder.append([wTitle, lineNo, nLines])
if i > 0:
finalOrder[i-1][2] = lineNo
nFiles = len(finalOrder) spLevel = self.splitLevel.currentData()
if nFiles == 0: intoFolder = self.folderSwitch.isChecked()
self.mainGui.makeAlert(self.tr( docHierarchy = self.hierarchySwitch.isChecked()
"No headers found. Nothing to do."
), nwAlert.ERROR)
return False
msgYes = self.mainGui.askQuestion( self._data["spLevel"] = spLevel
self.tr("Split Document"), self._data["headerList"] = headerList
"{0}<br><br>{1}".format( self._data["intoFolder"] = intoFolder
self.tr( self._data["docHierarchy"] = docHierarchy
"The document will be split into {0} file(s) in a new folder. "
"The original document will remain intact."
).format(nFiles),
self.tr(
"Continue with the splitting process?"
)
)
)
if not msgYes:
return False
# Create the folder pOptions = self.theProject.options
fHandle = self.theProject.newFolder(srcItem.itemName, srcItem.itemParent) pOptions.setValue("GuiDocSplit", "spLevel", spLevel)
self.mainGui.projView.revealNewTreeItem(fHandle) pOptions.setValue("GuiDocSplit", "intoFolder", intoFolder)
logger.verbose("Creating folder '%s'", fHandle) pOptions.setValue("GuiDocSplit", "docHierarchy", docHierarchy)
# Loop through, and create the files return self._data
for wTitle, iStart, iEnd in finalOrder:
wTitle = wTitle.lstrip("#").strip() ##
nHandle = self.theProject.newFile(wTitle, fHandle) # Slots
newItem = self.theProject.tree[nHandle] ##
newItem.setStatus(srcItem.itemStatus)
newItem.setImport(srcItem.itemImport)
logger.verbose(
"Creating new document '%s' with text from line %d to %d",
nHandle, iStart+1, iEnd
)
theText = "\n".join(self.sourceText[iStart:iEnd]) def _reloadList(self):
theText = theText.rstrip("\n") + "\n\n" """Reload the content of the list box.
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._doClose()
return True
def _doClose(self):
"""Close the dialog window without doing anything.
""" """
self.theProject.options.saveSettings() sHandle = self._data.get("sHandle", None)
self.close() self._loadContent(sHandle)
return return
## ##
# Internal Functions # Internal Functions
## ##
def _populateList(self): def _loadContent(self, sHandle):
"""Get the item selected in the tree, check that it is a folder, """Load content from a given source item.
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.
""" """
self.listBox.clear() self._data = {}
if self.sourceItem is None: self._data["sHandle"] = sHandle
self.sourceItem = self.mainGui.projView.getSelectedHandle()
if self.sourceItem is None: nwItem = self.theProject.tree[sHandle]
return False if nwItem is None or not nwItem.isFileType():
nwItem = self.theProject.tree[self.sourceItem]
if nwItem is None:
return False
if not nwItem.isFileType():
self.mainGui.makeAlert(self.tr(
"Element selected in the project tree must be a file."
), nwAlert.ERROR)
return False
inDoc = NWDoc(self.theProject, self.sourceItem)
theText = inDoc.readDocument()
if theText is None:
theText = ""
return False return False
spLevel = self.splitLevel.currentData() spLevel = self.splitLevel.currentData()
self.theProject.options.setValue("GuiDocSplit", "spLevel", spLevel) if not self._text:
logger.debug( inDoc = NWDoc(self.theProject, sHandle)
"Scanning document '%s' for headings level <= %d", self._text = (inDoc.readDocument() or "").splitlines()
self.sourceItem, spLevel
)
self.sourceText = theText.splitlines() self.listBox.clear()
for lineNo, aLine in enumerate(self.sourceText): for lineNo, aLine in enumerate(self._text):
onLine = -1 onLine = -1
if aLine.startswith("# ") and spLevel >= 1: if aLine.startswith(("# ", "#! ")) and spLevel >= 1:
onLine = lineNo onLine = lineNo
elif aLine.startswith("## ") and spLevel >= 2: elif aLine.startswith(("## ", "##! ")) and spLevel >= 2:
onLine = lineNo onLine = lineNo
elif aLine.startswith("### ") and spLevel >= 3: elif aLine.startswith("### ") and spLevel >= 3:
onLine = lineNo onLine = lineNo
+25 -2
View File
@@ -41,7 +41,7 @@ from PyQt5.QtWidgets import (
from novelwriter.core import DocMerger from novelwriter.core import 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, GuiDocSplit, GuiEditLabel
from novelwriter.constants import nwHeaders, trConst, nwLabels from novelwriter.constants import nwHeaders, trConst, nwLabels
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -1458,7 +1458,30 @@ class GuiProjectTree(QTreeWidget):
return True return True
def _splitDocument(self, tHandle): def _splitDocument(self, tHandle):
return """Split a document into multiple documents.
"""
logger.info("Request to split items with handle '%s'", tHandle)
tItem = self.theProject.tree[tHandle]
if tItem is None:
return False
if not tItem.isFileType():
logger.error("Only documents can be split")
return False
dlgSplit = GuiDocSplit(self.mainGui, tHandle)
dlgSplit.exec_()
if dlgSplit.result() == QDialog.Accepted:
print(dlgSplit.getData())
else:
logger.info("Action cancelled by user")
return False
return True
def _scanChildren(self, theList, tItem, tIndex): def _scanChildren(self, theList, tItem, tIndex):
"""This is a recursive function returning all items in a tree """This is a recursive function returning all items in a tree
+1 -13
View File
@@ -44,7 +44,7 @@ from novelwriter.gui import (
GuiViewsBar GuiViewsBar
) )
from novelwriter.dialogs import ( from novelwriter.dialogs import (
GuiAbout, GuiDocSplit, GuiPreferences, GuiProjectDetails, GuiProjectLoad, GuiAbout, GuiPreferences, GuiProjectDetails, GuiProjectLoad,
GuiProjectSettings, GuiUpdates, GuiWordList GuiProjectSettings, GuiUpdates, GuiWordList
) )
from novelwriter.tools import ( from novelwriter.tools import (
@@ -754,18 +754,6 @@ class GuiMain(QMainWindow):
return True return True
def splitDocument(self):
"""Split a single document into multiple documents.
"""
if not self.hasProject:
logger.error("No project open")
return False
dlgSplit = GuiDocSplit(self)
dlgSplit.exec_()
return True
def passDocumentAction(self, theAction): def passDocumentAction(self, theAction):
"""Pass on document action to the document viewer if it has """Pass on document action to the document viewer if it has
focus, or pass it to the document editor if it or any of focus, or pass it to the document editor if it or any of