diff --git a/nw/config.py b/nw/config.py index 18f2bc8e..2e0baacb 100644 --- a/nw/config.py +++ b/nw/config.py @@ -62,6 +62,8 @@ class Config: ## General self.guiTheme = "default" self.guiSyntax = "default_light" + + ## Sizes self.winGeometry = [1100, 650] self.treeColWidth = [120, 30, 50] self.mainPanePos = [300, 800] @@ -74,7 +76,7 @@ class Config: ## Text Editor self.textFont = None self.textSize = 12 - self.textFixedW = False + self.textFixedW = True self.textWidth = 600 self.textMargin = 40 self.tabWidth = 40 @@ -94,12 +96,16 @@ class Config: self.spellLanguage = "en_GB" - # Backup + ## Backup self.backupPath = "" self.backupOnClose = False self.askBeforeBackup = True - # Path + ## State + self.showRefPanel = True + self.viewComments = True + + ## Path self.recentList = [""]*10 # Check Qt5 Versions @@ -244,6 +250,11 @@ class Config: self.backupOnClose = self._parseLine(cnfParse, cnfSec, "backuponclose", self.CNF_BOOL, self.backupOnClose) self.askBeforeBackup = self._parseLine(cnfParse, cnfSec, "askbeforebackup", self.CNF_BOOL, self.askBeforeBackup) + ## State + cnfSec = "State" + self.showRefPanel = self._parseLine(cnfParse, cnfSec, "showrefpanel", self.CNF_BOOL, self.showRefPanel) + self.viewComments = self._parseLine(cnfParse, cnfSec, "viewcomments", self.CNF_BOOL, self.viewComments) + ## Path cnfSec = "Path" self.lastPath = self._parseLine(cnfParse, cnfSec, "lastpath", self.CNF_STR, self.lastPath) @@ -312,6 +323,12 @@ class Config: cnfParse.set(cnfSec,"backuponclose", str(self.backupOnClose)) cnfParse.set(cnfSec,"askbeforebackup",str(self.askBeforeBackup)) + ## State + cnfSec = "State" + cnfParse.add_section(cnfSec) + cnfParse.set(cnfSec,"showrefpanel",str(self.showRefPanel)) + cnfParse.set(cnfSec,"viewcomments",str(self.viewComments)) + ## Path cnfSec = "Path" cnfParse.add_section(cnfSec) @@ -385,6 +402,16 @@ class Config: self.confChanged = True return True + def setShowRefPanel(self, checkState): + self.showRefPanel = checkState + self.confChanged = True + return + + def setViewComments(self, checkState): + self.viewComments = checkState + self.confChanged = True + return + ## # Internal Functions ## diff --git a/nw/constants.py b/nw/constants.py index 51bde4e5..7919ba8c 100644 --- a/nw/constants.py +++ b/nw/constants.py @@ -32,6 +32,20 @@ class nwFiles(): # END Class nwFiles +class nwKeyWords: + + TAG_KEY = "@tag" + POV_KEY = "@pov" + CHAR_KEY = "@char" + PLOT_KEY = "@plot" + TIME_KEY = "@time" + WORLD_KEY = "@location" + OBJECT_KEY = "@object" + ENTITY_KEY = "@entity" + CUSTOM_KEY = "@custom" + +# END Class nwKeyWords + class nwLabels(): CLASS_NAME = { @@ -42,6 +56,7 @@ class nwLabels(): nwItemClass.WORLD : "Locations", nwItemClass.TIMELINE : "Timeline", nwItemClass.OBJECT : "Objects", + nwItemClass.ENTITY : "Entity", nwItemClass.CUSTOM : "Custom", nwItemClass.TRASH : "Trash", } @@ -53,6 +68,7 @@ class nwLabels(): nwItemClass.WORLD : "L", nwItemClass.TIMELINE : "T", nwItemClass.OBJECT : "O", + nwItemClass.ENTITY : "E", nwItemClass.CUSTOM : "X", nwItemClass.TRASH : "R", } @@ -78,6 +94,17 @@ class nwLabels(): nwItemLayout.SCENE : "Sc", nwItemLayout.NOTE : "Nt", } + KEY_NAME = { + nwKeyWords.TAG_KEY : "Tag", + nwKeyWords.POV_KEY : "Point of View", + nwKeyWords.CHAR_KEY : "Characters", + nwKeyWords.PLOT_KEY : "Plot", + nwKeyWords.TIME_KEY : "Time", + nwKeyWords.WORLD_KEY : "Locations", + nwKeyWords.OBJECT_KEY : "Objects", + nwKeyWords.ENTITY_KEY : "Entities", + nwKeyWords.CUSTOM_KEY : "Custom", + } # END Class nwLabels diff --git a/nw/convert/text/tohtml.py b/nw/convert/text/tohtml.py index c8615f75..fae40df4 100644 --- a/nw/convert/text/tohtml.py +++ b/nw/convert/text/tohtml.py @@ -15,7 +15,7 @@ import re import nw from nw.convert.tokenizer import Tokenizer -from nw.constants import nwUnicode +from nw.constants import nwUnicode, nwLabels logger = logging.getLogger(__name__) @@ -26,7 +26,7 @@ class ToHtml(Tokenizer): self.forPreview = False return - def setPreview(self, forPreview): + def setPreview(self, forPreview, doComments): """If we're using this class to generate markdown preview, we need to make a few changes to formatting, which is selected by this flag. """ @@ -34,6 +34,7 @@ class ToHtml(Tokenizer): self.forPreview = forPreview if forPreview: self.doKeywords = True + self.doComments = doComments return @@ -118,7 +119,7 @@ class ToHtml(Tokenizer): thisPar.append(tTemp.rstrip()+" ") elif tType == self.T_COMMENT and self.doComments: - self.theResult += "
%s
\n" % tText + self.theResult += self._formatComments(tText) elif tType == self.T_KEYWORD and self.doKeywords: self.theResult += self._formatTags(tText) @@ -134,17 +135,6 @@ class ToHtml(Tokenizer): if not self.forPreview: return "
@%s
\n" % tText - theLabel = { - "@tag" : "Tag", - "@pov" : "Point of View", - "@char" : "Character(s)", - "@plot" : "Plot", - "@time" : "Time", - "@location" : "Location(s)", - "@object" : "Object(s)", - "@custom" : "Custom", - } - tText = "@"+tText isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText) if not isValid or not theBits: @@ -152,8 +142,8 @@ class ToHtml(Tokenizer): retText = "" refTags = [] - if theBits[0] in theLabel: - retText += "%s: " % theLabel[theBits[0]] + if theBits[0] in nwLabels.KEY_NAME: + retText += "%s: " % nwLabels.KEY_NAME[theBits[0]] for tTag in theBits[1:]: refTags.append("%s" % ( theBits[0][1:], tTag, tTag @@ -162,4 +152,11 @@ class ToHtml(Tokenizer): return "
%s
" % retText + def _formatComments(self, tText): + + if not self.forPreview: + return "
%s
\n" % tText + + return "

