Merge pull request #159 from vkbo/split_merge
Split, merge, and delete functionality
This commit is contained in:
+4
-3
@@ -1,10 +1,9 @@
|
|||||||
os: linux
|
os: linux
|
||||||
dist: xenial
|
dist: bionic
|
||||||
services:
|
services:
|
||||||
- xvfb
|
- xvfb
|
||||||
language: python
|
language: python
|
||||||
cache: bundler
|
cache: bundler
|
||||||
sudo: required
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
@@ -12,9 +11,11 @@ addons:
|
|||||||
- python3-pyqt5
|
- python3-pyqt5
|
||||||
- python3-pyqt5.qtsvg
|
- python3-pyqt5.qtsvg
|
||||||
python:
|
python:
|
||||||
- "3.5"
|
- "3.6"
|
||||||
- "3.7"
|
- "3.7"
|
||||||
|
- "3.8"
|
||||||
install:
|
install:
|
||||||
|
- pip install --upgrade pip
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
# - pip install pytest-faulthandler
|
# - pip install pytest-faulthandler
|
||||||
- pip install pytest-xvfb
|
- pip install pytest-xvfb
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
FROM ICON SET: Typicons
|
FROM ICON SET: Typicons
|
||||||
LICENSE: Creative Commons (Attribution-Share Alike 3.0 Unported)
|
LICENSE: Creative Commons (Attribution-Share Alike 3.0 Unported)
|
||||||
https://creativecommons.org/licenses/by-sa/3.0/
|
https://creativecommons.org/licenses/by-sa/3.0/
|
||||||
|
|
||||||
|
Apllies to:
|
||||||
|
export.svg
|
||||||
|
merge.svg
|
||||||
|
split.svg
|
||||||
@@ -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 |
@@ -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 |
@@ -29,6 +29,7 @@ class nwFiles():
|
|||||||
EXPORT_OPT = "exportOptions.json"
|
EXPORT_OPT = "exportOptions.json"
|
||||||
TLINE_OPT = "timelineOptions.json"
|
TLINE_OPT = "timelineOptions.json"
|
||||||
SLOG_OPT = "sessionLogOptions.json"
|
SLOG_OPT = "sessionLogOptions.json"
|
||||||
|
MERGE_OPT = "docMergeOptions.json"
|
||||||
|
|
||||||
# END Class nwFiles
|
# END Class nwFiles
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from nw.gui.theme import GuiTheme
|
|||||||
|
|
||||||
# Dialogs
|
# Dialogs
|
||||||
from nw.gui.dialogs.configeditor import GuiConfigEditor
|
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.export import GuiExport
|
||||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||||
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
||||||
@@ -33,6 +35,8 @@ __all__ = [
|
|||||||
"GuiMainStatus",
|
"GuiMainStatus",
|
||||||
"GuiTheme",
|
"GuiTheme",
|
||||||
"GuiConfigEditor",
|
"GuiConfigEditor",
|
||||||
|
"GuiDocMerge",
|
||||||
|
"GuiDocSplit",
|
||||||
"GuiExport",
|
"GuiExport",
|
||||||
"GuiItemEditor",
|
"GuiItemEditor",
|
||||||
"GuiProjectEditor",
|
"GuiProjectEditor",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from nw.gui.dialogs.configeditor import GuiConfigEditor
|
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.export import GuiExport
|
||||||
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
from nw.gui.dialogs.itemeditor import GuiItemEditor
|
||||||
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
from nw.gui.dialogs.projecteditor import GuiProjectEditor
|
||||||
@@ -9,6 +11,8 @@ from nw.gui.dialogs.timelineview import GuiTimeLineView
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"GuiConfigEditor",
|
"GuiConfigEditor",
|
||||||
|
"GuiDocMerge",
|
||||||
|
"GuiDocSplit",
|
||||||
"GuiExport",
|
"GuiExport",
|
||||||
"GuiItemEditor",
|
"GuiItemEditor",
|
||||||
"GuiProjectEditor",
|
"GuiProjectEditor",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -16,10 +16,10 @@ import nw
|
|||||||
from PyQt5.QtCore import Qt, QSize
|
from PyQt5.QtCore import Qt, QSize
|
||||||
from PyQt5.QtGui import QFont, QColor
|
from PyQt5.QtGui import QFont, QColor
|
||||||
from PyQt5.QtWidgets import (
|
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 (
|
from nw.constants import (
|
||||||
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||||
)
|
)
|
||||||
@@ -171,14 +171,21 @@ class GuiDocTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Add the new item to the tree
|
# Add the new item to the tree
|
||||||
nwItem = self.theProject.getItem(tHandle)
|
self.revealTreeItem(tHandle)
|
||||||
trItem = self._addTreeItem(nwItem)
|
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():
|
if pHandle is not None and pHandle in self.theMap.keys():
|
||||||
self.theMap[pHandle].setExpanded(True)
|
self.theMap[pHandle].setExpanded(True)
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
trItem.setSelected(True)
|
trItem.setSelected(True)
|
||||||
self.theParent.editItem()
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def moveTreeItem(self, nStep):
|
def moveTreeItem(self, nStep):
|
||||||
@@ -221,6 +228,16 @@ class GuiDocTree(QTreeWidget):
|
|||||||
self.theProject.setTreeOrder(theList)
|
self.theProject.setTreeOrder(theList)
|
||||||
return True
|
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):
|
def getColumnSizes(self):
|
||||||
retVals = [
|
retVals = [
|
||||||
self.columnWidth(0),
|
self.columnWidth(0),
|
||||||
@@ -246,21 +263,61 @@ class GuiDocTree(QTreeWidget):
|
|||||||
trItemS = self._getTreeItem(tHandle)
|
trItemS = self._getTreeItem(tHandle)
|
||||||
nwItemS = self.theProject.getItem(tHandle)
|
nwItemS = self.theProject.getItem(tHandle)
|
||||||
|
|
||||||
|
if nwItemS is None:
|
||||||
|
return False
|
||||||
|
|
||||||
if nwItemS.itemType == nwItemType.FILE:
|
if nwItemS.itemType == nwItemType.FILE:
|
||||||
logger.debug("User requested file %s moved to trash" % tHandle)
|
logger.debug("User requested file %s moved to trash" % tHandle)
|
||||||
trItemP = trItemS.parent()
|
trItemP = trItemS.parent()
|
||||||
trItemT = self._addTrashRoot()
|
trItemT = self._addTrashRoot()
|
||||||
if trItemP is None or trItemT is None:
|
if trItemP is None or trItemT is None:
|
||||||
logger.error("Could not move item to trash")
|
logger.error("Could not delete item")
|
||||||
return False
|
return False
|
||||||
tIndex = trItemP.indexOfChild(trItemS)
|
|
||||||
trItemC = trItemP.takeChild(tIndex)
|
pHandle = nwItemS.parHandle
|
||||||
trItemT.addChild(trItemC)
|
if pHandle is not None and pHandle == self.theProject.trashRoot:
|
||||||
nwItemS.setParent(self.theProject.trashRoot)
|
# If the file is in the trash folder already, as the
|
||||||
self.clearSelection()
|
# user if they want to permanently delete the file.
|
||||||
trItemP.setSelected(True)
|
|
||||||
self.theProject.setProjectChanged(True)
|
doPermanent = False
|
||||||
self.theParent.theIndex.deleteHandle(tHandle)
|
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:
|
elif nwItemS.itemType == nwItemType.FOLDER:
|
||||||
logger.debug("User requested folder %s deleted" % tHandle)
|
logger.debug("User requested folder %s deleted" % tHandle)
|
||||||
@@ -271,8 +328,6 @@ class GuiDocTree(QTreeWidget):
|
|||||||
tIndex = trItemP.indexOfChild(trItemS)
|
tIndex = trItemP.indexOfChild(trItemS)
|
||||||
if trItemS.childCount() == 0:
|
if trItemS.childCount() == 0:
|
||||||
trItemP.takeChild(tIndex)
|
trItemP.takeChild(tIndex)
|
||||||
self.clearSelection()
|
|
||||||
trItemP.setSelected(True)
|
|
||||||
self.theProject.deleteItem(tHandle)
|
self.theProject.deleteItem(tHandle)
|
||||||
else:
|
else:
|
||||||
self.makeAlert(["Cannot delete folder.","It is not empty."], nwAlert.ERROR)
|
self.makeAlert(["Cannot delete folder.","It is not empty."], nwAlert.ERROR)
|
||||||
@@ -428,6 +483,9 @@ class GuiDocTree(QTreeWidget):
|
|||||||
return newItem
|
return newItem
|
||||||
|
|
||||||
def _addTrashRoot(self):
|
def _addTrashRoot(self):
|
||||||
|
"""Adds the trash root folder if it doesn't already exist in the
|
||||||
|
project tree.
|
||||||
|
"""
|
||||||
if self.theProject.trashRoot is None:
|
if self.theProject.trashRoot is None:
|
||||||
self.theProject.addTrash()
|
self.theProject.addTrash()
|
||||||
trItem = self._addTreeItem(
|
trItem = self._addTreeItem(
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ class GuiIcons:
|
|||||||
|
|
||||||
DECO_MAP = {
|
DECO_MAP = {
|
||||||
"export" : "export.svg",
|
"export" : "export.svg",
|
||||||
|
"merge" : "merge.svg",
|
||||||
"settings" : "gear.svg",
|
"settings" : "gear.svg",
|
||||||
|
"split" : "split.svg",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
|
|||||||
@@ -369,6 +369,18 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aImportFile.triggered.connect(self.theParent.importDocument)
|
self.aImportFile.triggered.connect(self.theParent.importDocument)
|
||||||
self.docuMenu.addAction(self.aImportFile)
|
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
|
return
|
||||||
|
|
||||||
def _buildViewMenu(self):
|
def _buildViewMenu(self):
|
||||||
|
|||||||
+21
-5
@@ -24,10 +24,10 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from nw.gui import (
|
from nw.gui import (
|
||||||
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor,
|
GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor, GuiExport,
|
||||||
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar,
|
GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
|
||||||
GuiDocViewDetails, GuiConfigEditor, GuiProjectEditor, GuiExport,
|
GuiConfigEditor, GuiProjectEditor, GuiItemEditor, GuiTimeLineView,
|
||||||
GuiItemEditor, GuiTimeLineView, GuiSessionLogView
|
GuiSessionLogView, GuiDocMerge, GuiDocSplit
|
||||||
)
|
)
|
||||||
from nw.project import NWProject, NWDoc, NWItem, NWIndex, NWBackup
|
from nw.project import NWProject, NWDoc, NWItem, NWIndex, NWBackup
|
||||||
from nw.tools import countWords
|
from nw.tools import countWords
|
||||||
@@ -83,7 +83,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
# Assemble Main Window
|
# Assemble Main Window
|
||||||
self.treePane = QFrame()
|
self.treePane = QFrame()
|
||||||
self.treeBox = QVBoxLayout()
|
self.treeBox = QVBoxLayout()
|
||||||
self.treeBox.setContentsMargins(0,0,0,0)
|
self.treeBox.setContentsMargins(0,0,0,0)
|
||||||
self.treeBox.addWidget(self.treeView)
|
self.treeBox.addWidget(self.treeView)
|
||||||
self.treeBox.addWidget(self.treeMeta)
|
self.treeBox.addWidget(self.treeMeta)
|
||||||
@@ -458,6 +458,22 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
return True
|
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):
|
def passDocumentAction(self, theAction):
|
||||||
"""Pass on document action theAction to whatever document has
|
"""Pass on document action theAction to whatever document has
|
||||||
the focus. If no document has focus, the action is discarded.
|
the focus. If no document has focus, the action is discarded.
|
||||||
|
|||||||
+26
-9
@@ -59,7 +59,7 @@ class NWDoc():
|
|||||||
if self.theItem.parHandle == self.theProject.trashRoot:
|
if self.theItem.parHandle == self.theProject.trashRoot:
|
||||||
self.docEditable = False
|
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)
|
self.fileLoc = path.join(docDir,docFile)
|
||||||
logger.debug("Opening document %s" % self.fileLoc)
|
logger.debug("Opening document %s" % self.fileLoc)
|
||||||
dataDir = path.join(self.theProject.projPath, docDir)
|
dataDir = path.join(self.theProject.projPath, docDir)
|
||||||
@@ -92,7 +92,7 @@ class NWDoc():
|
|||||||
if self.docHandle is None or not self.docEditable:
|
if self.docHandle is None or not self.docEditable:
|
||||||
return False
|
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))
|
logger.debug("Saving document %s" % path.join(docDir,docFile))
|
||||||
dataPath = path.join(self.theProject.projPath, docDir)
|
dataPath = path.join(self.theProject.projPath, docDir)
|
||||||
docPath = path.join(dataPath, docFile)
|
docPath = path.join(dataPath, docFile)
|
||||||
@@ -124,15 +124,32 @@ class NWDoc():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
##
|
def deleteDocument(self, tHandle):
|
||||||
# Internal Functions
|
"""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):
|
@staticmethod
|
||||||
if self.docHandle is None:
|
def assemblePath(tHandle, docExt):
|
||||||
|
if tHandle is None:
|
||||||
return None
|
return None
|
||||||
docDir = "data_"+self.docHandle[0]
|
docDir = "data_"+tHandle[0]
|
||||||
docFile = self.docHandle[1:13]+"_"+docExt
|
docFile = tHandle[1:13]+"_"+docExt
|
||||||
return docDir, docFile
|
return docDir, docFile
|
||||||
|
|
||||||
# END Class NWDoc
|
# END Class NWDoc
|
||||||
|
|||||||
+15
-7
@@ -13,7 +13,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import nw
|
import nw
|
||||||
|
|
||||||
from os import path, mkdir, listdir
|
from os import path, mkdir, listdir, unlink
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
@@ -452,9 +452,6 @@ class NWProject():
|
|||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def getSessionWordCount(self):
|
|
||||||
return self.currWCount - self.lastWCount
|
|
||||||
|
|
||||||
def setStatusColours(self, newCols):
|
def setStatusColours(self, newCols):
|
||||||
replaceMap = self.statusItems.setNewEntries(newCols)
|
replaceMap = self.statusItems.setNewEntries(newCols)
|
||||||
if self.projTree is not None:
|
if self.projTree is not None:
|
||||||
@@ -497,6 +494,9 @@ class NWProject():
|
|||||||
logger.error("No tree item with handle %s" % str(tHandle))
|
logger.error("No tree item with handle %s" % str(tHandle))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def getSessionWordCount(self):
|
||||||
|
return self.currWCount - self.lastWCount
|
||||||
|
|
||||||
def getRootItem(self, tHandle):
|
def getRootItem(self, tHandle):
|
||||||
"""Iterate upwards in the tree until we find the item with
|
"""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
|
parent None, the root item. We do this with a for loop with a
|
||||||
@@ -505,10 +505,13 @@ class NWProject():
|
|||||||
tItem = self.getItem(tHandle)
|
tItem = self.getItem(tHandle)
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
for i in range(200):
|
for i in range(200):
|
||||||
tHandle = tItem.parHandle
|
if tItem.parHandle is None:
|
||||||
tItem = self.getItem(tHandle)
|
|
||||||
if tItem is None:
|
|
||||||
return tHandle
|
return tHandle
|
||||||
|
else:
|
||||||
|
tHandle = tItem.parHandle
|
||||||
|
tItem = self.getItem(tHandle)
|
||||||
|
if tItem is None:
|
||||||
|
return tHandle
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getProjectItems(self):
|
def getProjectItems(self):
|
||||||
@@ -560,6 +563,11 @@ class NWProject():
|
|||||||
"""This only removes the item from the order list, but not from
|
"""This only removes the item from the order list, but not from
|
||||||
the project tree.
|
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.treeOrder.remove(tHandle)
|
||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
|||||||
Reference in New Issue
Block a user