Merge pull request #416 from vkbo/fixes

Outtake/Archive Fixes
This commit is contained in:
Veronica K. Berglyd Olsen
2020-08-16 22:05:21 +02:00
committed by GitHub
8 changed files with 103 additions and 70 deletions
+7 -6
View File
@@ -48,9 +48,10 @@ addition to the word count.
Project Tree Drag and Drop
--------------------------
The project tree allows dragging and drop to a certain extent, but it does not allow bulk actions.
This is deliberate to avoid accidentally messing up your project. The project tree has no undo
function.
The project tree allows dragging and drop to a certain extent. This feature is primarily intended
for rearranging the order of your files within each root folder, and has only limited support for
moving files elsewhere in the project tree. In general, bulk actions are not allowed. This is
deliberate to avoid accidentally messing up your project. The project tree has no undo function.
Document files and their folders can be rearranged freely within their root folders. Novel files
cannot be moved out of the :guilabel:`Novel` folder, except to :guilabel:`Trash` and the
@@ -59,9 +60,9 @@ cannot be moved out of the :guilabel:`Novel` folder, except to :guilabel:`Trash`
Folders cannot be moved at all outside their root tree. Neither can a folder containing files be
deleted. You must first delete the files.
Root folders in the project tree cannot be dragged and dropped. However, if you want to reorder
them, you can move them up or down with respect to eachother from the :guilabel:`Tools` menu, or by
pressing :kbd:`Ctrl`:kbd:`Shift` and the :kbd:`Up` or :kbd:`Down` key.
Root folders in the project tree cannot be dragged and dropped at all. However, if you want to
reorder them, you can move them up or down with respect to eachother from the :guilabel:`Tools`
menu, or by pressing :kbd:`Ctrl`:kbd:`Shift` and the :kbd:`Up` or :kbd:`Down` key.
.. _a_ui_edit:
+7 -9
View File
@@ -90,10 +90,8 @@ trash folder can then be deleted permanently, either individually, or by emptyin
menu. Files in this folder are removed from the project index and cannot be referenced.
Folders and root folders can only be deleted when they are empty. Recursive deletion is not
supported.
A document file or a folder can be deleted from the :guilabel:`project` menu, or by pressing
:kbd:`Ctrl`:kbd:`Del`.
supported. A document file or a folder can be deleted from the :guilabel:`project` menu, or by
pressing :kbd:`Ctrl`:kbd:`Del`.
.. _a_proj_roots_out:
@@ -103,13 +101,13 @@ Archived Documents (Outtakes)
If you don't want to delete a file, or put it in the :guilabel:`Trash` folder where it may be
deleted, but still want it out of your main project tree, you can create an :guilabel:`Outtakes`
root folder from the :guilabel:`project` menu. This folder is similar to the :guilabel:`Trash`
folder in that it doesn't allow subfolders and creating new files.
root folder from the :guilabel:`Project` menu. You are not allowed to move folders to this root
folder, only files. If you need folders in it to organise your files, you can of course create new
ones there.
You can drag any file to this folder and preserve its settings. The file will always be excluded
from the :guilabel:`Build Novel Project` builds, even with the setting to ignore the excluded files
option. The file is also removed from the project index, so the tags and references defined in it
will not show up anywhere else.
from the :guilabel:`Build Novel Project` builds. The file is also removed from the project index, so
the tags and references defined in it will not show up anywhere else.
.. _a_proj_roots_orph:
+2
View File
@@ -33,6 +33,8 @@ class nwConst():
fStampFmt = "%Y-%m-%d %H.%M.%S" # FileName safe format
dStampFmt = "%Y-%m-%d" # Date only format
maxDepth = 30 # Maximum folder depth of a project
# END Class nwConst
class nwRegEx():
+2 -2
View File
@@ -33,7 +33,7 @@ from os import path, rename, unlink
from nw.core.item import NWItem
from nw.constants import nwAlert
from nw.common import isHandle
from nw.constants import nwItemLayout, nwItemClass
from nw.constants import nwItemLayout, nwItemClass, nwConst
logger = logging.getLogger(__name__)
@@ -213,7 +213,7 @@ class NWDoc():
# Scan for handles
thePath = []
for n in range(200):
for n in range(nwConst.maxDepth + 5):
if len(theMeta) < 14:
break
if theMeta[13] == ":":
+19 -16
View File
@@ -176,7 +176,7 @@ class NWIndex():
if "textCounts" in theData.keys():
self.textCounts = theData["textCounts"]
nowTime = time()
nowTime = round(time())
self.timeNovel = nowTime
self.timeNote = nowTime
self.timeIndex = nowTime
@@ -268,21 +268,28 @@ class NWIndex():
"""
theItem = self.theProject.projTree[tHandle]
theRoot = self.theProject.projTree.getRootItem(tHandle)
if theItem is None:
logger.error("Not indexing unknown item %s" % tHandle)
return False
if theItem.itemType != nwItemType.FILE:
logger.error("Not indexing non-file item %s" % tHandle)
return False
if theItem.parHandle == self.theProject.projTree.trashRoot():
logger.error("Not indexing trash item %s" % tHandle)
return False
if theItem.itemLayout == nwItemLayout.NO_LAYOUT:
logger.error("Not indexing no-layout item %s" % tHandle)
return False
if theRoot is None:
logger.error("Not indexing homeless item %s" % tHandle)
return False
# Run word counter for the whole text
cC, wC, pC = countWords(theText)
self.textCounts[tHandle] = [cC, wC, pC]
# If the file is archived or trashed, we don't index the file itself
if theItem.parHandle == self.theProject.projTree.trashRoot():
logger.error("Not indexing trash item %s" % tHandle)
return False
if theRoot.itemClass == nwItemClass.ARCHIVE:
logger.error("Not indexing archived item %s" % tHandle)
return False
@@ -297,7 +304,7 @@ class NWIndex():
self.refIndex[tHandle] = {}
self.refIndex[tHandle]["T000000"] = {
"tags" : [],
"updated" : time(),
"updated" : round(time()),
}
if itemLayout == nwItemLayout.NOTE:
self.noteIndex[tHandle] = {}
@@ -351,12 +358,8 @@ class NWIndex():
lastText = "\n".join(theLines[nTitle-1:nLine-1])
self._indexWordCounts(tHandle, isNovel, lastText, nTitle)
# Run word counter for whole text
cC, wC, pC = countWords(theText)
self.textCounts[tHandle] = [cC, wC, pC]
# Update timestamps for index changes
nowTime = time()
nowTime = round(time())
self.timeIndex = nowTime
if isNovel:
self.timeNovel = nowTime
@@ -391,7 +394,7 @@ class NWIndex():
sTitle = "T%06d" % nLine
self.refIndex[tHandle][sTitle] = {
"tags" : [],
"updated" : time(),
"updated" : round(time()),
}
theData = {
"level" : hDepth,
@@ -401,7 +404,7 @@ class NWIndex():
"cCount" : 0,
"wCount" : 0,
"pCount" : 0,
"updated" : time(),
"updated" : round(time()),
}
if hText != "":
@@ -425,14 +428,14 @@ class NWIndex():
self.novelIndex[tHandle][sTitle]["cCount"] = cC
self.novelIndex[tHandle][sTitle]["wCount"] = wC
self.novelIndex[tHandle][sTitle]["pCount"] = pC
self.novelIndex[tHandle][sTitle]["updated"] = time()
self.novelIndex[tHandle][sTitle]["updated"] = round(time())
else:
if tHandle in self.noteIndex:
if sTitle in self.noteIndex[tHandle]:
self.noteIndex[tHandle][sTitle]["cCount"] = cC
self.noteIndex[tHandle][sTitle]["wCount"] = wC
self.noteIndex[tHandle][sTitle]["pCount"] = pC
self.noteIndex[tHandle][sTitle]["updated"] = time()
self.noteIndex[tHandle][sTitle]["updated"] = round(time())
return
def _indexSynopsis(self, tHandle, isNovel, theText, nTitle):
@@ -443,12 +446,12 @@ class NWIndex():
if tHandle in self.novelIndex:
if sTitle in self.novelIndex[tHandle]:
self.novelIndex[tHandle][sTitle]["synopsis"] = theText
self.novelIndex[tHandle][sTitle]["updated"] = time()
self.novelIndex[tHandle][sTitle]["updated"] = round(time())
else:
if tHandle in self.noteIndex:
if sTitle in self.noteIndex[tHandle]:
self.noteIndex[tHandle][sTitle]["synopsis"] = theText
self.noteIndex[tHandle][sTitle]["updated"] = time()
self.noteIndex[tHandle][sTitle]["updated"] = round(time())
return
def _indexNoteRef(self, tHandle, aLine, nLine, nTitle):
+5 -5
View File
@@ -35,7 +35,7 @@ from time import time
from nw.core.item import NWItem
from nw.common import checkString
from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout
from nw.constants import nwFiles, nwItemType, nwItemClass, nwItemLayout, nwConst
logger = logging.getLogger(__name__)
@@ -251,11 +251,11 @@ class NWTree():
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.
maximum depth to make infinite loops impossible.
"""
tItem = self.__getitem__(tHandle)
if tItem is not None:
for i in range(200):
for i in range(nwConst.maxDepth + 1):
if tItem.parHandle is None:
return tItem
else:
@@ -266,14 +266,14 @@ class NWTree():
def getItemPath(self, tHandle):
"""Iterate upwards in the tree until we find the item with
parent None, the root item, and return the list of handles.
We do this with a for loop with a maximum depth of 200 to make
We do this with a for loop with a maximum depth to make
infinite loops impossible.
"""
tTree = []
tItem = self.__getitem__(tHandle)
if tItem is not None:
tTree.append(tHandle)
for i in range(200):
for i in range(nwConst.maxDepth + 1):
if tItem.parHandle is None:
return tTree
else:
+43 -21
View File
@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import (
from nw.core import NWDoc
from nw.constants import (
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert
nwLabels, nwItemType, nwItemClass, nwItemLayout, nwAlert, nwConst
)
logger = logging.getLogger(__name__)
@@ -59,6 +59,7 @@ class GuiProjectTree(QTreeWidget):
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theProject = theParent.theProject
self.theIndex = theParent.theIndex
# Tree Settings
self.theMap = None
@@ -145,6 +146,9 @@ class GuiProjectTree(QTreeWidget):
if not self.theParent.hasProject:
return False
# The item needs to be assigned an item class, so one must be
# provided, or it must be possible to extract it from the parent
# item of the new item.
if itemClass is None and pHandle is not None:
pItem = self.theProject.projTree[pHandle]
if pItem is not None:
@@ -167,6 +171,7 @@ class GuiProjectTree(QTreeWidget):
self.makeAlert("Failed to add new item.", nwAlert.BUG)
return False
# Everything is fine, we have what we need, so we proceed
logger.verbose("Adding new item of type %s and class %s to handle %s" % (
itemType.name, itemClass.name, str(pHandle))
)
@@ -182,7 +187,9 @@ class GuiProjectTree(QTreeWidget):
# If still nothing, 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
# Now check if the selected item is a file, in which case
@@ -207,19 +214,23 @@ class GuiProjectTree(QTreeWidget):
)
return False
if pItem.itemClass == nwItemClass.ARCHIVE:
self.makeAlert(
"Cannot add new files or folders to the %s folder." % (
nwLabels.CLASS_NAME[nwItemClass.ARCHIVE]
), nwAlert.ERROR
)
return False
parTree = self.theProject.projTree.getItemPath(pHandle)
# 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:
if len(parTree) >= nwConst.maxDepth - 1:
# Folders cannot be deeper than maxDepth - 1, leaving room
# for one more level of files.
self.makeAlert((
"Cannot add new folder to this item. "
"Maximum folder depth has been reached."
), nwAlert.ERROR)
return False
tHandle = self.theProject.newFolder("New Folder", itemClass, pHandle)
else:
logger.error("Failed to add new item")
return False
@@ -332,7 +343,9 @@ class GuiProjectTree(QTreeWidget):
logger.debug("Emptying Trash folder")
if trashHandle is None:
self.makeAlert("There is no Trash folder.", nwAlert.INFO)
self.makeAlert(
"There is currently no Trash folder in this project.", nwAlert.INFO
)
return False
theTrash = self.getTreeFromHandle(trashHandle)
@@ -341,13 +354,13 @@ class GuiProjectTree(QTreeWidget):
nTrash = len(theTrash)
if nTrash == 0:
self.makeAlert("The Trash folder is empty.", nwAlert.INFO)
self.makeAlert("The Trash folder is already empty.", nwAlert.INFO)
return False
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Empty Trash", "Permanently delete %d file%s from Trash?" % (
nTrash, "s"*int(nTrash > 1)
nTrash, "s" if nTrash > 1 else ""
)
)
if msgRes != QMessageBox.Yes:
@@ -420,7 +433,7 @@ class GuiProjectTree(QTreeWidget):
theDoc = NWDoc(self.theProject, self.theParent)
theDoc.deleteDocument(tHandle)
del self.theProject.projTree[tHandle]
self.theParent.theIndex.deleteHandle(tHandle)
self.theIndex.deleteHandle(tHandle)
else:
# The file is not already in the trash folder, so we
@@ -448,7 +461,7 @@ class GuiProjectTree(QTreeWidget):
self.propagateCount(tHandle, wCount)
self._setTreeChanged(True)
self.theParent.theIndex.deleteHandle(tHandle)
self.theIndex.deleteHandle(tHandle)
elif nwItemS.itemType == nwItemType.FOLDER:
logger.debug("User requested folder %s deleted" % tHandle)
@@ -461,7 +474,11 @@ class GuiProjectTree(QTreeWidget):
trItemP.takeChild(tIndex)
del self.theProject.projTree[tHandle]
else:
self.makeAlert("Cannot delete folder. It is not empty.", nwAlert.ERROR)
self.makeAlert((
"Cannot delete folder. It is not empty. "
"Recursive deletion is not supported. "
"Please delete the content first."
), nwAlert.ERROR)
return False
elif nwItemS.itemType == nwItemType.ROOT:
@@ -473,7 +490,11 @@ class GuiProjectTree(QTreeWidget):
self.theParent.mainMenu.setAvailableRoot()
self._setTreeChanged(True)
else:
self.makeAlert("Cannot delete root folder. It is not empty.", nwAlert.ERROR)
self.makeAlert((
"Cannot delete root folder. It is not empty. "
"Recursive deletion is not supported. "
"Please delete the content first."
), nwAlert.ERROR)
return False
return True
@@ -532,7 +553,7 @@ class GuiProjectTree(QTreeWidget):
pCount += int(pItem.child(i).text(self.C_COUNT))
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
if not nDepth > 200 and pHandle != "":
if not nDepth > nwConst.maxDepth + 1 and pHandle != "":
self.propagateCount(pHandle, pCount, nDepth+1)
return
@@ -710,11 +731,12 @@ class GuiProjectTree(QTreeWidget):
self.propagateCount(sHandle, wCount)
# The items dropped into archive or trash should be removed
# from the project index
# from the project index, for all other items, we rescan the
# file to ensure the index is up to date.
if onFree:
self.theParent.theIndex.deleteHandle(sHandle)
self.theIndex.deleteHandle(sHandle)
else:
self.theParent.theIndex.reIndexHandle(sHandle)
self.theIndex.reIndexHandle(sHandle)
else:
logger.debug("Drag'n'drop of item %s not accepted" % sHandle)
@@ -964,7 +986,7 @@ class GuiProjectTreeMenu(QMenu):
showEdit = not isTrash and not isOrph
showExport = isFile and not inTrash and not isOrph
showNewFile = not isTrash and not inTrash and not isOrph and not isArch
showNewFolder = not isTrash and not inTrash and not isOrph and not isArch
showNewFolder = not isTrash and not inTrash and not isOrph
showDelete = not isTrash
showEmpty = isTrash
+18 -11
View File
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="0.12.1" hexVersion="0x001201f0" fileVersion="1.2" timeStamp="2020-08-16 17:50:53">
<novelWriterXML appVersion="0.12.1" hexVersion="0x001201f0" fileVersion="1.2" timeStamp="2020-08-16 21:12:32">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>713</saveCount>
<autoCount>138</autoCount>
<editTime>35134</editTime>
<saveCount>723</saveCount>
<autoCount>140</autoCount>
<editTime>35485</editTime>
</project>
<settings>
<doBackup>False</doBackup>
@@ -15,8 +15,8 @@
<autoOutline>True</autoOutline>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastWordCount>1071</lastWordCount>
<novelWordCount>695</novelWordCount>
<lastWordCount>1077</lastWordCount>
<novelWordCount>701</novelWordCount>
<notesWordCount>376</notesWordCount>
<autoReplace>
<entry key="A">B</entry>
@@ -46,7 +46,7 @@
<entry blue="175" green="0" red="117">Main</entry>
</importance>
</settings>
<content count="24">
<content count="25">
<item handle="7031beac91f75" order="0" parent="None">
<name>Novel</name>
<type>ROOT</type>
@@ -269,7 +269,14 @@
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="8a5deb88c0e97" order="0" parent="6827118336ac1">
<item handle="ae9bf3c3ea159" order="0" parent="6827118336ac1">
<name>Scenes</name>
<type>FOLDER</type>
<class>ARCHIVE</class>
<status>None</status>
<expanded>True</expanded>
</item>
<item handle="8a5deb88c0e97" order="0" parent="ae9bf3c3ea159">
<name>Old File</name>
<type>FILE</type>
<class>NOVEL</class>
@@ -295,9 +302,9 @@
<status>New</status>
<exported>True</exported>
<layout>SCENE</layout>
<charCount>0</charCount>
<wordCount>0</wordCount>
<paraCount>0</paraCount>
<charCount>30</charCount>
<wordCount>6</wordCount>
<paraCount>1</paraCount>
<cursorPos>36</cursorPos>
</item>
</content>