diff --git a/nw/gui/configeditor.py b/nw/gui/dialogs/configeditor.py
similarity index 100%
rename from nw/gui/configeditor.py
rename to nw/gui/dialogs/configeditor.py
diff --git a/nw/gui/export.py b/nw/gui/dialogs/export.py
similarity index 100%
rename from nw/gui/export.py
rename to nw/gui/dialogs/export.py
diff --git a/nw/gui/itemeditor.py b/nw/gui/dialogs/itemeditor.py
similarity index 100%
rename from nw/gui/itemeditor.py
rename to nw/gui/dialogs/itemeditor.py
diff --git a/nw/gui/projecteditor.py b/nw/gui/dialogs/projecteditor.py
similarity index 100%
rename from nw/gui/projecteditor.py
rename to nw/gui/dialogs/projecteditor.py
diff --git a/nw/gui/sessionlog.py b/nw/gui/dialogs/sessionlog.py
similarity index 100%
rename from nw/gui/sessionlog.py
rename to nw/gui/dialogs/sessionlog.py
diff --git a/nw/gui/timelineview.py b/nw/gui/dialogs/timelineview.py
similarity index 100%
rename from nw/gui/timelineview.py
rename to nw/gui/dialogs/timelineview.py
diff --git a/nw/gui/docdetails.py b/nw/gui/elements/docdetails.py
similarity index 100%
rename from nw/gui/docdetails.py
rename to nw/gui/elements/docdetails.py
diff --git a/nw/gui/doceditor.py b/nw/gui/elements/doceditor.py
similarity index 95%
rename from nw/gui/doceditor.py
rename to nw/gui/elements/doceditor.py
index d7b64290..6b78eee6 100644
--- a/nw/gui/doceditor.py
+++ b/nw/gui/elements/doceditor.py
@@ -13,18 +13,20 @@
import logging
import nw
-from time import time
+from time import time
-from PyQt5.QtCore import Qt, QTimer, QSizeF
-from PyQt5.QtWidgets import qApp, QTextEdit, QAction, QMenu, QShortcut
-from PyQt5.QtGui import QTextCursor, QTextOption, QIcon, QKeySequence, QFont, QColor, QPalette, QTextDocument
+from PyQt5.QtCore import Qt, QTimer, QSizeF
+from PyQt5.QtWidgets import qApp, QTextEdit, QAction, QMenu, QShortcut
+from PyQt5.QtGui import (
+ QTextCursor, QTextOption, QIcon, QKeySequence, QFont, QColor, QPalette, QTextDocument
+)
-from nw.project.document import NWDoc
-from nw.gui.dochighlight import GuiDocHighlighter
-from nw.gui.wordcounter import WordCounter
-from nw.tools.spellcheck import NWSpellCheck
-from nw.constants import nwFiles, nwUnicode
-from nw.enum import nwDocAction, nwAlert
+from nw.project.document import NWDoc
+from nw.gui.tools.dochighlight import GuiDocHighlighter
+from nw.gui.tools.wordcounter import WordCounter
+from nw.tools.spellcheck import NWSpellCheck
+from nw.constants import nwFiles, nwUnicode
+from nw.enum import nwDocAction, nwAlert
logger = logging.getLogger(__name__)
@@ -114,6 +116,7 @@ class GuiDocEditor(QTextEdit):
self.hasSelection = False
self.setDocumentChanged(False)
+ self.theParent.noticeBar.hideNote()
return True
@@ -161,12 +164,16 @@ class GuiDocEditor(QTextEdit):
# Initialise the syntax highlighter
self.hLight.initHighlighter()
- # If we have a document open, we should reload it in case the font changed
+ # If we have a document open, we should reload it in case the font changed, otherwise
+ # we just clear the editor entirely, which makes it read only.
if self.theHandle is not None:
+ # We must save the current handle as clearEditor() sets it to None
tHandle = self.theHandle
self.clearEditor()
self.loadText(tHandle)
self.changeWidth()
+ else:
+ self.clearEditor()
return True
@@ -191,9 +198,13 @@ class GuiDocEditor(QTextEdit):
self._runCounter()
self.wcTimer.start()
self.setDocumentChanged(False)
- self.setReadOnly(False)
self.theHandle = tHandle
+ if self.nwDocument.docEditable:
+ self.setReadOnly(False)
+ else:
+ self.theParent.noticeBar.showNote("This document is read only.")
+
return True
def saveText(self):
@@ -236,9 +247,10 @@ class GuiDocEditor(QTextEdit):
return theText
def setCursorPosition(self, thePosition):
- theCursor = self.textCursor()
- theCursor.setPosition(thePosition)
- self.setTextCursor(theCursor)
+ if thePosition > 0:
+ theCursor = self.textCursor()
+ theCursor.setPosition(thePosition)
+ self.setTextCursor(theCursor)
return True
def getCursorPosition(self):
diff --git a/nw/gui/doctree.py b/nw/gui/elements/doctree.py
similarity index 95%
rename from nw/gui/doctree.py
rename to nw/gui/elements/doctree.py
index cdef8d3f..75c745a6 100644
--- a/nw/gui/doctree.py
+++ b/nw/gui/elements/doctree.py
@@ -41,8 +41,8 @@ class GuiDocTree(QTreeWidget):
self.theProject = theProject
# Tree Settings
- self.theMap = None
- self.orphRoot = None
+ self.theMap = None
+ self.orphRoot = None
self.clearTree()
@@ -97,15 +97,23 @@ class GuiDocTree(QTreeWidget):
return False
if itemClass is None and pHandle is not None:
- itemClass = self.theProject.getItem(pHandle).itemClass
+ pItem = self.theProject.getItem(pHandle)
+ if pItem is not None:
+ itemClass = pItem.itemClass
if itemClass is None:
if itemType is not None:
if itemType == nwItemType.FILE:
- self.makeAlert("Please select a location in the tree to add a document.", nwAlert.ERROR)
+ self.makeAlert(
+ "Please select a valid location in the tree to add a document.",
+ nwAlert.ERROR
+ )
return False
elif itemType == nwItemType.FOLDER:
- self.makeAlert("Please select a location in the tree to add a folder.", nwAlert.ERROR)
+ self.makeAlert(
+ "Please select a valid location in the tree to add a folder.",
+ nwAlert.ERROR
+ )
return False
self.makeAlert("Failed to add new item.", nwAlert.BUG)
return False
@@ -134,10 +142,14 @@ class GuiDocTree(QTreeWidget):
# If we again has no home, give up
if pHandle is None:
- logger.error("Did not find anywhere to add the item!")
+ self.makeAlert("Did not find anywhere to add the file or folder!", nwAlert.ERROR)
return False
- # If we're still here, add the file
+ if pHandle == self.theProject.trashRoot:
+ self.makeAlert("Cannot add new files or folders to the trash folder.", nwAlert.ERROR)
+ return False
+
+ # If we're still here, add the file or folder
if itemType == nwItemType.FILE:
tHandle = self.theProject.newFile("New File", itemClass, pHandle)
elif itemType == nwItemType.FOLDER:
diff --git a/nw/gui/docviewer.py b/nw/gui/elements/docviewer.py
similarity index 100%
rename from nw/gui/docviewer.py
rename to nw/gui/elements/docviewer.py
diff --git a/nw/gui/elements/noticebar.py b/nw/gui/elements/noticebar.py
new file mode 100644
index 00000000..17bd512e
--- /dev/null
+++ b/nw/gui/elements/noticebar.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+"""novelWriter GUI Main Window Notice Bar
+
+ novelWriter – GUI Main Window Notice Bar
+==========================================
+ Class holding the main window notice bar for the doc editor
+
+ File History:
+ Created: 2019-10-31 [0.3.2]
+
+"""
+
+import logging
+import nw
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QPalette, QColor
+from PyQt5.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton
+
+logger = logging.getLogger(__name__)
+
+class GuiNoticeBar(QFrame):
+
+ def __init__(self, theParent):
+ QFrame.__init__(self, theParent)
+
+ logger.debug("Initialising NoticeBar ...")
+
+ self.mainConf = nw.CONFIG
+ self.theParent = theParent
+ self.theTheme = theParent.theTheme
+
+ self.setContentsMargins(0,0,0,0)
+ self.setFrameShape(QFrame.Box)
+
+ self.mainBox = QHBoxLayout(self)
+ self.mainBox.setContentsMargins(8,2,2,2)
+
+ self.noteLabel = QLabel("Hi there!")
+ self.closeButton = QPushButton(self.theTheme.getIcon("close"),"")
+ self.closeButton.clicked.connect(self.hideNote)
+
+ self.mainBox.addWidget(self.noteLabel)
+ self.mainBox.addWidget(self.closeButton)
+ self.mainBox.setStretch(0, 1)
+
+ self.setLayout(self.mainBox)
+
+ self.hideNote()
+
+ logger.debug("NoticeBar initialisation complete")
+
+ return
+
+ def showNote(self, theNote):
+ self.noteLabel.setText("Note: %s" % theNote)
+ self.setVisible(True)
+ return
+
+ def hideNote(self):
+ self.noteLabel.setText("")
+ self.setVisible(False)
+ return
+
+# END Class GuiNoticeBar
diff --git a/nw/gui/searchbar.py b/nw/gui/elements/searchbar.py
similarity index 97%
rename from nw/gui/searchbar.py
rename to nw/gui/elements/searchbar.py
index 53a5883b..e5da35ee 100644
--- a/nw/gui/searchbar.py
+++ b/nw/gui/elements/searchbar.py
@@ -26,7 +26,7 @@ class GuiSearchBar(QFrame):
def __init__(self, theParent):
QFrame.__init__(self, theParent)
- logger.debug("Initialising GuiSearchBar ...")
+ logger.debug("Initialising SearchBar ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
@@ -73,7 +73,7 @@ class GuiSearchBar(QFrame):
self._replaceVisible(False)
- logger.debug("GuiSearchBar initialisation complete")
+ logger.debug("SearchBar initialisation complete")
return
diff --git a/nw/gui/dochighlight.py b/nw/gui/tools/dochighlight.py
similarity index 100%
rename from nw/gui/dochighlight.py
rename to nw/gui/tools/dochighlight.py
diff --git a/nw/gui/wordcounter.py b/nw/gui/tools/wordcounter.py
similarity index 100%
rename from nw/gui/wordcounter.py
rename to nw/gui/tools/wordcounter.py
diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py
index 53a882d9..a45c9067 100644
--- a/nw/gui/winmain.py
+++ b/nw/gui/winmain.py
@@ -14,36 +14,38 @@ import logging
import time
import nw
-from os import path
-from PyQt5.QtCore import Qt, QTimer
-from PyQt5.QtGui import QIcon, QPixmap, QColor
-from PyQt5.QtWidgets import (
+from os import path
+
+from PyQt5.QtCore import Qt, QTimer
+from PyQt5.QtGui import QIcon, QPixmap, QColor
+from PyQt5.QtWidgets import (
qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog,
QShortcut, QMessageBox, QProgressDialog, QDialog
)
-from nw.gui.doctree import GuiDocTree
-from nw.gui.doceditor import GuiDocEditor
-from nw.gui.docviewer import GuiDocViewer
-from nw.gui.docdetails import GuiDocDetails
-from nw.gui.searchbar import GuiSearchBar
-from nw.gui.mainmenu import GuiMainMenu
-from nw.gui.configeditor import GuiConfigEditor
-from nw.gui.projecteditor import GuiProjectEditor
-from nw.gui.export import GuiExport
-from nw.gui.itemeditor import GuiItemEditor
-from nw.gui.statusbar import GuiMainStatus
-from nw.gui.timelineview import GuiTimeLineView
-from nw.gui.sessionlog import GuiSessionLogView
-from nw.project.project import NWProject
-from nw.project.document import NWDoc
-from nw.project.item import NWItem
-from nw.project.index import NWIndex
-from nw.project.backup import NWBackup
-from nw.tools.wordcount import countWords
-from nw.theme import Theme
-from nw.enum import nwItemType, nwAlert
-from nw.constants import nwFiles
+from nw.gui.mainmenu import GuiMainMenu
+from nw.gui.statusbar import GuiMainStatus
+from nw.gui.elements.doctree import GuiDocTree
+from nw.gui.elements.doceditor import GuiDocEditor
+from nw.gui.elements.docviewer import GuiDocViewer
+from nw.gui.elements.docdetails import GuiDocDetails
+from nw.gui.elements.searchbar import GuiSearchBar
+from nw.gui.elements.noticebar import GuiNoticeBar
+from nw.gui.dialogs.configeditor import GuiConfigEditor
+from nw.gui.dialogs.projecteditor import GuiProjectEditor
+from nw.gui.dialogs.export import GuiExport
+from nw.gui.dialogs.itemeditor import GuiItemEditor
+from nw.gui.dialogs.timelineview import GuiTimeLineView
+from nw.gui.dialogs.sessionlog import GuiSessionLogView
+from nw.project.project import NWProject
+from nw.project.document import NWDoc
+from nw.project.item import NWItem
+from nw.project.index import NWIndex
+from nw.project.backup import NWBackup
+from nw.tools.wordcount import countWords
+from nw.theme import Theme
+from nw.enum import nwItemType, nwAlert
+from nw.constants import nwFiles
logger = logging.getLogger(__name__)
@@ -71,6 +73,7 @@ class GuiMain(QMainWindow):
# Main GUI Elements
self.statusBar = GuiMainStatus(self)
+ self.noticeBar = GuiNoticeBar(self)
self.docEditor = GuiDocEditor(self, self.theProject)
self.docViewer = GuiDocViewer(self, self.theProject)
self.docDetails = GuiDocDetails(self, self.theProject)
@@ -85,6 +88,7 @@ class GuiMain(QMainWindow):
# Assemble Main Window
self.treePane = QFrame()
self.treeBox = QVBoxLayout()
+ self.treeBox.setContentsMargins(0,0,0,0)
self.treeBox.addWidget(self.treeView)
self.treeBox.addWidget(self.docDetails)
self.treePane.setLayout(self.treeBox)
@@ -93,6 +97,7 @@ class GuiMain(QMainWindow):
self.docView = QVBoxLayout()
self.docView.setContentsMargins(0,0,0,0)
self.docView.addWidget(self.searchBar)
+ self.docView.addWidget(self.noticeBar)
self.docView.addWidget(self.docEditor)
self.docPane.setLayout(self.docView)
@@ -102,6 +107,7 @@ class GuiMain(QMainWindow):
self.splitView.splitterMoved.connect(self._splitViewMove)
self.splitMain = QSplitter(Qt.Horizontal)
+ self.splitMain.setContentsMargins(4,4,4,4)
self.splitMain.addWidget(self.treePane)
self.splitMain.addWidget(self.splitView)
self.splitMain.setSizes(self.mainConf.mainPanePos)
diff --git a/nw/project/document.py b/nw/project/document.py
index 923f5c9e..0a3fa126 100644
--- a/nw/project/document.py
+++ b/nw/project/document.py
@@ -25,11 +25,12 @@ class NWDoc():
def __init__(self, theProject, theParent):
- self.mainConf = nw.CONFIG
- self.theProject = theProject
- self.theParent = theParent
- self.theItem = None
- self.docHandle = None
+ self.mainConf = nw.CONFIG
+ self.theProject = theProject
+ self.theParent = theParent
+ self.theItem = None
+ self.docHandle = None
+ self.docEditable = False
# Internal Mapping
self.makeAlert = self.theParent.makeAlert
@@ -37,8 +38,9 @@ class NWDoc():
return
def clearDocument(self):
- self.theItem = None
- self.docHandle = None
+ self.theItem = None
+ self.docHandle = None
+ self.docEditable = False
return
def openDocument(self, tHandle, showStatus=True):
@@ -46,6 +48,15 @@ class NWDoc():
self.docHandle = tHandle
self.theItem = self.theProject.getItem(tHandle)
+ if self.theItem is None:
+ self.clearDocument()
+ return None
+
+ # By default, the document is editable. Except for files in the trash folder.
+ self.docEditable = True
+ if self.theItem.parHandle == self.theProject.trashRoot:
+ self.docEditable = False
+
docDir, docFile = self._assemblePath(self.FILE_MN)
logger.debug("Opening document %s" % path.join(docDir,docFile))
dataDir = path.join(self.theProject.projPath, docDir)
@@ -75,7 +86,7 @@ class NWDoc():
def saveDocument(self, docText):
- if self.docHandle is None:
+ if self.docHandle is None or not self.docEditable:
return False
docDir, docFile = self._assemblePath(self.FILE_MN)
diff --git a/nw/project/project.py b/nw/project/project.py
index df298dc8..06c3d593 100644
--- a/nw/project/project.py
+++ b/nw/project/project.py
@@ -478,6 +478,19 @@ class NWProject():
logger.error("No tree item with handle %s" % str(tHandle))
return None
+ def getRootItem(self, tHandle):
+ """Iterate upwards in the tree until we find the item with parent None, the root item.
+ We do this with a for loop with a maximum depth of 200 to make infinite loops impossible.
+ """
+ tItem = self.getItem(tHandle)
+ if tItem is not None:
+ for i in range(200):
+ tHandle = tItem.parHandle
+ tItem = self.getItem(tHandle)
+ if tItem is None:
+ return tHandle
+ return None
+
def getProjectItems(self):
"""This function is called from the tree view when building the tree. Each item in the
project is returned in the order saved in the project file, but first it checks that it has
diff --git a/sample/sampleNovel/data_4/cd0bd12b087d_main.nwd b/sample/sampleNovel/data_4/cd0bd12b087d_main.nwd
index 21e28fe8..47a92622 100644
--- a/sample/sampleNovel/data_4/cd0bd12b087d_main.nwd
+++ b/sample/sampleNovel/data_4/cd0bd12b087d_main.nwd
@@ -1 +1,3 @@
-# Very Deep File
\ No newline at end of file
+# Very Deep File
+
+This file is still editable.
diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx
index 11629296..af138bec 100644
--- a/sample/sampleNovel/nwProject.nwx
+++ b/sample/sampleNovel/nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Sample Project
Sample Project
@@ -11,7 +11,7 @@
True
b8136a5a774a0
ba8a28a246524
- 879
+ 884
B
E
@@ -142,7 +142,7 @@
567
112
6
- 634
+ 326
-
Characters
@@ -220,19 +220,7 @@
None
True
- -
- Orphaned File 2
- FILE
- NO_CLASS
- None
- False
- NO_LAYOUT
- 14
- 3
- 0
- 1
-
- -
+
-
Delete Me!
FILE
NOVEL
@@ -244,5 +232,17 @@
1
36
+ -
+ Orphaned File 1
+ FILE
+ NO_CLASS
+ None
+ False
+ NO_LAYOUT
+ 42
+ 8
+ 1
+ 47
+
diff --git a/tests/test_gui.py b/tests/test_gui.py
index 9c5ad51c..16723feb 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -8,10 +8,11 @@ from nwtools import *
from os import path, unlink
from PyQt5.QtCore import Qt
-from nw.gui.projecteditor import GuiProjectEditor
-from nw.gui.timelineview import GuiTimeLineView
-from nw.gui.itemeditor import GuiItemEditor
-from nw.enum import *
+from nw.gui.dialogs.projecteditor import GuiProjectEditor
+from nw.gui.dialogs.timelineview import GuiTimeLineView
+from nw.gui.dialogs.itemeditor import GuiItemEditor
+
+from nw.enum import *
keyDelay = 10
stepDelay = 50