Add a custom text document in the editor
This commit is contained in:
@@ -56,6 +56,7 @@ from novelwriter.common import minmax, transferCase
|
||||
from novelwriter.constants import nwKeyWords, nwUnicode
|
||||
from novelwriter.core.index import countWords
|
||||
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
||||
from novelwriter.gui.editordocument import GuiTextDocument
|
||||
from novelwriter.extensions.wheeleventfilter import WheelEventFilter
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
@@ -120,9 +121,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self._typPadBefore = ""
|
||||
self._typPadAfter = ""
|
||||
|
||||
# Core Elements and Signals
|
||||
qDoc = self.document()
|
||||
qDoc.contentsChange.connect(self._docChange)
|
||||
# Create Custom Document
|
||||
self._qDocument = GuiTextDocument(self)
|
||||
self.setDocument(self._qDocument)
|
||||
|
||||
# Connect Signals
|
||||
self._qDocument.contentsChange.connect(self._docChange)
|
||||
self.selectionChanged.connect(self._updateSelectedStatus)
|
||||
|
||||
# Document Title
|
||||
@@ -130,9 +134,6 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.docFooter = GuiDocEditFooter(self)
|
||||
self.docSearch = GuiDocEditSearch(self)
|
||||
|
||||
# Syntax
|
||||
self.highLight = GuiDocHighlighter(qDoc)
|
||||
|
||||
# Context Menu
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self._openContextMenu)
|
||||
@@ -212,7 +213,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
@property
|
||||
def isEmpty(self) -> bool:
|
||||
"""Check if the current document is empty."""
|
||||
return self.document().isEmpty()
|
||||
return self._qDocument.isEmpty()
|
||||
|
||||
##
|
||||
# Methods
|
||||
@@ -266,7 +267,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.docHeader.matchColours()
|
||||
self.docFooter.matchColours()
|
||||
|
||||
self.highLight.initHighlighter()
|
||||
self._qDocument.syntaxHighlighter.initHighlighter()
|
||||
|
||||
return
|
||||
|
||||
@@ -312,8 +313,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
# Due to cursor visibility, a part of the margin must be
|
||||
# allocated to the document itself. See issue #1112.
|
||||
cW = 2*self.cursorWidth()
|
||||
qDoc = self.document()
|
||||
qDoc.setDocumentMargin(cW)
|
||||
self._qDocument.setDocumentMargin(cW)
|
||||
self._vpMargin = max(CONFIG.getTextMargin() - cW, 0)
|
||||
self.setViewportMargins(self._vpMargin, self._vpMargin, self._vpMargin, self._vpMargin)
|
||||
|
||||
@@ -327,7 +327,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
if CONFIG.showLineEndings:
|
||||
theOpt.setFlags(theOpt.flags() | QTextOption.ShowLineAndParagraphSeparators)
|
||||
|
||||
qDoc.setDefaultTextOption(theOpt)
|
||||
self._qDocument.setDefaultTextOption(theOpt)
|
||||
|
||||
# Scroll bars
|
||||
if CONFIG.hideVScroll:
|
||||
@@ -377,13 +377,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
self._docHandle = tHandle
|
||||
self.highLight.setHandle(tHandle)
|
||||
|
||||
tStart = time()
|
||||
self._allowAutoReplace(False)
|
||||
self.setPlainText(docText)
|
||||
self._qDocument.setTextContent(docText, tHandle)
|
||||
self._allowAutoReplace(True)
|
||||
logger.debug("Document text loaded in %.3f ms", 1000*(time() - tStart))
|
||||
logger.debug("Document text set in %.3f ms", 1000*(time() - tStart))
|
||||
qApp.processEvents()
|
||||
|
||||
self._lastEdit = time()
|
||||
@@ -404,14 +403,15 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.docFooter.updateLineCount()
|
||||
|
||||
# This is a hack to fix invisible cursor on an empty document
|
||||
if self.document().characterCount() <= 1:
|
||||
if self._qDocument.characterCount() <= 1:
|
||||
self.setPlainText("\n")
|
||||
self.setPlainText("")
|
||||
self.setCursorPosition(0)
|
||||
|
||||
qApp.processEvents()
|
||||
self.document().clearUndoRedoStacks()
|
||||
self.setDocumentChanged(False)
|
||||
self._qDocument.clearUndoRedoStacks()
|
||||
|
||||
qApp.restoreOverrideCursor()
|
||||
|
||||
# Update the status bar
|
||||
@@ -422,12 +422,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
def updateTagHighLighting(self) -> None:
|
||||
"""Rerun the syntax highlighter on all meta data lines."""
|
||||
self.highLight.rehighlightByType(GuiDocHighlighter.BLOCK_META)
|
||||
self._qDocument.syntaxHighlighter.rehighlightByType(GuiDocHighlighter.BLOCK_META)
|
||||
return
|
||||
|
||||
def redrawText(self) -> None:
|
||||
"""Redraw the text by marking the document content as dirty."""
|
||||
self.document().markContentsDirty(0, self.document().characterCount())
|
||||
self._qDocument.markContentsDirty(0, self._qDocument.characterCount())
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
@@ -561,7 +561,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
paragraph and line separators though.
|
||||
See: https://doc.qt.io/qt-5/qtextdocument.html#toPlainText
|
||||
"""
|
||||
text = self.document().toRawText()
|
||||
text = self._qDocument.toRawText()
|
||||
text = text.replace(nwUnicode.U_LSEP, "\n") # Line separators
|
||||
text = text.replace(nwUnicode.U_PSEP, "\n") # Paragraph separators
|
||||
return text
|
||||
@@ -586,7 +586,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
def setCursorPosition(self, position: int) -> None:
|
||||
"""Move the cursor to a given position in the document."""
|
||||
nChars = self.document().characterCount()
|
||||
nChars = self._qDocument.characterCount()
|
||||
if nChars > 1 and isinstance(position, int):
|
||||
cursor = self.textCursor()
|
||||
cursor.setPosition(minmax(position, 0, nChars-1))
|
||||
@@ -605,7 +605,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
def setCursorLine(self, line: int | None) -> None:
|
||||
"""Move the cursor to a given line in the document."""
|
||||
if isinstance(line, int) and line > 0:
|
||||
block = self.document().findBlockByNumber(line - 1)
|
||||
block = self._qDocument.findBlockByNumber(line - 1)
|
||||
if block:
|
||||
self.setCursorPosition(block.position())
|
||||
logger.debug("Cursor moved to line %d", line)
|
||||
@@ -638,7 +638,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self._spellCheck = state
|
||||
self.mainGui.mainMenu.setSpellCheck(state)
|
||||
SHARED.project.data.setSpellCheck(state)
|
||||
self.highLight.setSpellCheck(state)
|
||||
self._qDocument.syntaxHighlighter.setSpellCheck(state)
|
||||
if state is False:
|
||||
self.spellCheckDocument()
|
||||
|
||||
@@ -655,7 +655,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
logger.debug("Running spell checker")
|
||||
start = time()
|
||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
self.highLight.rehighlight()
|
||||
self._qDocument.syntaxHighlighter.rehighlight()
|
||||
qApp.restoreOverrideCursor()
|
||||
logger.debug("Document highlighted in %.3f ms", 1000*(time() - start))
|
||||
self.statusMessage.emit(self.tr("Spell check complete"))
|
||||
@@ -994,7 +994,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.wcTimerDoc.start()
|
||||
|
||||
if self._doReplace and added == 1:
|
||||
self._docAutoReplace(self.document().findBlock(pos))
|
||||
self._docAutoReplace(self._qDocument.findBlock(pos))
|
||||
|
||||
return
|
||||
|
||||
@@ -1122,7 +1122,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
theWord = cursor.selectedText().strip().strip(self._nonWord)
|
||||
logger.debug("Added '%s' to project dictionary", theWord)
|
||||
SHARED.spelling.addWord(theWord)
|
||||
self.highLight.rehighlightBlock(cursor.block())
|
||||
self._qDocument.syntaxHighlighter.rehighlightBlock(cursor.block())
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -1418,8 +1418,8 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
posS = theCursor.selectionStart()
|
||||
posE = theCursor.selectionEnd()
|
||||
|
||||
blockS = self.document().findBlock(posS)
|
||||
blockE = self.document().findBlock(posE)
|
||||
blockS = self._qDocument.findBlock(posS)
|
||||
blockE = self._qDocument.findBlock(posE)
|
||||
|
||||
if blockS != blockE:
|
||||
posE = blockS.position() + blockS.length() - 1
|
||||
@@ -1430,14 +1430,14 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
numB = 0
|
||||
for n in range(fLen):
|
||||
if self.document().characterAt(posS-n-1) == fChar:
|
||||
if self._qDocument.characterAt(posS-n-1) == fChar:
|
||||
numB += 1
|
||||
else:
|
||||
break
|
||||
|
||||
numA = 0
|
||||
for n in range(fLen):
|
||||
if self.document().characterAt(posE+n) == fChar:
|
||||
if self._qDocument.characterAt(posE+n) == fChar:
|
||||
numA += 1
|
||||
else:
|
||||
break
|
||||
@@ -1487,9 +1487,8 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
posS = theCursor.selectionStart()
|
||||
posE = theCursor.selectionEnd()
|
||||
|
||||
qDoc = self.document()
|
||||
blockS = qDoc.findBlock(posS)
|
||||
blockE = qDoc.findBlock(posE)
|
||||
blockS = self._qDocument.findBlock(posS)
|
||||
blockE = self._qDocument.findBlock(posE)
|
||||
if blockS != blockE:
|
||||
posE = blockS.position() + blockS.length() - 1
|
||||
|
||||
@@ -1690,16 +1689,15 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
def _removeInParLineBreaks(self) -> None:
|
||||
"""Strip line breaks within paragraphs in the selected text."""
|
||||
theCursor = self.textCursor()
|
||||
theDoc = self.document()
|
||||
cursor = self.textCursor()
|
||||
|
||||
iS = 0
|
||||
iE = theDoc.blockCount() - 1
|
||||
iE = self._qDocument.blockCount() - 1
|
||||
rS = 0
|
||||
rE = theDoc.characterCount()
|
||||
if theCursor.hasSelection():
|
||||
sBlock = theDoc.findBlock(theCursor.selectionStart())
|
||||
eBlock = theDoc.findBlock(theCursor.selectionEnd())
|
||||
rE = self._qDocument.characterCount()
|
||||
if cursor.hasSelection():
|
||||
sBlock = self._qDocument.findBlock(cursor.selectionStart())
|
||||
eBlock = self._qDocument.findBlock(cursor.selectionEnd())
|
||||
iS = sBlock.blockNumber()
|
||||
iE = eBlock.blockNumber()
|
||||
rS = sBlock.position()
|
||||
@@ -1709,7 +1707,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
currPar = []
|
||||
cleanText = ""
|
||||
for i in range(iS, iE+1):
|
||||
cBlock = theDoc.findBlockByNumber(i)
|
||||
cBlock = self._qDocument.findBlockByNumber(i)
|
||||
cText = cBlock.text()
|
||||
if cText.strip() == "":
|
||||
if currPar:
|
||||
@@ -1726,12 +1724,12 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
cleanText += " ".join(currPar) + "\n\n"
|
||||
|
||||
# Replace the text with the cleaned up text
|
||||
theCursor.beginEditBlock()
|
||||
theCursor.clearSelection()
|
||||
theCursor.setPosition(rS)
|
||||
theCursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, rE-rS)
|
||||
theCursor.insertText(cleanText.rstrip() + "\n")
|
||||
theCursor.endEditBlock()
|
||||
cursor.beginEditBlock()
|
||||
cursor.clearSelection()
|
||||
cursor.setPosition(rS)
|
||||
cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, rE-rS)
|
||||
cursor.insertText(cleanText.rstrip() + "\n")
|
||||
cursor.endEditBlock()
|
||||
|
||||
return
|
||||
|
||||
@@ -1913,11 +1911,10 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
# Underscore counts as a part of the word, so check that the
|
||||
# selection isn't wrapped in italics markers.
|
||||
reSelect = False
|
||||
qDoc = self.document()
|
||||
if qDoc.characterAt(posS) == "_":
|
||||
if self._qDocument.characterAt(posS) == "_":
|
||||
posS += 1
|
||||
reSelect = True
|
||||
if qDoc.characterAt(posE) == "_":
|
||||
if self._qDocument.characterAt(posE) == "_":
|
||||
posE -= 1
|
||||
reSelect = True
|
||||
if reSelect:
|
||||
@@ -2838,7 +2835,7 @@ class GuiDocEditFooter(QWidget):
|
||||
else:
|
||||
theCursor = self.docEditor.textCursor()
|
||||
iLine = theCursor.blockNumber() + 1
|
||||
iDist = 100*iLine/self.docEditor.document().blockCount()
|
||||
iDist = 100*iLine/self.docEditor._qDocument.blockCount()
|
||||
self.linesText.setText(
|
||||
self.tr("Line: {0} ({1})").format(f"{iLine:n}", f"{iDist:.0f} %")
|
||||
)
|
||||
@@ -2869,7 +2866,7 @@ class GuiDocEditFooter(QWidget):
|
||||
self.tr("Words: {0} ({1})").format(f"{wCount:n}", f"{wDiff:+n}")
|
||||
)
|
||||
|
||||
byteSize = self.docEditor.document().characterCount()
|
||||
byteSize = self.docEditor._qDocument.characterCount()
|
||||
self.wordsText.setToolTip(
|
||||
self.tr("Document size is {0} bytes").format(f"{byteSize:n}")
|
||||
)
|
||||
|
||||
@@ -51,6 +51,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
logger.debug("Create: GuiDocHighlighter")
|
||||
|
||||
self._tItem = None
|
||||
self._tHandle = None
|
||||
self._spellCheck = False
|
||||
self._spellRx = QRegularExpression()
|
||||
@@ -79,11 +80,6 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def spellCheck(self) -> bool:
|
||||
"""Check if spell checking is enabled."""
|
||||
return self._spellCheck
|
||||
|
||||
def initHighlighter(self) -> None:
|
||||
"""Initialise the syntax highlighter, setting all the colour
|
||||
rules and building the RegExes.
|
||||
@@ -248,6 +244,11 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
def setHandle(self, tHandle: str) -> None:
|
||||
"""Set the handle of the currently highlighted document."""
|
||||
self._tHandle = tHandle
|
||||
self._tItem = SHARED.project.tree[tHandle]
|
||||
logger.debug(
|
||||
"Syntax highlighter %s for item '%s'",
|
||||
"enabled" if self._tItem else "disabled", tHandle
|
||||
)
|
||||
return
|
||||
|
||||
##
|
||||
@@ -284,27 +285,24 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
if text.startswith("@"): # Keywords and commands
|
||||
self.setCurrentBlockState(self.BLOCK_META)
|
||||
pIndex = SHARED.project.index
|
||||
tItem = SHARED.project.tree[self._tHandle]
|
||||
if tItem is None:
|
||||
return
|
||||
|
||||
isValid, theBits, thePos = pIndex.scanThis(text)
|
||||
isGood = pIndex.checkThese(theBits, tItem)
|
||||
if isValid:
|
||||
for n, theBit in enumerate(theBits):
|
||||
xPos = thePos[n]
|
||||
xLen = len(theBit)
|
||||
if isGood[n]:
|
||||
if n == 0:
|
||||
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
||||
if self._tItem:
|
||||
pIndex = SHARED.project.index
|
||||
isValid, theBits, thePos = pIndex.scanThis(text)
|
||||
isGood = pIndex.checkThese(theBits, self._tItem)
|
||||
if isValid:
|
||||
for n, theBit in enumerate(theBits):
|
||||
xPos = thePos[n]
|
||||
xLen = len(theBit)
|
||||
if isGood[n]:
|
||||
if n == 0:
|
||||
self.setFormat(xPos, xLen, self._hStyles["keyword"])
|
||||
else:
|
||||
self.setFormat(xPos, xLen, self._hStyles["value"])
|
||||
else:
|
||||
self.setFormat(xPos, xLen, self._hStyles["value"])
|
||||
else:
|
||||
kwFmt = self.format(xPos)
|
||||
kwFmt.setUnderlineColor(self._colError)
|
||||
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||
self.setFormat(xPos, xLen, kwFmt)
|
||||
kwFmt = self.format(xPos)
|
||||
kwFmt.setUnderlineColor(self._colError)
|
||||
kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
|
||||
self.setFormat(xPos, xLen, kwFmt)
|
||||
|
||||
# We never want to run the spell checker on keyword/values,
|
||||
# so we force a return here
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
novelWriter – GUI Text Document
|
||||
===============================
|
||||
|
||||
File History:
|
||||
Created: 2023-09-07 [2.2b1]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2023, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from PyQt5.QtCore import QObject
|
||||
|
||||
from PyQt5.QtGui import QTextDocument
|
||||
from PyQt5.QtWidgets import QPlainTextDocumentLayout
|
||||
|
||||
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiTextDocument(QTextDocument):
|
||||
|
||||
def __init__(self, parent: QObject) -> None:
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._handle = None
|
||||
self._syntax = GuiDocHighlighter(self)
|
||||
self.setDocumentLayout(QPlainTextDocumentLayout(self))
|
||||
|
||||
logger.debug("Ready: GuiTextDocument")
|
||||
|
||||
return
|
||||
|
||||
def __del__(self): # pragma: no cover
|
||||
logger.debug("Delete: GuiTextDocument")
|
||||
return
|
||||
|
||||
@property
|
||||
def syntaxHighlighter(self) -> GuiDocHighlighter:
|
||||
return self._syntax
|
||||
|
||||
def setTextContent(self, text: str, tHandle: str) -> None:
|
||||
"""Set the text content of the document."""
|
||||
self._syntax.setHandle(tHandle)
|
||||
self.setPlainText(text)
|
||||
return
|
||||
|
||||
# END Class GuiTextDocument
|
||||
@@ -621,10 +621,10 @@ class GuiMain(QMainWindow):
|
||||
break
|
||||
|
||||
if nHandle is not None:
|
||||
self.openDocument(nHandle, tLine=0, doScroll=True)
|
||||
self.openDocument(nHandle, tLine=1, doScroll=True)
|
||||
return True
|
||||
elif wrapAround:
|
||||
self.openDocument(fHandle, tLine=0, doScroll=True)
|
||||
self.openDocument(fHandle, tLine=1, doScroll=True)
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
%%~name: Making a Scene
|
||||
%%~path: 6a2d6d5f4f401/636b6aa9b697b
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: 053cc65631403c15ddc112849dc7fcae44eb9d63
|
||||
%%~date: Unknown/2023-08-25 16:56:11
|
||||
%%~hash: 7aae771de46c3cab06d8be0e860dc0bd383860a5
|
||||
%%~date: Unknown/2023-09-07 19:00:10
|
||||
### Making a Scene
|
||||
|
||||
@pov: Jane
|
||||
@@ -15,7 +15,7 @@ Each paragraph in the scene is separated by a blank line. The text supports mini
|
||||
|
||||
In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter settings and colour theme, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.”
|
||||
|
||||
If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, let’s auto-replace this A with <A>, and this C with <C>. While <E> is just <E>. Press Ctrl+R to see what this looks like in the view pane. The list of auto-replaced text is sett in Project Settings.
|
||||
If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, let’s auto-replace this A with <A>, and this C with <C>. While <E> is just <E>. Press Ctrl+R to see what this looks like in the view pane. The list of auto-replaced text is set in Project Settings.
|
||||
|
||||
The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. Regular dashes are also supported – and can be automatically inserted when typing two hyphens.
|
||||
|
||||
|
||||
+14
-14
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.2-alpha1" hexVersion="0x020200a1" fileVersion="1.5" fileRevision="1" timeStamp="2023-09-01 20:48:55">
|
||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1522" autoCount="237" editTime="75353">
|
||||
<novelWriterXML appVersion="2.2-alpha1" hexVersion="0x020200a1" fileVersion="1.5" fileRevision="1" timeStamp="2023-09-07 19:14:50">
|
||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="1531" autoCount="238" editTime="75619">
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
@@ -58,27 +58,27 @@
|
||||
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
|
||||
</item>
|
||||
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="66" />
|
||||
<meta expanded="no" heading="H3" charCount="2686" wordCount="479" paraCount="14" cursorPos="19" />
|
||||
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
|
||||
</item>
|
||||
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465" />
|
||||
<meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="531" />
|
||||
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
|
||||
</item>
|
||||
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310" />
|
||||
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="0" />
|
||||
<name status="s78ea90" import="ia857f0" active="yes">Interlude</name>
|
||||
</item>
|
||||
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0" />
|
||||
<meta expanded="no" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="1940" />
|
||||
<name status="sf24ce6" import="ia857f0" active="no">A Note on Structure</name>
|
||||
</item>
|
||||
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="yes" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188" />
|
||||
<meta expanded="yes" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="356" />
|
||||
<name status="s90e6c9" import="ia857f0" active="yes">Chapter Two</name>
|
||||
</item>
|
||||
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0" />
|
||||
<meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="237" />
|
||||
<name status="s90e6c9" import="ia857f0" active="yes">We Found John!</name>
|
||||
</item>
|
||||
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
|
||||
@@ -90,7 +90,7 @@
|
||||
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
|
||||
</item>
|
||||
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104" />
|
||||
<meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="387" />
|
||||
<name status="s90e6c9" import="ia857f0" active="yes">Chapter One</name>
|
||||
</item>
|
||||
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
|
||||
@@ -102,11 +102,11 @@
|
||||
<name status="sf12341" import="ia857f0">Main Characters</name>
|
||||
</item>
|
||||
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24" />
|
||||
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="65" />
|
||||
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name>
|
||||
</item>
|
||||
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25" />
|
||||
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="71" />
|
||||
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name>
|
||||
</item>
|
||||
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
|
||||
@@ -114,15 +114,15 @@
|
||||
<name status="sf12341" import="ia857f0">Locations</name>
|
||||
</item>
|
||||
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20" />
|
||||
<meta expanded="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="111" />
|
||||
<name status="sf12341" import="i56be10" active="yes">Earth</name>
|
||||
</item>
|
||||
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133" />
|
||||
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="135" />
|
||||
<name status="sf12341" import="icfb3a5" active="yes">Space</name>
|
||||
</item>
|
||||
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45" />
|
||||
<meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="62" />
|
||||
<name status="sf12341" import="i2d7a54" active="yes">Mars</name>
|
||||
</item>
|
||||
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
|
||||
|
||||
Reference in New Issue
Block a user