Merge pull request #159 from vkbo/split_merge

Split, merge, and delete functionality
This commit is contained in:
Veronica K. Berglyd Olsen
2020-02-05 01:28:21 +01:00
committed by GitHub
15 changed files with 622 additions and 41 deletions
+4 -3
View File
@@ -1,10 +1,9 @@
os: linux
dist: xenial
dist: bionic
services:
- xvfb
language: python
cache: bundler
sudo: required
addons:
apt:
packages:
@@ -12,9 +11,11 @@ addons:
- python3-pyqt5
- python3-pyqt5.qtsvg
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"
install:
- pip install --upgrade pip
- pip install -r requirements.txt
# - pip install pytest-faulthandler
- pip install pytest-xvfb
@@ -1,3 +1,8 @@
FROM ICON SET: Typicons
LICENSE: Creative Commons (Attribution-Share Alike 3.0 Unported)
https://creativecommons.org/licenses/by-sa/3.0/
Apllies to:
export.svg
merge.svg
split.svg
+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg4"
viewBox="0 0 20 20"
height="20"
width="20"
version="1.2">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<path
style="fill:#000000;fill-opacity:0.35294118;stroke:none;stroke-opacity:1"
id="path2"
d="m 16,5.816 v 1.851 c 0,1.93 -1.57,3.5 -3.5,3.5 -0.827,0 -1.5,0.673 -1.5,1.5 v 1.517 c 1.161,0.415 2,1.514 2,2.816 0,1.654 -1.346,3 -3,3 C 8.346,20 7,18.654 7,17 7,15.698 7.839,14.599 9,14.185 V 12.667 C 9,11.84 8.327,11.167 7.5,11.167 5.57,11.167 4,9.597 4,7.667 V 5.816 C 2.839,5.401 2,4.302 2,3 2,1.346 3.346,0 5,0 6.654,0 8,1.346 8,3 8,4.302 7.161,5.401 6,5.816 v 1.851 c 0,0.827 0.673,1.5 1.5,1.5 0.979,0 1.864,0.407 2.5,1.058 0.636,-0.651 1.521,-1.058 2.5,-1.058 0.827,0 1.5,-0.673 1.5,-1.5 V 5.816 C 12.839,5.401 12,4.302 12,3 c 0,-1.654 1.346,-3 3,-3 1.654,0 3,1.346 3,3 0,1.302 -0.839,2.401 -2,2.816 z M 5,2 C 4.448,2 4,2.449 4,3 4,3.551 4.448,4 5,4 5.552,4 6,3.551 6,3 6,2.449 5.552,2 5,2 Z m 5,16 c 0.552,0 1,-0.449 1,-1 0,-0.551 -0.448,-1 -1,-1 -0.552,0 -1,0.449 -1,1 0,0.551 0.448,1 1,1 z M 15,2 c -0.552,0 -1,0.449 -1,1 0,0.551 0.448,1 1,1 0.552,0 1,-0.449 1,-1 0,-0.551 -0.448,-1 -1,-1 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg4"
viewBox="0 0 20 20"
height="20"
width="20"
version="1.2">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<path
style="fill:#000000;fill-opacity:0.35294118;stroke:none;stroke-opacity:1"
id="path2"
d="m 16,14.184 v -1.851 c 0,-1.93 -1.57,-3.5 -3.5,-3.5 -0.827,0 -1.5,-0.673 -1.5,-1.5 V 5.816 C 12.161,5.401 13,4.302 13,3 13,1.346 11.654,0 10,0 8.346,0 7,1.346 7,3 7,4.302 7.839,5.401 9,5.815 v 1.518 c 0,0.827 -0.673,1.5 -1.5,1.5 -1.93,0 -3.5,1.57 -3.5,3.5 v 1.851 C 2.839,14.599 2,15.698 2,17 c 0,1.654 1.346,3 3,3 1.654,0 3,-1.346 3,-3 0,-1.302 -0.839,-2.401 -2,-2.816 v -1.851 c 0,-0.827 0.673,-1.5 1.5,-1.5 0.979,0 1.864,-0.407 2.5,-1.058 0.636,0.651 1.521,1.058 2.5,1.058 0.827,0 1.5,0.673 1.5,1.5 v 1.851 c -1.161,0.415 -2,1.514 -2,2.816 0,1.654 1.346,3 3,3 1.654,0 3,-1.346 3,-3 0,-1.302 -0.839,-2.401 -2,-2.816 z M 5,18 c -0.552,0 -1,-0.449 -1,-1 0,-0.551 0.448,-1 1,-1 0.552,0 1,0.449 1,1 0,0.551 -0.448,1 -1,1 z M 10,2 c 0.552,0 1,0.449 1,1 0,0.551 -0.448,1 -1,1 C 9.448,4 9,3.551 9,3 9,2.449 9.448,2 10,2 Z m 5,16 c -0.552,0 -1,-0.449 -1,-1 0,-0.551 0.448,-1 1,-1 0.552,0 1,0.449 1,1 0,0.551 -0.448,1 -1,1 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+1
View File
@@ -29,6 +29,7 @@ class nwFiles():
EXPORT_OPT = "exportOptions.json"
TLINE_OPT = "timelineOptions.json"
SLOG_OPT = "sessionLogOptions.json"
MERGE_OPT = "docMergeOptions.json"
# END Class nwFiles
+4
View File
@@ -8,6 +8,8 @@ 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
@@ -33,6 +35,8 @@ __all__ = [
"GuiMainStatus",
"GuiTheme",
"GuiConfigEditor",
"GuiDocMerge",
"GuiDocSplit",
"GuiExport",
"GuiItemEditor",
"GuiProjectEditor",
+4
View File
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
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
@@ -9,6 +11,8 @@ from nw.gui.dialogs.timelineview import GuiTimeLineView
__all__ = [
"GuiConfigEditor",
"GuiDocMerge",
"GuiDocSplit",
"GuiExport",
"GuiItemEditor",
"GuiProjectEditor",
+157
View File
@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Doc Merge
novelWriter GUI Doc Merge
=============================
Tool for merging multiple documents to one
File History:
Created: 2020-01-23 [0.4.3]
"""
import logging
import nw
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGridLayout, QPushButton,
QListWidget, QAbstractItemView, QListWidgetItem
)
from nw.constants import nwAlert, nwItemType
from nw.project import NWDoc
logger = logging.getLogger(__name__)
class GuiDocMerge(QDialog):
def __init__(self, theParent, theProject):
QDialog.__init__(self, theParent)
logger.debug("Initialising GuiDocMerge ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.theProject = theProject
self.sourceItem = None
self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout()
self.setWindowTitle("Merge Documents")
self.setLayout(self.outerBox)
self.guiDeco = self.theParent.theTheme.loadDecoration("merge",(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.InternalMove)
self.mergeButton = QPushButton("Merge")
self.mergeButton.clicked.connect(self._doMerge)
self.closeButton = QPushButton("Close")
self.closeButton.clicked.connect(self._doClose)
self.doMergeForm.addWidget(self.listBox, 0, 0, 1, 3)
self.doMergeForm.addWidget(self.mergeButton, 1, 1)
self.doMergeForm.addWidget(self.closeButton, 1, 2)
self.innerBox.addLayout(self.doMergeForm)
self.rejected.connect(self._doClose)
self.show()
self._populateList()
logger.debug("GuiDocMerge initialisation complete")
return
##
# Buttons
##
def _doMerge(self):
"""Perform the merge of the files in the selected folder, and
create a new file in the same parent folder. The old files are
not removed in the merge process, and must be deleted manually.
"""
logger.verbose("GuiDocMerge merge button clicked")
finalOrder = []
for i in range(self.listBox.count()):
finalOrder.append(self.listBox.item(i).data(Qt.UserRole))
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("GuiDocMerge 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.
"""
tHandle = self.theParent.treeView.getSelectedHandle()
self.sourceItem = tHandle
if tHandle is None:
return
nwItem = self.theProject.getItem(tHandle)
if nwItem is None:
return
if nwItem.itemType is not nwItemType.FOLDER:
self.theParent.makeAlert((
"Element selected in the project tree must be a folder."
), nwAlert.ERROR)
return
for sHandle in self.theParent.treeView.getTreeFromHandle(tHandle):
newItem = QListWidgetItem()
nwItem = self.theProject.getItem(sHandle)
if nwItem.itemType is not nwItemType.FILE:
continue
newItem.setText(nwItem.itemName)
newItem.setData(Qt.UserRole, sHandle)
self.listBox.addItem(newItem)
return
# END Class GuiDocMerge
+234
View File
@@ -0,0 +1,234 @@
# -*- 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, 3)
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")
logger.debug(
"Splitting document %s with %d lines" % (self.sourceItem,nLines)
)
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)
logger.verbose("Creating folder %s" % 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()
nHandle = self.theProject.newFile(wTitle, srcItem.itemClass, fHandle)
newItem = self.theProject.getItem(nHandle)
newItem.setLayout(itemLayout)
logger.verbose(
"Creating new document %s with text from line %d to %d" % (nHandle, iStart, iEnd-1)
)
theText = "\n".join(theLines[iStart:iEnd])
theDoc.openDocument(nHandle, False)
theDoc.saveDocument(theText)
theDoc.clearDocument()
self.theParent.treeView.revealTreeItem(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
+75 -17
View File
@@ -16,10 +16,10 @@ import nw
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFont, QColor
from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QApplication
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QApplication, QMessageBox
)
from nw.project import NWItem
from nw.project import NWItem, NWDoc
from nw.constants import (
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert
)
@@ -171,14 +171,21 @@ class GuiDocTree(QTreeWidget):
return False
# Add the new item to the tree
nwItem = self.theProject.getItem(tHandle)
trItem = self._addTreeItem(nwItem)
self.revealTreeItem(tHandle)
self.theParent.editItem()
return True
def revealTreeItem(self, tHandle):
"""Reveal a newly added project item in the project tree.
"""
nwItem = self.theProject.getItem(tHandle)
trItem = self._addTreeItem(nwItem)
pHandle = nwItem.parHandle
if pHandle is not None and pHandle in self.theMap.keys():
self.theMap[pHandle].setExpanded(True)
self.clearSelection()
trItem.setSelected(True)
self.theParent.editItem()
return True
def moveTreeItem(self, nStep):
@@ -221,6 +228,16 @@ class GuiDocTree(QTreeWidget):
self.theProject.setTreeOrder(theList)
return True
def getTreeFromHandle(self, tHandle):
"""Recursively return all the children items starting from a
given item handle.
"""
theList = []
theItem = self._getTreeItem(tHandle)
if theItem is not None:
theList = self._scanChildren(theList, theItem, 0)
return theList
def getColumnSizes(self):
retVals = [
self.columnWidth(0),
@@ -246,21 +263,61 @@ class GuiDocTree(QTreeWidget):
trItemS = self._getTreeItem(tHandle)
nwItemS = self.theProject.getItem(tHandle)
if nwItemS is None:
return False
if nwItemS.itemType == nwItemType.FILE:
logger.debug("User requested file %s moved to trash" % tHandle)
trItemP = trItemS.parent()
trItemT = self._addTrashRoot()
if trItemP is None or trItemT is None:
logger.error("Could not move item to trash")
logger.error("Could not delete item")
return False
tIndex = trItemP.indexOfChild(trItemS)
trItemC = trItemP.takeChild(tIndex)
trItemT.addChild(trItemC)
nwItemS.setParent(self.theProject.trashRoot)
self.clearSelection()
trItemP.setSelected(True)
self.theProject.setProjectChanged(True)
self.theParent.theIndex.deleteHandle(tHandle)
pHandle = nwItemS.parHandle
if pHandle is not None and pHandle == self.theProject.trashRoot:
# If the file is in the trash folder already, as the
# user if they want to permanently delete the file.
doPermanent = False
if self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Delete File", "Permanently delete file '%s'?" % nwItemS.itemName
)
if msgRes == QMessageBox.Yes:
doPermanent = True
else:
doPermanent = True
if doPermanent:
logger.debug("Permanently deleting file with handle %s" % tHandle)
tIndex = trItemP.indexOfChild(trItemS)
trItemC = trItemP.takeChild(tIndex)
if self.theParent.docEditor.theHandle == tHandle:
self.theParent.closeDocument()
theDoc = NWDoc(self.theProject, self.theParent)
theDoc.deleteDocument(tHandle)
self.theProject.deleteItem(tHandle)
self.theParent.theIndex.deleteHandle(tHandle)
else:
# The file is not already in the trash folder, so we
# move it there.
if pHandle is None:
logger.warning("File has no parent item")
tIndex = trItemP.indexOfChild(trItemS)
trItemC = trItemP.takeChild(tIndex)
trItemT.addChild(trItemC)
nwItemS.setParent(self.theProject.trashRoot)
self.theProject.setProjectChanged(True)
self.theParent.theIndex.deleteHandle(tHandle)
elif nwItemS.itemType == nwItemType.FOLDER:
logger.debug("User requested folder %s deleted" % tHandle)
@@ -271,8 +328,6 @@ class GuiDocTree(QTreeWidget):
tIndex = trItemP.indexOfChild(trItemS)
if trItemS.childCount() == 0:
trItemP.takeChild(tIndex)
self.clearSelection()
trItemP.setSelected(True)
self.theProject.deleteItem(tHandle)
else:
self.makeAlert(["Cannot delete folder.","It is not empty."], nwAlert.ERROR)
@@ -428,6 +483,9 @@ class GuiDocTree(QTreeWidget):
return newItem
def _addTrashRoot(self):
"""Adds the trash root folder if it doesn't already exist in the
project tree.
"""
if self.theProject.trashRoot is None:
self.theProject.addTrash()
trItem = self._addTreeItem(
+2
View File
@@ -46,7 +46,9 @@ class GuiIcons:
DECO_MAP = {
"export" : "export.svg",
"merge" : "merge.svg",
"settings" : "gear.svg",
"split" : "split.svg",
}
def __init__(self, theParent):
+12
View File
@@ -369,6 +369,18 @@ class GuiMainMenu(QMenuBar):
self.aImportFile.triggered.connect(self.theParent.importDocument)
self.docuMenu.addAction(self.aImportFile)
# 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.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):
+21 -5
View File
@@ -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
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)
@@ -458,6 +458,22 @@ class GuiMain(QMainWindow):
return True
def mergeDocuments(self):
"""Merge multiple documents to one single new document.
"""
if self.mainConf.showGUI:
dlgMerge = GuiDocMerge(self, self.theProject)
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):
"""Pass on document action theAction to whatever document has
the focus. If no document has focus, the action is discarded.
+26 -9
View File
@@ -59,7 +59,7 @@ class NWDoc():
if self.theItem.parHandle == self.theProject.trashRoot:
self.docEditable = False
docDir, docFile = self._assemblePath(self.FILE_MN)
docDir, docFile = self.assemblePath(self.docHandle, self.FILE_MN)
self.fileLoc = path.join(docDir,docFile)
logger.debug("Opening document %s" % self.fileLoc)
dataDir = path.join(self.theProject.projPath, docDir)
@@ -92,7 +92,7 @@ class NWDoc():
if self.docHandle is None or not self.docEditable:
return False
docDir, docFile = self._assemblePath(self.FILE_MN)
docDir, docFile = self.assemblePath(self.docHandle, self.FILE_MN)
logger.debug("Saving document %s" % path.join(docDir,docFile))
dataPath = path.join(self.theProject.projPath, docDir)
docPath = path.join(dataPath, docFile)
@@ -124,15 +124,32 @@ class NWDoc():
return True
##
# Internal Functions
##
def deleteDocument(self, tHandle):
"""Permanently delete a document source file and its backups
from the project data folder.
"""
docDir, docFile = self.assemblePath(tHandle, self.FILE_MN)
dataPath = path.join(self.theProject.projPath, docDir)
chkList = []
chkList.append(path.join(dataPath, docFile))
chkList.append(path.join(dataPath,docFile[:-3]+"tmp"))
chkList.append(path.join(dataPath,docFile[:-3]+"bak"))
for chkFile in chkList:
if path.isfile(chkFile):
try:
unlink(chkFile)
logger.debug("Deleted: %s" % chkFile)
except Exception as e:
self.makeAlert(["Could not delete document file.",str(e)], nwAlert.ERROR)
return False
return True
def _assemblePath(self, docExt):
if self.docHandle is None:
@staticmethod
def assemblePath(tHandle, docExt):
if tHandle is None:
return None
docDir = "data_"+self.docHandle[0]
docFile = self.docHandle[1:13]+"_"+docExt
docDir = "data_"+tHandle[0]
docFile = tHandle[1:13]+"_"+docExt
return docDir, docFile
# END Class NWDoc
+15 -7
View File
@@ -13,7 +13,7 @@
import logging
import nw
from os import path, mkdir, listdir
from os import path, mkdir, listdir, unlink
from shutil import copyfile
from lxml import etree
from hashlib import sha256
@@ -452,9 +452,6 @@ class NWProject():
self.setProjectChanged(True)
return True
def getSessionWordCount(self):
return self.currWCount - self.lastWCount
def setStatusColours(self, newCols):
replaceMap = self.statusItems.setNewEntries(newCols)
if self.projTree is not None:
@@ -497,6 +494,9 @@ class NWProject():
logger.error("No tree item with handle %s" % str(tHandle))
return None
def getSessionWordCount(self):
return self.currWCount - self.lastWCount
def getRootItem(self, tHandle):
"""Iterate upwards in the tree until we find the item with
parent None, the root item. We do this with a for loop with a
@@ -505,10 +505,13 @@ class NWProject():
tItem = self.getItem(tHandle)
if tItem is not None:
for i in range(200):
tHandle = tItem.parHandle
tItem = self.getItem(tHandle)
if tItem is None:
if tItem.parHandle is None:
return tHandle
else:
tHandle = tItem.parHandle
tItem = self.getItem(tHandle)
if tItem is None:
return tHandle
return None
def getProjectItems(self):
@@ -560,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