Merge pull request #106 from vkbo/trash_folder

Trash folder fixes and Notice Bar
This commit is contained in:
Veronica K. Berglyd Olsen
2019-10-31 14:54:28 +01:00
committed by GitHub
20 changed files with 201 additions and 79 deletions
@@ -13,18 +13,20 @@
import logging import logging
import nw import nw
from time import time from time import time
from PyQt5.QtCore import Qt, QTimer, QSizeF 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
from PyQt5.QtGui import QTextCursor, QTextOption, QIcon, QKeySequence, QFont, QColor, QPalette, QTextDocument from PyQt5.QtGui import (
QTextCursor, QTextOption, QIcon, QKeySequence, QFont, QColor, QPalette, QTextDocument
)
from nw.project.document import NWDoc from nw.project.document import NWDoc
from nw.gui.dochighlight import GuiDocHighlighter from nw.gui.tools.dochighlight import GuiDocHighlighter
from nw.gui.wordcounter import WordCounter from nw.gui.tools.wordcounter import WordCounter
from nw.tools.spellcheck import NWSpellCheck from nw.tools.spellcheck import NWSpellCheck
from nw.constants import nwFiles, nwUnicode from nw.constants import nwFiles, nwUnicode
from nw.enum import nwDocAction, nwAlert from nw.enum import nwDocAction, nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -114,6 +116,7 @@ class GuiDocEditor(QTextEdit):
self.hasSelection = False self.hasSelection = False
self.setDocumentChanged(False) self.setDocumentChanged(False)
self.theParent.noticeBar.hideNote()
return True return True
@@ -161,12 +164,16 @@ class GuiDocEditor(QTextEdit):
# Initialise the syntax highlighter # Initialise the syntax highlighter
self.hLight.initHighlighter() 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: if self.theHandle is not None:
# We must save the current handle as clearEditor() sets it to None
tHandle = self.theHandle tHandle = self.theHandle
self.clearEditor() self.clearEditor()
self.loadText(tHandle) self.loadText(tHandle)
self.changeWidth() self.changeWidth()
else:
self.clearEditor()
return True return True
@@ -191,9 +198,13 @@ class GuiDocEditor(QTextEdit):
self._runCounter() self._runCounter()
self.wcTimer.start() self.wcTimer.start()
self.setDocumentChanged(False) self.setDocumentChanged(False)
self.setReadOnly(False)
self.theHandle = tHandle self.theHandle = tHandle
if self.nwDocument.docEditable:
self.setReadOnly(False)
else:
self.theParent.noticeBar.showNote("This document is read only.")
return True return True
def saveText(self): def saveText(self):
@@ -236,9 +247,10 @@ class GuiDocEditor(QTextEdit):
return theText return theText
def setCursorPosition(self, thePosition): def setCursorPosition(self, thePosition):
theCursor = self.textCursor() if thePosition > 0:
theCursor.setPosition(thePosition) theCursor = self.textCursor()
self.setTextCursor(theCursor) theCursor.setPosition(thePosition)
self.setTextCursor(theCursor)
return True return True
def getCursorPosition(self): def getCursorPosition(self):
@@ -41,8 +41,8 @@ class GuiDocTree(QTreeWidget):
self.theProject = theProject self.theProject = theProject
# Tree Settings # Tree Settings
self.theMap = None self.theMap = None
self.orphRoot = None self.orphRoot = None
self.clearTree() self.clearTree()
@@ -97,15 +97,23 @@ class GuiDocTree(QTreeWidget):
return False return False
if itemClass is None and pHandle is not None: 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 itemClass is None:
if itemType is not None: if itemType is not None:
if itemType == nwItemType.FILE: 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 return False
elif itemType == nwItemType.FOLDER: 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 return False
self.makeAlert("Failed to add new item.", nwAlert.BUG) self.makeAlert("Failed to add new item.", nwAlert.BUG)
return False return False
@@ -134,10 +142,14 @@ class GuiDocTree(QTreeWidget):
# If we again has no home, give up # If we again has no home, give up
if pHandle is None: 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 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: if itemType == nwItemType.FILE:
tHandle = self.theProject.newFile("New File", itemClass, pHandle) tHandle = self.theProject.newFile("New File", itemClass, pHandle)
elif itemType == nwItemType.FOLDER: elif itemType == nwItemType.FOLDER:
+65
View File
@@ -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("<b>Note:</b> %s" % theNote)
self.setVisible(True)
return
def hideNote(self):
self.noteLabel.setText("")
self.setVisible(False)
return
# END Class GuiNoticeBar
@@ -26,7 +26,7 @@ class GuiSearchBar(QFrame):
def __init__(self, theParent): def __init__(self, theParent):
QFrame.__init__(self, theParent) QFrame.__init__(self, theParent)
logger.debug("Initialising GuiSearchBar ...") logger.debug("Initialising SearchBar ...")
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.theParent = theParent self.theParent = theParent
@@ -73,7 +73,7 @@ class GuiSearchBar(QFrame):
self._replaceVisible(False) self._replaceVisible(False)
logger.debug("GuiSearchBar initialisation complete") logger.debug("SearchBar initialisation complete")
return return
+32 -26
View File
@@ -14,36 +14,38 @@ import logging
import time import time
import nw import nw
from os import path from os import path
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QPixmap, QColor from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import ( from PyQt5.QtGui import QIcon, QPixmap, QColor
from PyQt5.QtWidgets import (
qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog,
QShortcut, QMessageBox, QProgressDialog, QDialog QShortcut, QMessageBox, QProgressDialog, QDialog
) )
from nw.gui.doctree import GuiDocTree from nw.gui.mainmenu import GuiMainMenu
from nw.gui.doceditor import GuiDocEditor from nw.gui.statusbar import GuiMainStatus
from nw.gui.docviewer import GuiDocViewer from nw.gui.elements.doctree import GuiDocTree
from nw.gui.docdetails import GuiDocDetails from nw.gui.elements.doceditor import GuiDocEditor
from nw.gui.searchbar import GuiSearchBar from nw.gui.elements.docviewer import GuiDocViewer
from nw.gui.mainmenu import GuiMainMenu from nw.gui.elements.docdetails import GuiDocDetails
from nw.gui.configeditor import GuiConfigEditor from nw.gui.elements.searchbar import GuiSearchBar
from nw.gui.projecteditor import GuiProjectEditor from nw.gui.elements.noticebar import GuiNoticeBar
from nw.gui.export import GuiExport from nw.gui.dialogs.configeditor import GuiConfigEditor
from nw.gui.itemeditor import GuiItemEditor from nw.gui.dialogs.projecteditor import GuiProjectEditor
from nw.gui.statusbar import GuiMainStatus from nw.gui.dialogs.export import GuiExport
from nw.gui.timelineview import GuiTimeLineView from nw.gui.dialogs.itemeditor import GuiItemEditor
from nw.gui.sessionlog import GuiSessionLogView from nw.gui.dialogs.timelineview import GuiTimeLineView
from nw.project.project import NWProject from nw.gui.dialogs.sessionlog import GuiSessionLogView
from nw.project.document import NWDoc from nw.project.project import NWProject
from nw.project.item import NWItem from nw.project.document import NWDoc
from nw.project.index import NWIndex from nw.project.item import NWItem
from nw.project.backup import NWBackup from nw.project.index import NWIndex
from nw.tools.wordcount import countWords from nw.project.backup import NWBackup
from nw.theme import Theme from nw.tools.wordcount import countWords
from nw.enum import nwItemType, nwAlert from nw.theme import Theme
from nw.constants import nwFiles from nw.enum import nwItemType, nwAlert
from nw.constants import nwFiles
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -71,6 +73,7 @@ class GuiMain(QMainWindow):
# Main GUI Elements # Main GUI Elements
self.statusBar = GuiMainStatus(self) self.statusBar = GuiMainStatus(self)
self.noticeBar = GuiNoticeBar(self)
self.docEditor = GuiDocEditor(self, self.theProject) self.docEditor = GuiDocEditor(self, self.theProject)
self.docViewer = GuiDocViewer(self, self.theProject) self.docViewer = GuiDocViewer(self, self.theProject)
self.docDetails = GuiDocDetails(self, self.theProject) self.docDetails = GuiDocDetails(self, self.theProject)
@@ -85,6 +88,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.addWidget(self.treeView) self.treeBox.addWidget(self.treeView)
self.treeBox.addWidget(self.docDetails) self.treeBox.addWidget(self.docDetails)
self.treePane.setLayout(self.treeBox) self.treePane.setLayout(self.treeBox)
@@ -93,6 +97,7 @@ class GuiMain(QMainWindow):
self.docView = QVBoxLayout() self.docView = QVBoxLayout()
self.docView.setContentsMargins(0,0,0,0) self.docView.setContentsMargins(0,0,0,0)
self.docView.addWidget(self.searchBar) self.docView.addWidget(self.searchBar)
self.docView.addWidget(self.noticeBar)
self.docView.addWidget(self.docEditor) self.docView.addWidget(self.docEditor)
self.docPane.setLayout(self.docView) self.docPane.setLayout(self.docView)
@@ -102,6 +107,7 @@ class GuiMain(QMainWindow):
self.splitView.splitterMoved.connect(self._splitViewMove) self.splitView.splitterMoved.connect(self._splitViewMove)
self.splitMain = QSplitter(Qt.Horizontal) self.splitMain = QSplitter(Qt.Horizontal)
self.splitMain.setContentsMargins(4,4,4,4)
self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.treePane)
self.splitMain.addWidget(self.splitView) self.splitMain.addWidget(self.splitView)
self.splitMain.setSizes(self.mainConf.mainPanePos) self.splitMain.setSizes(self.mainConf.mainPanePos)
+19 -8
View File
@@ -25,11 +25,12 @@ class NWDoc():
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.theProject = theProject self.theProject = theProject
self.theParent = theParent self.theParent = theParent
self.theItem = None self.theItem = None
self.docHandle = None self.docHandle = None
self.docEditable = False
# Internal Mapping # Internal Mapping
self.makeAlert = self.theParent.makeAlert self.makeAlert = self.theParent.makeAlert
@@ -37,8 +38,9 @@ class NWDoc():
return return
def clearDocument(self): def clearDocument(self):
self.theItem = None self.theItem = None
self.docHandle = None self.docHandle = None
self.docEditable = False
return return
def openDocument(self, tHandle, showStatus=True): def openDocument(self, tHandle, showStatus=True):
@@ -46,6 +48,15 @@ class NWDoc():
self.docHandle = tHandle self.docHandle = tHandle
self.theItem = self.theProject.getItem(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) docDir, docFile = self._assemblePath(self.FILE_MN)
logger.debug("Opening document %s" % path.join(docDir,docFile)) logger.debug("Opening document %s" % path.join(docDir,docFile))
dataDir = path.join(self.theProject.projPath, docDir) dataDir = path.join(self.theProject.projPath, docDir)
@@ -75,7 +86,7 @@ class NWDoc():
def saveDocument(self, docText): def saveDocument(self, docText):
if self.docHandle is None: 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.FILE_MN)
+13
View File
@@ -478,6 +478,19 @@ 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 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): def getProjectItems(self):
"""This function is called from the tree view when building the tree. Each item in the """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 project is returned in the order saved in the project file, but first it checks that it has
@@ -1 +1,3 @@
# Very Deep File # Very Deep File
This file is still editable.
+16 -16
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.3.2" fileVersion="1.0" timeStamp="2019-10-31 11:33:41"> <novelWriterXML appVersion="0.3.2" fileVersion="1.0" timeStamp="2019-10-31 13:54:16">
<project> <project>
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
@@ -11,7 +11,7 @@
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<lastEdited>b8136a5a774a0</lastEdited> <lastEdited>b8136a5a774a0</lastEdited>
<lastViewed>ba8a28a246524</lastViewed> <lastViewed>ba8a28a246524</lastViewed>
<lastWordCount>879</lastWordCount> <lastWordCount>884</lastWordCount>
<autoReplace> <autoReplace>
<A>B</A> <A>B</A>
<B>E</B> <B>E</B>
@@ -142,7 +142,7 @@
<charCount>567</charCount> <charCount>567</charCount>
<wordCount>112</wordCount> <wordCount>112</wordCount>
<paraCount>6</paraCount> <paraCount>6</paraCount>
<cursorPos>634</cursorPos> <cursorPos>326</cursorPos>
</item> </item>
<item handle="f6622b4617424" order="1" parent="None"> <item handle="f6622b4617424" order="1" parent="None">
<name>Characters</name> <name>Characters</name>
@@ -220,19 +220,7 @@
<status>None</status> <status>None</status>
<expanded>True</expanded> <expanded>True</expanded>
</item> </item>
<item handle="4cd0bd12b087d" order="0" parent="98acd8c76c93a"> <item handle="b8136a5a774a0" order="0" parent="98acd8c76c93a">
<name>Orphaned File 2</name>
<type>FILE</type>
<class>NO_CLASS</class>
<status>None</status>
<expanded>False</expanded>
<layout>NO_LAYOUT</layout>
<charCount>14</charCount>
<wordCount>3</wordCount>
<paraCount>0</paraCount>
<cursorPos>1</cursorPos>
</item>
<item handle="b8136a5a774a0" order="1" parent="98acd8c76c93a">
<name>Delete Me!</name> <name>Delete Me!</name>
<type>FILE</type> <type>FILE</type>
<class>NOVEL</class> <class>NOVEL</class>
@@ -244,5 +232,17 @@
<paraCount>1</paraCount> <paraCount>1</paraCount>
<cursorPos>36</cursorPos> <cursorPos>36</cursorPos>
</item> </item>
<item handle="4cd0bd12b087d" order="1" parent="98acd8c76c93a">
<name>Orphaned File 1</name>
<type>FILE</type>
<class>NO_CLASS</class>
<status>None</status>
<expanded>False</expanded>
<layout>NO_LAYOUT</layout>
<charCount>42</charCount>
<wordCount>8</wordCount>
<paraCount>1</paraCount>
<cursorPos>47</cursorPos>
</item>
</content> </content>
</novelWriterXML> </novelWriterXML>
+5 -4
View File
@@ -8,10 +8,11 @@ from nwtools import *
from os import path, unlink from os import path, unlink
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from nw.gui.projecteditor import GuiProjectEditor from nw.gui.dialogs.projecteditor import GuiProjectEditor
from nw.gui.timelineview import GuiTimeLineView from nw.gui.dialogs.timelineview import GuiTimeLineView
from nw.gui.itemeditor import GuiItemEditor from nw.gui.dialogs.itemeditor import GuiItemEditor
from nw.enum import *
from nw.enum import *
keyDelay = 10 keyDelay = 10
stepDelay = 50 stepDelay = 50