Merge branch 'main' into release_1.0rc1

This commit is contained in:
Veronica K. B. Olsen
2020-11-08 18:11:50 +01:00
11 changed files with 186 additions and 203 deletions
+2 -2
View File
@@ -49,8 +49,8 @@ and the file extension ``.nwd``.
If you wish to find the physical location of a file in the project, you can either look it up in the
project XML file, select :guilabel:`Show File Details` from the :guilabel:`Document` menu when
having the document open, or look in one of the ``ToC`` files in the root of the project folder.
The ``ToC`` files have a list of all document files in the project and where they are saved.
having the document open, or look in the ``ToC.txt`` file in the root of the project folder.
The ``ToC.txt`` file has a list of all document files in the project and where they are saved.
The reason for this cryptic file naming is to avoid issues with file naming conventions and
restrictions on different operating systems, and also to have a file name that does not depend on
-1
View File
@@ -59,7 +59,6 @@ class nwFiles():
PROJ_DICT = "wordlist.txt"
PROJ_LOCK = "nwProject.lock"
TOC_TXT = "ToC.txt"
TOC_JSON = "ToC.json"
SESS_STATS = "sessionStats.log"
INDEX_FILE = "tagsIndex.json"
OPTS_FILE = "guiOptions.json"
+6 -1
View File
@@ -743,7 +743,7 @@ class NWProject():
"""Close the current project and clear all meta data.
"""
self.optState.saveSettings()
self.projTree.writeToCFiles()
self.projTree.writeToCFile()
self._appendSessionStats()
self._clearLockFile()
self.clearProject()
@@ -781,6 +781,10 @@ class NWProject():
def zipIt(self, doNotify):
"""Create a zip file of the entire project.
"""
if not self.theParent.hasProject:
logger.error("No project open")
return False
logger.info("Backing up project")
self.theParent.setStatus("Backing up project ...")
@@ -1476,6 +1480,7 @@ class NWProject():
os.path.join(self.projMeta, "timelineOptions.json"),
os.path.join(self.projMeta, "docMergeOptions.json"),
os.path.join(self.projMeta, "sessionLogOptions.json"),
os.path.join(self.projPath, "ToC.json"),
]
for rmFile in rmList:
+27 -32
View File
@@ -26,7 +26,6 @@
"""
import logging
import json
import os
from lxml import etree
@@ -139,46 +138,42 @@ class NWTree():
return True
def writeToCFiles(self):
"""Write the convenience table of contents files in the root of
the project directory. These files are there to assist the user
if they wish to browse the stored files.
def writeToCFile(self):
"""Write the convenience table of contents file in the root of
the project directory.
"""
tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT)
tocJson = os.path.join(self.theProject.projPath, nwFiles.TOC_JSON)
tocList = []
tocLen = 0
for tHandle in self._treeOrder:
tItem = self.__getitem__(tHandle)
if tItem is None:
continue
tFile = tHandle+".nwd"
if os.path.isfile(os.path.join(self.theProject.projContent, tFile)):
tocLine = "%-25s %-9s %-10s %s" % (
os.path.join("content", tFile),
tItem.itemClass.name,
tItem.itemLayout.name,
tItem.itemName,
)
tocList.append(tocLine)
tocLen = max(tocLen, len(tocLine))
jsonData = []
try:
# Dump the text
tocText = os.path.join(self.theProject.projPath, nwFiles.TOC_TXT)
with open(tocText, mode="w", encoding="utf8") as outFile:
outFile.write("\n")
outFile.write(" Table of Contents\n")
outFile.write("===================\n")
outFile.write("Table of Contents\n")
outFile.write("=================\n")
outFile.write("\n")
outFile.write(" %-25s %-9s %s\n" % ("File Name", "Class", "Document Label"))
outFile.write("-"*80+"\n")
for tHandle in sorted(self._treeOrder):
tItem = self.__getitem__(tHandle)
if tItem is None:
continue
tFile = tHandle+".nwd"
if os.path.isfile(os.path.join(self.theProject.projContent, tFile)):
outFile.write(" %-25s %-9s %s\n" % (
os.path.join("content", tFile),
tItem.itemClass.name,
tItem.itemName,
))
jsonData.append([
os.path.join("content", tFile),
tItem.itemClass.name,
tItem.itemName,
])
outFile.write("%-25s %-9s %-10s %s\n" % (
"File Name", "Class", "Layout", "Document Label"
))
outFile.write("-"*tocLen + "\n")
outFile.write("\n".join(tocList))
outFile.write("\n")
# Dump the JSON
with open(tocJson, mode="w+", encoding="utf8") as outFile:
json.dump(jsonData, outFile, indent=2)
except Exception as e:
logger.error(str(e))
+23 -12
View File
@@ -620,9 +620,10 @@ class GuiDocEditor(QTextEdit):
this class when calling these actions from other classes.
"""
logger.verbose("Requesting action: %s" % theAction.name)
if not self.theParent.hasProject:
logger.error("No project open")
if self.theHandle is None:
logger.error("No document open")
return False
self._allowAutoReplace(False)
if theAction == nwDocAction.UNDO:
self.undo()
@@ -678,7 +679,9 @@ class GuiDocEditor(QTextEdit):
logger.debug("Unknown or unsupported document action %s" % str(theAction))
self._allowAutoReplace(True)
return False
self._allowAutoReplace(True)
return True
def isEmpty(self):
@@ -690,21 +693,29 @@ class GuiDocEditor(QTextEdit):
"""Tell the user where on the file system the file in the editor
is saved.
"""
if self.theHandle is not None:
msgBox = QMessageBox()
msgBox.information(self, "File Location", (
"File details for the currently open file<br>"
"Handle: {handle:s}<br>"
"Location: {fileLoc:s}"
).format(
handle = self.theHandle,
fileLoc = str(self.nwDocument.getFileLocation())
))
if self.theHandle is None:
logger.error("No document open")
return False
msgBox = QMessageBox()
msgBox.information(self, "File Location", (
"File details for the currently open file<br>"
"Handle: {handle:s}<br>"
"Location: {fileLoc:s}"
).format(
handle = self.theHandle,
fileLoc = str(self.nwDocument.getFileLocation())
))
return
def insertText(self, theInsert):
"""Insert a specific type of text at the cursor position.
"""
if self.theHandle is None:
logger.error("No document open")
return False
if isinstance(theInsert, str):
theText = theInsert
elif isinstance(theInsert, nwDocInsert):
+13
View File
@@ -163,6 +163,7 @@ class GuiProjectTree(QTreeWidget):
nHandle = None
if not self.theParent.hasProject:
logger.error("No project open")
return False
# The item needs to be assigned an item class, so one must be
@@ -281,6 +282,10 @@ class GuiProjectTree(QTreeWidget):
"""Move an item up or down in the tree, but only if the treeView
has focus. This also applies when the menu is used.
"""
if not self.theParent.hasProject:
logger.error("No project open")
return False
hasFocus = qApp.focusWidget() == self or not self.mainConf.showGUI
if hasFocus and self.theParent.hasProject:
@@ -364,6 +369,10 @@ class GuiProjectTree(QTreeWidget):
function only asks for confirmation once, and calls the regular
deleteItem function for each document in the Trash folder.
"""
if not self.theParent.hasProject:
logger.error("No project open")
return False
trashHandle = self.theProject.projTree.trashRoot()
logger.debug("Emptying Trash folder")
@@ -409,6 +418,10 @@ class GuiProjectTree(QTreeWidget):
delete the files on disk. Folders are deleted if they're empty only,
and the deletion is always permanent.
"""
if not self.theParent.hasProject:
logger.error("No project open")
return False
if tHandle is None:
tHandle = self.getSelectedHandle()
+93 -45
View File
@@ -316,7 +316,8 @@ class GuiMain(QMainWindow):
if not isYes:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Close Project", "Save changes and close the current project?"
self, "Close Project",
"Close the current project?<br>Changes are saved automatically."
)
if msgRes != QMessageBox.Yes:
return False
@@ -442,6 +443,7 @@ class GuiMain(QMainWindow):
"""Save the current project.
"""
if not self.hasProject:
logger.error("No project open")
return False
# If the project is new, it may not have a path, so we need one
@@ -465,27 +467,33 @@ class GuiMain(QMainWindow):
def closeDocument(self):
"""Close the document and clear the editor and title field.
"""
if self.hasProject:
self.docEditor.saveCursorPosition()
if self.docEditor.docChanged:
self.saveDocument()
self.docEditor.clearEditor()
if not self.hasProject:
logger.error("No project open")
return False
self.docEditor.saveCursorPosition()
if self.docEditor.docChanged:
self.saveDocument()
self.docEditor.clearEditor()
return True
def openDocument(self, tHandle, tLine=None, changeFocus=True, doScroll=False):
"""Open a specific document, optionally at a given line.
"""
if self.hasProject:
self.closeDocument()
self.tabWidget.setCurrentWidget(self.splitDocs)
if self.docEditor.loadText(tHandle, tLine):
if changeFocus:
self.docEditor.setFocus()
self.theProject.setLastEdited(tHandle)
self.treeView.setSelectedHandle(tHandle, doScroll=doScroll)
else:
return False
if not self.hasProject:
logger.error("No project open")
return False
self.closeDocument()
self.tabWidget.setCurrentWidget(self.splitDocs)
if self.docEditor.loadText(tHandle, tLine):
if changeFocus:
self.docEditor.setFocus()
self.theProject.setLastEdited(tHandle)
self.treeView.setSelectedHandle(tHandle, doScroll=doScroll)
else:
return False
return True
@@ -493,43 +501,54 @@ class GuiMain(QMainWindow):
"""Opens the next document in the project tree, following the
document with the given handle. Stops when reaching the end.
"""
if self.hasProject:
self.treeView.flushTreeOrder()
nHandle = None # The next handle after tHandle
fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
for tItem in self.theProject.projTree:
if tItem is None:
continue
if tItem.itemType != nwItemType.FILE:
continue
if fHandle is None:
fHandle = tItem.itemHandle
if tItem.itemHandle == tHandle:
foundIt = True
elif foundIt:
nHandle = tItem.itemHandle
break
if not self.hasProject:
logger.error("No project open")
return False
if nHandle is not None:
self.openDocument(nHandle, tLine=0, doScroll=True)
return True
elif wrapAround:
self.openDocument(fHandle, tLine=0, doScroll=True)
return False
self.treeView.flushTreeOrder()
nHandle = None # The next handle after tHandle
fHandle = None # The first file handle we encounter
foundIt = False # We've found tHandle, pick the next we see
for tItem in self.theProject.projTree:
if tItem is None:
continue
if tItem.itemType != nwItemType.FILE:
continue
if fHandle is None:
fHandle = tItem.itemHandle
if tItem.itemHandle == tHandle:
foundIt = True
elif foundIt:
nHandle = tItem.itemHandle
break
if nHandle is not None:
self.openDocument(nHandle, tLine=0, doScroll=True)
return True
elif wrapAround:
self.openDocument(fHandle, tLine=0, doScroll=True)
return False
return False
def saveDocument(self):
"""Save the current documents.
"""
if self.hasProject:
self.docEditor.saveText()
if not self.hasProject:
logger.error("No project open")
return False
self.docEditor.saveText()
return True
def viewDocument(self, tHandle=None, tAnchor=None):
"""Load a document for viewing in the view panel.
"""
if not self.hasProject:
logger.error("No project open")
return False
if tHandle is None:
logger.debug("Viewing document, but no handle provided")
@@ -573,8 +592,11 @@ class GuiMain(QMainWindow):
"""Import the text contained in an out-of-project text file, and
insert the text into the currently open document.
"""
lastPath = self.mainConf.lastPath
if not self.hasProject:
logger.error("No project open")
return False
lastPath = self.mainConf.lastPath
extFilter = [
"Text files (*.txt)",
"Markdown files (*.md)",
@@ -627,16 +649,26 @@ class GuiMain(QMainWindow):
def mergeDocuments(self):
"""Merge multiple documents to one single new document.
"""
if not self.hasProject:
logger.error("No project open")
return False
dlgMerge = GuiDocMerge(self, self.theProject)
dlgMerge.exec_()
return
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, self.theProject)
dlgSplit.exec_()
return
return True
def passDocumentAction(self, theAction):
"""Pass on document action theAction to the document viewer if
@@ -655,6 +687,10 @@ class GuiMain(QMainWindow):
def openSelectedItem(self):
"""Open the selected documents.
"""
if not self.hasProject:
logger.error("No project open")
return False
tHandle = self.treeView.getSelectedHandle()
if tHandle is None:
logger.warning("No item selected")
@@ -673,6 +709,10 @@ class GuiMain(QMainWindow):
def editItem(self, tHandle=None):
"""Open the edit item dialog.
"""
if not self.hasProject:
logger.error("No project open")
return False
if tHandle is None:
tHandle = self.treeView.getSelectedHandle()
if tHandle is None:
@@ -703,6 +743,7 @@ class GuiMain(QMainWindow):
"""Rebuild the entire index.
"""
if not self.hasProject:
logger.error("No project open")
return False
logger.debug("Rebuilding index ...")
@@ -748,9 +789,14 @@ class GuiMain(QMainWindow):
def rebuildOutline(self):
"""Force a rebuild of the Outline view.
"""
if not self.hasProject:
logger.error("No project open")
return False
logger.verbose("Forcing a rebuild of the Project Outline")
self.tabWidget.setCurrentWidget(self.splitOutline)
self.projView.refreshTree(overRide=True)
return True
##
@@ -943,7 +989,8 @@ class GuiMain(QMainWindow):
if self.hasProject:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Exit", "Do you want to save changes and exit?"
self, "Exit",
"Do you want to exit novelWriter?<br>Changes are saved automatically."
)
if msgRes != QMessageBox.Yes:
return False
@@ -1008,6 +1055,7 @@ class GuiMain(QMainWindow):
"""
if self.docEditor.theHandle is None:
logger.error("No document open, so not activating Focus Mode")
self.mainMenu.aFocusMode.setChecked(self.isFocusMode)
return False
self.isFocusMode = not self.isFocusMode
-87
View File
@@ -1,87 +0,0 @@
[
[
"content/14298de4d9524.nwd",
"CHARACTER",
"John Smith"
],
[
"content/53b69b83cdafc.nwd",
"NOVEL",
"Title Page"
],
[
"content/5eaea4e8cdee8.nwd",
"WORLD",
"Mars"
],
[
"content/636b6aa9b697b.nwd",
"NOVEL",
"Making a Scene"
],
[
"content/6a2d6d5f4f401.nwd",
"NOVEL",
"Chapter One"
],
[
"content/88706ddc78b1b.nwd",
"NOVEL",
"Chapter Two"
],
[
"content/8a5deb88c0e97.nwd",
"NOVEL",
"Old File"
],
[
"content/96b68994dfa3d.nwd",
"NOVEL",
"A Note on Structure"
],
[
"content/974e400180a99.nwd",
"NOVEL",
"Page"
],
[
"content/ae7339df26ded.nwd",
"NOVEL",
"We Found John!"
],
[
"content/b3e74dbc1f584.nwd",
"WORLD",
"Earth"
],
[
"content/b8136a5a774a0.nwd",
"NOVEL",
"Delete Me!"
],
[
"content/ba8a28a246524.nwd",
"NOVEL",
"Interlude"
],
[
"content/bb2c23b3c42cc.nwd",
"CHARACTER",
"Jane Smith"
],
[
"content/bc0cbd2a407f3.nwd",
"NOVEL",
"Another Scene"
],
[
"content/edca4be2fcaf8.nwd",
"NOVEL",
"Part One"
],
[
"content/f1471bef9f2ae.nwd",
"WORLD",
"Space"
]
]
+21 -22
View File
@@ -1,24 +1,23 @@
Table of Contents
===================
File Name Class Document Label
--------------------------------------------------------------------------------
content/14298de4d9524.nwd CHARACTER John Smith
content/53b69b83cdafc.nwd NOVEL Title Page
content/5eaea4e8cdee8.nwd WORLD Mars
content/636b6aa9b697b.nwd NOVEL Making a Scene
content/6a2d6d5f4f401.nwd NOVEL Chapter One
content/88706ddc78b1b.nwd NOVEL Chapter Two
content/8a5deb88c0e97.nwd NOVEL Old File
content/96b68994dfa3d.nwd NOVEL A Note on Structure
content/974e400180a99.nwd NOVEL Page
content/ae7339df26ded.nwd NOVEL We Found John!
content/b3e74dbc1f584.nwd WORLD Earth
content/b8136a5a774a0.nwd NOVEL Delete Me!
content/ba8a28a246524.nwd NOVEL Interlude
content/bb2c23b3c42cc.nwd CHARACTER Jane Smith
content/bc0cbd2a407f3.nwd NOVEL Another Scene
content/edca4be2fcaf8.nwd NOVEL Part One
content/f1471bef9f2ae.nwd WORLD Space
Table of Contents
=================
File Name Class Layout Document Label
---------------------------------------------------------------------
content/53b69b83cdafc.nwd NOVEL TITLE Title Page
content/974e400180a99.nwd NOVEL PAGE Page
content/edca4be2fcaf8.nwd NOVEL PARTITION Part One
content/6a2d6d5f4f401.nwd NOVEL CHAPTER Chapter One
content/636b6aa9b697b.nwd NOVEL SCENE Making a Scene
content/bc0cbd2a407f3.nwd NOVEL SCENE Another Scene
content/ba8a28a246524.nwd NOVEL UNNUMBERED Interlude
content/96b68994dfa3d.nwd NOVEL NOTE A Note on Structure
content/88706ddc78b1b.nwd NOVEL CHAPTER Chapter Two
content/ae7339df26ded.nwd NOVEL SCENE We Found John!
content/14298de4d9524.nwd CHARACTER NOTE John Smith
content/bb2c23b3c42cc.nwd CHARACTER NOTE Jane Smith
content/b3e74dbc1f584.nwd WORLD NOTE Earth
content/f1471bef9f2ae.nwd WORLD NOTE Space
content/5eaea4e8cdee8.nwd WORLD NOTE Mars
content/8a5deb88c0e97.nwd NOVEL SCENE Old File
content/b8136a5a774a0.nwd NOVEL SCENE Delete Me!
+1
View File
@@ -6,6 +6,7 @@ class DummyMain():
def __init__(self):
self.mainConf = None
self.hasProject = True
self.statusBar = StatusBar()
return
-1
View File
@@ -627,7 +627,6 @@ def testProjectOldFormat(nwDummy, nwOldProj):
# Check that new files have been created
assert os.path.isfile(os.path.join(nwOldProj, "meta", "guiOptions.json"))
assert os.path.isfile(os.path.join(nwOldProj, "meta", "sessionStats.log"))
assert os.path.isfile(os.path.join(nwOldProj, "ToC.json"))
assert os.path.isfile(os.path.join(nwOldProj, "ToC.txt"))
@pytest.mark.project