%s

\n" % tText + # END Class ToHtml diff --git a/nw/enum.py b/nw/enum.py index f83d9135..738ebce8 100644 --- a/nw/enum.py +++ b/nw/enum.py @@ -31,8 +31,9 @@ class nwItemClass(Enum): WORLD = 4 TIMELINE = 5 OBJECT = 6 - CUSTOM = 7 - TRASH = 8 + ENTITY = 7 + CUSTOM = 8 + TRASH = 9 # END Enum nwItemClass diff --git a/nw/gui/dialogs/configeditor.py b/nw/gui/dialogs/configeditor.py index 2f13ef89..a77dcfc8 100644 --- a/nw/gui/dialogs/configeditor.py +++ b/nw/gui/dialogs/configeditor.py @@ -322,17 +322,17 @@ class GuiConfigEditEditor(QWidget): self.textFlowForm = QGridLayout(self) self.textFlow.setLayout(self.textFlowForm) - self.textFlowFixed = QCheckBox("Fixed width",self) - self.textFlowFixed.setToolTip("Make text in editor fixed width and scale margins instead.") + self.textFlowFixed = QCheckBox("Max text width",self) + self.textFlowFixed.setToolTip("Maximum width of the text.") if self.mainConf.textFixedW: self.textFlowFixed.setCheckState(Qt.Checked) else: self.textFlowFixed.setCheckState(Qt.Unchecked) - self.textFlowWidth = QSpinBox(self) - self.textFlowWidth.setMinimum(300) - self.textFlowWidth.setMaximum(10000) - self.textFlowWidth.setSingleStep(10) - self.textFlowWidth.setValue(self.mainConf.textWidth) + self.textFlowMax = QSpinBox(self) + self.textFlowMax.setMinimum(300) + self.textFlowMax.setMaximum(10000) + self.textFlowMax.setSingleStep(10) + self.textFlowMax.setValue(self.mainConf.textWidth) self.textFlowJustify = QCheckBox("Justify text",self) self.textFlowJustify.setToolTip("Justify text in main document editor.") @@ -342,7 +342,7 @@ class GuiConfigEditEditor(QWidget): self.textFlowJustify.setCheckState(Qt.Unchecked) self.textFlowForm.addWidget(self.textFlowFixed, 0, 0) - self.textFlowForm.addWidget(self.textFlowWidth, 0, 1) + self.textFlowForm.addWidget(self.textFlowMax, 0, 1) self.textFlowForm.addWidget(QLabel("px"), 0, 2) self.textFlowForm.addWidget(self.textFlowJustify, 1, 0) self.textFlowForm.setColumnStretch(4, 1) @@ -519,7 +519,7 @@ class GuiConfigEditEditor(QWidget): self.mainConf.textFont = textFont self.mainConf.textSize = textSize - textWidth = self.textFlowWidth.value() + textWidth = self.textFlowMax.value() textFixedW = self.textFlowFixed.isChecked() doJustify = self.textFlowJustify.isChecked() diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index 6b78eee6..852d3272 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -16,9 +16,9 @@ import nw from time import time from PyQt5.QtCore import Qt, QTimer, QSizeF -from PyQt5.QtWidgets import qApp, QTextEdit, QAction, QMenu, QShortcut +from PyQt5.QtWidgets import qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox from PyQt5.QtGui import ( - QTextCursor, QTextOption, QIcon, QKeySequence, QFont, QColor, QPalette, QTextDocument + QTextCursor, QTextOption, QIcon, QKeySequence, QFont, QColor, QPalette, QTextDocument, ) from nw.project.document import NWDoc @@ -293,8 +293,8 @@ class GuiDocEditor(QTextEdit): tW = self.mainConf.textWidth wW = self.width() tM = int((wW - sW - tW)/2) - if tM < 0: - tM = 0 + if tM < self.mainConf.textMargin: + tM = self.mainConf.textMargin docFormat = self.qDocument.rootFrame().frameFormat() docFormat.setLeftMargin(tM) docFormat.setRightMargin(tM) @@ -332,6 +332,19 @@ class GuiDocEditor(QTextEdit): def isEmpty(self): return self.qDocument.isEmpty() + def revealLocation(self): + if self.theHandle is not None: + msgBox = QMessageBox() + msgBox.information(self, "File Location", ( + "File details for the currently open file
" + "Handle: {handle:s}
" + "Location: {fileLoc:s}" + ).format( + handle = self.theHandle, + fileLoc = str(self.nwDocument.fileLoc) + )) + return + ## # Document Events and Maintenance ## diff --git a/nw/gui/elements/doctree.py b/nw/gui/elements/doctree.py index d618e4ce..bc53ab59 100644 --- a/nw/gui/elements/doctree.py +++ b/nw/gui/elements/doctree.py @@ -18,7 +18,7 @@ from PyQt5.QtGui import QIcon, QFont, QColor from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, QApplication from nw.project.item import NWItem -from nw.enum import nwItemType, nwItemClass, nwAlert +from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert from nw.constants import nwLabels logger = logging.getLogger(__name__) @@ -62,9 +62,14 @@ class GuiDocTree(QTreeWidget): self.setDragEnabled(True) self.setDragDropMode(QAbstractItemView.InternalMove) + # But don't allow drop on root level + trRoot = self.invisibleRootItem() + trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled) + # Set Multiple Selection by CTRL - self.setSelectionMode(QAbstractItemView.ExtendedSelection) - self.setSelectionBehavior(QAbstractItemView.SelectRows) + # Disabled for now, until the merge files option has been added + # self.setSelectionMode(QAbstractItemView.ExtendedSelection) + # self.setSelectionBehavior(QAbstractItemView.SelectRows) for colN in range(len(self.mainConf.treeColWidth)): self.setColumnWidth(colN,self.mainConf.treeColWidth[colN]) @@ -183,7 +188,7 @@ class GuiDocTree(QTreeWidget): nIndex = tIndex + nStep if nIndex < 0 or nIndex >= nChild: return False - cItem = self.takeTopLevelItem(tIndex) + cItem = self.takeTopLevelItem(tIndex) self.insertTopLevelItem(nIndex, cItem) else: tIndex = pItem.indexOfChild(tItem) @@ -191,7 +196,7 @@ class GuiDocTree(QTreeWidget): nIndex = tIndex + nStep if nIndex < 0 or nIndex >= nChild: return False - cItem = pItem.takeChild(tIndex) + cItem = pItem.takeChild(tIndex) pItem.insertChild(nIndex, cItem) self.clearSelection() cItem.setSelected(True) @@ -446,17 +451,28 @@ class GuiDocTree(QTreeWidget): return def _updateItemParent(self, tHandle): + """Update the parent handle of an item so that the information in the project is consistent + with the treeView. Also move the word count over to the new parent tree. + """ + trItemS = self._getTreeItem(tHandle) nwItemS = self.theProject.getItem(tHandle) trItemP = trItemS.parent() if trItemP is None: logger.error("Failed to find new parent item of %s" % tHandle) - return + return False + pHandle = trItemP.text(self.C_HANDLE) + wC = int(trItemS.text(self.C_COUNT)) + self.propagateCount(tHandle, -wC) nwItemS.setParent(pHandle) + self.propagateCount(tHandle, wC) self.setTreeItemValues(tHandle) self.theProject.setProjectChanged(True) - return + + logger.debug("The parent of item %s has been changed to %s" % (tHandle,pHandle)) + + return True def _moveOrphanedItem(self, tHandle, dHandle): trItemS = self._getTreeItem(tHandle) @@ -505,25 +521,30 @@ class GuiDocTree(QTreeWidget): dnItem = self.theProject.getItem(dHandle) isSame = snItem.itemClass == dnItem.itemClass isNone = snItem.itemClass == nwItemClass.NO_CLASS + isNote = snItem.itemLayout == nwItemLayout.NOTE onFile = dnItem.itemType == nwItemType.FILE isRoot = snItem.itemType == nwItemType.ROOT onRoot = dnItem.itemType == nwItemType.ROOT isOnTop = self.dropIndicatorPosition() == QAbstractItemView.OnItem - isAbove = self.dropIndicatorPosition() == QAbstractItemView.AboveItem - isBelow = self.dropIndicatorPosition() == QAbstractItemView.BelowItem - if (isSame or isNone) and not (onFile and isOnTop) and not isRoot: - logger.verbose("Drag'n'drop of item %s accepted" % sHandle) + if (isSame or isNone or isNote) and not (onFile and isOnTop) and not isRoot: + logger.debug("Drag'n'drop of item %s accepted" % sHandle) QTreeWidget.dropEvent(self, theEvent) if isNone: self._moveOrphanedItem(sHandle, dHandle) self._cleanOrphanedRoot() else: self._updateItemParent(sHandle) - elif isRoot and (isAbove or isBelow) and onRoot: - logger.verbose("Drag'n'drop of item %s accepted" % sHandle) - QTreeWidget.dropEvent(self, theEvent) + if not isSame: + logger.debug("Item %s class has been changed from %s to %s" % ( + sHandle, + snItem.itemClass.name, + dnItem.itemClass.name + )) + snItem.setClass(dnItem.itemClass) + self.setTreeItemValues(sHandle) else: - logger.verbose("Drag'n'drop of item %s not accepted" % sHandle) + logger.debug("Drag'n'drop of item %s not accepted" % sHandle) + self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR) return diff --git a/nw/gui/elements/docviewer.py b/nw/gui/elements/docviewer.py index 629a9824..468f4f2b 100644 --- a/nw/gui/elements/docviewer.py +++ b/nw/gui/elements/docviewer.py @@ -105,7 +105,7 @@ class GuiDocViewer(QTextBrowser): logger.debug("Generating preview for item %s" % tHandle) sPos = self.verticalScrollBar().value() aDoc = ToHtml(self.theProject, self.theParent) - aDoc.setPreview(True) + aDoc.setPreview(True, self.mainConf.viewComments) aDoc.setText(tHandle) aDoc.doAutoReplace() aDoc.tokenizeText() @@ -117,10 +117,14 @@ class GuiDocViewer(QTextBrowser): self.theHandle = tHandle self.theProject.setLastViewed(tHandle) - self.theParent.docMeta.refreshReferences(tHandle) + self.theParent.viewMeta.refreshReferences(tHandle) return True + def reloadText(self): + self.loadText(self.theHandle) + return + def loadFromTag(self, theTag): logger.debug("Loading document from tag '%s'" % theTag) @@ -180,10 +184,6 @@ class GuiDocViewer(QTextBrowser): "a {{" " color: rgb({aColR},{aColG},{aColB});" "}}\n" - "pre {{" - " color: rgb({cColR},{cColG},{cColB});" - " font-size: {preSize:.1f}pt;" - "}}\n" "mark {{" " color: rgb({eColR},{eColG},{eColB});" "}}\n" @@ -197,6 +197,11 @@ class GuiDocViewer(QTextBrowser): " color: rgb({kColR},{kColG},{kColB});" " font-wright: bold;" "}}\n" + ".comment {{" + " color: rgb({cColR},{cColG},{cColB});" + " margin-left: 1em;" + " margin-right: 1em;" + "}}\n" ).format( textSize = self.mainConf.textSize, preSize = self.mainConf.textSize*0.9, @@ -212,9 +217,9 @@ class GuiDocViewer(QTextBrowser): eColR = self.theTheme.colEmph[0], eColG = self.theTheme.colEmph[1], eColB = self.theTheme.colEmph[2], - aColR = self.theTheme.colLink[0], - aColG = self.theTheme.colLink[1], - aColB = self.theTheme.colLink[2], + aColR = self.theTheme.colVal[0], + aColG = self.theTheme.colVal[1], + aColB = self.theTheme.colVal[2], kColR = self.theTheme.colKey[0], kColG = self.theTheme.colKey[1], kColB = self.theTheme.colKey[2], diff --git a/nw/gui/elements/viewdetails.py b/nw/gui/elements/viewdetails.py index 026955b4..d283cd20 100644 --- a/nw/gui/elements/viewdetails.py +++ b/nw/gui/elements/viewdetails.py @@ -6,7 +6,7 @@ Class holding the document view details panel File History: - Created: 2019-09-31 [0.3.2] + Created: 2019-10-31 [0.3.2] """ @@ -20,7 +20,7 @@ from PyQt5.QtWidgets import ( QSizePolicy, QCheckBox, QGridLayout ) -from nw.constants import nwLabels +from nw.constants import nwLabels logger = logging.getLogger(__name__) @@ -48,7 +48,6 @@ class GuiDocViewDetails(QWidget): self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly) self.showHide.setArrowType(Qt.DownArrow) self.showHide.setCheckable(True) - self.showHide.setChecked(True) self.showHide.setIconSize(QSize(16,16)) self.showHide.toggled.connect(self._doShowHide) @@ -66,8 +65,7 @@ class GuiDocViewDetails(QWidget): self.scrollBox.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollBox.setFrameStyle(QFrame.NoFrame) self.scrollBox.setWidgetResizable(True) - self.scrollBox.setMaximumHeight(300) - self.scrollBox.setMinimumHeight(60) + self.scrollBox.setFixedHeight(80) self.scrollBox.setWidget(self.refList) self.outerBox.addWidget(self.showHide, 0, 0) @@ -79,6 +77,8 @@ class GuiDocViewDetails(QWidget): self.setLayout(self.outerBox) self.setContentsMargins(0,0,0,0) + self._doShowHide(self.mainConf.showRefPanel) + logger.debug("DocViewDetails initialisation complete") return @@ -91,12 +91,6 @@ class GuiDocViewDetails(QWidget): return theRefs = self.theParent.theIndex.buildReferenceList(tHandle) - if theRefs: - self.setVisible(True) - else: - self.setVisible(False) - return - theList = [] for tHandle in theRefs: tItem = self.theProject.getItem(tHandle) @@ -120,6 +114,7 @@ class GuiDocViewDetails(QWidget): def _doShowHide(self, chState): self.scrollBox.setVisible(chState) + self.mainConf.setShowRefPanel(chState) if chState: self.showHide.setArrowType(Qt.DownArrow) else: diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 7a42f6cf..23dff355 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -112,6 +112,11 @@ class GuiMainMenu(QMenuBar): self.toolsSpellCheck.setChecked(False) return True + def _toggleViewComments(self): + self.mainConf.setViewComments(self.docViewComments.isChecked()) + self.theParent.docViewer.reloadText() + return True + def _showAbout(self): listPrefix = "  •  " aboutMsg = ( @@ -163,6 +168,10 @@ class GuiMainMenu(QMenuBar): self.updateRecentProjects() return True + def _showDocumentLocation(self): + self.theParent.docEditor.revealLocation() + return True + ## # Menu Builders ## @@ -235,6 +244,7 @@ class GuiMainMenu(QMenuBar): self.rootItems[nwItemClass.WORLD] = QAction("Location Root", rootMenu) self.rootItems[nwItemClass.TIMELINE] = QAction("Timeline Root", rootMenu) self.rootItems[nwItemClass.OBJECT] = QAction("Object Root", rootMenu) + self.rootItems[nwItemClass.ENTITY] = QAction("Entity Root", rootMenu) self.rootItems[nwItemClass.CUSTOM] = QAction("Custom Root", rootMenu) nCount = 0 for itemClass in self.rootItems.keys(): @@ -330,10 +340,24 @@ class GuiMainMenu(QMenuBar): menuItem.triggered.connect(self.theParent.closeDocViewer) self.docuMenu.addAction(menuItem) + # Document > Toggle View Comments + self.docViewComments = QAction("View Comments", self) + self.docViewComments.setStatusTip("Show comments in view panel") + self.docViewComments.setCheckable(True) + self.docViewComments.setChecked(self.mainConf.viewComments) + self.docViewComments.toggled.connect(self._toggleViewComments) + self.docuMenu.addAction(self.docViewComments) + # Document > Separator self.docuMenu.addSeparator() - # Document > Close Preview + # Document > Show File Details + menuItem = QAction("Show File Details", self) + menuItem.setStatusTip("Shows a message box with the document location in the project folder") + menuItem.triggered.connect(self._showDocumentLocation) + self.docuMenu.addAction(menuItem) + + # Document > Import From File menuItem = QAction("Import from File", self) menuItem.setStatusTip("Import document from a text or markdown file") menuItem.setShortcut("Ctrl+Shift+I") diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index 872e9c66..c64c095d 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -77,7 +77,7 @@ class GuiMain(QMainWindow): self.noticeBar = GuiNoticeBar(self) self.docEditor = GuiDocEditor(self, self.theProject) self.docViewer = GuiDocViewer(self, self.theProject) - self.docMeta = GuiDocViewDetails(self, self.theProject) + self.viewMeta = GuiDocViewDetails(self, self.theProject) self.searchBar = GuiSearchBar(self) self.treeMeta = GuiDocDetails(self, self.theProject) self.treeView = GuiDocTree(self, self.theProject) @@ -107,7 +107,7 @@ class GuiMain(QMainWindow): self.docView = QVBoxLayout() self.docView.setContentsMargins(0,0,0,0) self.docView.addWidget(self.docViewer) - self.docView.addWidget(self.docMeta) + self.docView.addWidget(self.viewMeta) self.docView.setStretch(0, 1) self.viewPane.setLayout(self.docView) @@ -313,7 +313,7 @@ class GuiMain(QMainWindow): return True - def saveProject(self): + def saveProject(self, isAuto=False): """Save the current project. """ if not self.hasProject: @@ -327,7 +327,7 @@ class GuiMain(QMainWindow): return False self.treeView.saveTreeOrder() - self.theProject.saveProject() + self.theProject.saveProject(isAuto) self.theIndex.saveIndex() self.mainMenu.updateRecentProjects() @@ -703,7 +703,7 @@ class GuiMain(QMainWindow): def _autoSaveProject(self): if self.hasProject and self.theProject.projChanged and self.theProject.projPath is not None: logger.debug("Autosaving project") - self.saveProject() + self.saveProject(isAuto=True) return def _autoSaveDocument(self): diff --git a/nw/project/backup.py b/nw/project/backup.py index 0abef5da..bb69309a 100644 --- a/nw/project/backup.py +++ b/nw/project/backup.py @@ -17,18 +17,16 @@ from os import path, mkdir, listdir from shutil import make_archive from datetime import datetime -from nw.enum import nwAlert +from nw.enum import nwAlert logger = logging.getLogger(__name__) class NWBackup(): def __init__(self, theParent, theProject): - self.mainConf = nw.CONFIG self.theParent = theParent self.theProject = theProject - return def zipIt(self): diff --git a/nw/project/document.py b/nw/project/document.py index 0a3fa126..ad531fb3 100644 --- a/nw/project/document.py +++ b/nw/project/document.py @@ -31,6 +31,7 @@ class NWDoc(): self.theItem = None self.docHandle = None self.docEditable = False + self.fileLoc = None # Internal Mapping self.makeAlert = self.theParent.makeAlert @@ -58,7 +59,8 @@ class NWDoc(): self.docEditable = False docDir, docFile = self._assemblePath(self.FILE_MN) - logger.debug("Opening document %s" % path.join(docDir,docFile)) + self.fileLoc = path.join(docDir,docFile) + logger.debug("Opening document %s" % self.fileLoc) dataDir = path.join(self.theProject.projPath, docDir) docPath = path.join(dataDir, docFile) diff --git a/nw/project/index.py b/nw/project/index.py index 08b872dc..db7d21e1 100644 --- a/nw/project/index.py +++ b/nw/project/index.py @@ -14,37 +14,37 @@ import logging import json import nw -from os import path +from os import path from nw.project.document import NWDoc from nw.enum import nwItemType, nwItemClass, nwItemLayout -from nw.constants import nwFiles +from nw.constants import nwFiles, nwKeyWords from nw.enum import nwAlert logger = logging.getLogger(__name__) class NWIndex(): - TAG_KEY = "@tag" - POV_KEY = "@pov" - CHAR_KEY = "@char" - PLOT_KEY = "@plot" - TIME_KEY = "@time" - WORLD_KEY = "@location" - OBJECT_KEY = "@object" - CUSTOM_KEY = "@custom" - - NOTE_KEYS = [TAG_KEY] - NOVEL_KEYS = [PLOT_KEY, POV_KEY, CHAR_KEY, WORLD_KEY, TIME_KEY, OBJECT_KEY, CUSTOM_KEY] - VALID_KEYS = [TAG_KEY, PLOT_KEY, POV_KEY, CHAR_KEY, WORLD_KEY, TIME_KEY, OBJECT_KEY, CUSTOM_KEY] + VALID_KEYS = [ + nwKeyWords.TAG_KEY, + nwKeyWords.PLOT_KEY, + nwKeyWords.POV_KEY, + nwKeyWords.CHAR_KEY, + nwKeyWords.WORLD_KEY, + nwKeyWords.TIME_KEY, + nwKeyWords.OBJECT_KEY, + nwKeyWords.ENTITY_KEY, + nwKeyWords.CUSTOM_KEY + ] TAG_CLASS = { - CHAR_KEY : [nwItemClass.CHARACTER, 1], - POV_KEY : [nwItemClass.CHARACTER, 2], - PLOT_KEY : [nwItemClass.PLOT, 1], - TIME_KEY : [nwItemClass.TIMELINE, 1], - WORLD_KEY : [nwItemClass.WORLD, 1], - OBJECT_KEY : [nwItemClass.OBJECT, 1], - CUSTOM_KEY : [nwItemClass.CUSTOM, 1], + nwKeyWords.CHAR_KEY : [nwItemClass.CHARACTER, 1], + nwKeyWords.POV_KEY : [nwItemClass.CHARACTER, 2], + nwKeyWords.PLOT_KEY : [nwItemClass.PLOT, 1], + nwKeyWords.TIME_KEY : [nwItemClass.TIMELINE, 1], + nwKeyWords.WORLD_KEY : [nwItemClass.WORLD, 1], + nwKeyWords.OBJECT_KEY : [nwItemClass.OBJECT, 1], + nwKeyWords.ENTITY_KEY : [nwItemClass.ENTITY, 1], + nwKeyWords.CUSTOM_KEY : [nwItemClass.CUSTOM, 1], } def __init__(self, theProject, theParent): @@ -79,9 +79,13 @@ class NWIndex(): def deleteHandle(self, tHandle): + delTags = [] for tTag in self.tagIndex: if self.tagIndex[tTag][1] == tHandle: - self.tagIndex.pop(tTag, None) + delTags.append(tTag) + + for tTag in delTags: + self.tagIndex.pop(tTag, None) self.refIndex.pop(tHandle, None) self.novelIndex.pop(tHandle, None) @@ -275,10 +279,9 @@ class NWIndex(): if not isValid or len(theBits) == 0: return False - theKey = theBits[0] - if theKey in self.NOVEL_KEYS: + if theBits[0] != nwKeyWords.TAG_KEY: for aVal in theBits[1:]: - self.refIndex[tHandle].append([nLine, theKey, aVal, nTitle]) + self.refIndex[tHandle].append([nLine, theBits[0], aVal, nTitle]) return True @@ -290,7 +293,7 @@ class NWIndex(): if not isValid or len(theBits) != 2: return False - if theBits[0] == self.TAG_KEY: + if theBits[0] == nwKeyWords.TAG_KEY: self.tagIndex[theBits[1]] = [nLine, tHandle, itemClass.name] return True @@ -355,7 +358,7 @@ class NWIndex(): return isGood # If we have a tag, only the first value is accepted, the rest is ignored - if theBits[0] == self.TAG_KEY and nBits > 1: + if theBits[0] == nwKeyWords.TAG_KEY and nBits > 1: isGood[0] = True if theBits[1] in self.tagIndex.keys(): if self.tagIndex[theBits[1]][1] == tItem.itemHandle: diff --git a/nw/project/project.py b/nw/project/project.py index 71891185..aa9bc9d2 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -280,7 +280,7 @@ class NWProject(): return True - def saveProject(self): + def saveProject(self, isAuto=False): if self.projPath is None: self.makeAlert("Project path not set, cannot save.", nwAlert.ERROR) @@ -296,7 +296,8 @@ class NWProject(): logger.debug("Saving project: %s" % self.projPath) # Save a copy of the current file, just in case - self._maintainPrevious() + if not isAuto: + self._maintainPrevious() # Root element and project details logger.debug("Writing project meta") diff --git a/tests/reference/novelwriter.conf b/tests/reference/novelwriter.conf index 0ba36f1a..b222cfb0 100644 --- a/tests/reference/novelwriter.conf +++ b/tests/reference/novelwriter.conf @@ -1,5 +1,5 @@ [Main] -timestamp = 2019-10-29 19:20:27 +timestamp = 2019-11-03 00:40:02 theme = default syntax = default_light @@ -16,7 +16,7 @@ autosavedoc = 30 [Editor] textfont = None textsize = 12 -fixedwidth = False +fixedwidth = True width = 600 margin = 40 tabwidth = 40 @@ -38,6 +38,10 @@ backuppath = backuponclose = False askbeforebackup = True +[State] +showrefpanel = True +viewcomments = True + [Path] lastpath = recent0 =