@@ -1,5 +1,19 @@
|
||||
# novelWriter ChangeLog
|
||||
|
||||
## Version 0.8 [2020-xx-xx]
|
||||
|
||||
**User Interface**
|
||||
|
||||
* A details panel below the Outline tree view has been added. The panel shows all the information of a selected row in the tree view above, including hidden columns, and some additional information. The tags and references also become clickable links that when clicked will open in the document viewer. PR #281.
|
||||
* Added a context menu to the project tree for easier access to some of the most use actions on the tree. PR #282.
|
||||
* Improved the support for High DPI screens. Margins and box sizes that are hardcoded should now scale. User settings should also scale back and forth when switching between scale factors. Issue #280, PR #285.
|
||||
|
||||
**Project Structure**
|
||||
|
||||
* The way GUI states of switches, column widths, etc., is saved has been improved a bit during the High DPI updates. PRs #285 and #286.
|
||||
* Some settings have been moved around to more appropriate sections in the project XML file. The project load function still reads the values from the previous location if opening an older project file. PR #288.
|
||||
|
||||
|
||||
## Version 0.7.1 [2020-06-06]
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
@@ -6,27 +6,41 @@
|
||||
|
||||
<img align="left" style="margin: 0 16px 4px 0;" src="assets/icons/96x96/novelwriter.png">
|
||||
novelWriter is a markdown-like text editor designed for writing novels and larger projects of many smaller plain text documents.
|
||||
It uses its own flavour of markdown that supports a meta data syntax for comments, synopsis and cross-referencing between files.
|
||||
The idea is to have a simple text editor which allows for easy organisation of text files and notes, built on a plain text file project repository for robustness.
|
||||
The plain text storage is suitable for version control software, and also well suited for file synchronisation tools.
|
||||
The core project structure is stored in a project XML file.
|
||||
Other meta data is primarily saved in JSON files.
|
||||
|
||||
novelWriter uses its own flavour of markdown that supports a meta data syntax for comments, synopsis and cross-referencing between files.
|
||||
|
||||
novelWriter is licensed under GPLv3, see the [GNU General Public License website](https://www.gnu.org/licenses/gpl-3.0.en.html) for more details, or consult the [LICENSE](LICENSE.md) file.
|
||||
The bundled Typicon-based icon themes are licensed under [CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/).
|
||||
|
||||
Below is a short overview of the features of novelWriter.
|
||||
The full documentation is available at [novelwriter.readthedocs.io](https://novelwriter.readthedocs.io/).
|
||||
|
||||
### Note
|
||||
|
||||
The application is under initial development, and not all planned features are included.
|
||||
The core functionality is, however, in place and has been used for a while by the author and collaborators.
|
||||
The application is still under initial development, but all core features have now been added.
|
||||
The core functionality has been in place for a while, and novelWriter is being used for writing projects by the author and collaborators.
|
||||
|
||||
New features are being added regularly, until the core toolset is complete.
|
||||
When all planned initial features are in place, a release 1.0 will be made.
|
||||
Until then, novelWriter is in a pre-release alpha state, and should be considered experimental.
|
||||
If you do use it for real projects, please run backups frequently to avoid data losses.
|
||||
No new major features will be added at this time, until the application is stable.
|
||||
Until then, novelWriter is in a beta state.
|
||||
Please report any issues you may encounter in the repository issue tracker.
|
||||
|
||||
You should be able to use novelWriter for real projects, but as with all software, please make regular backups.
|
||||
There is a built in backup feature that can pack the entire project into a zip file on close.
|
||||
Please check the documentation for further details.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This is Open Source software, and novelWriter is licensed under GPLv3.
|
||||
See the [GNU General Public License website](https://www.gnu.org/licenses/gpl-3.0.en.html) for more details, or consult the [LICENSE](LICENSE.md) file.
|
||||
|
||||
Bundled assets have the following licenses:
|
||||
|
||||
* The Typicon-based icon themes by Stephen Hutchings are licensed under [CC BY-SA 4.0](http://creativecommons.org/licenses/by-sa/4.0/). The icons have been altered in size and colour for use with novelWriter, and some additional icons added. The original icon set is available at [stephenhutchings/typicons.font](https://github.com/stephenhutchings/typicons.font).
|
||||
* The Cantarell font by Dave Crossland is licensed under [OPEN FONT LICENSE Version 1.1](http://scripts.sil.org/OFL). It is available at [Google Fonts](https://fonts.google.com/specimen/Cantarell).
|
||||
* The Tomorrow syntax themes use colour schemes taken from Chris Kempson's collection of code editor themes, licensed with the [MIT License](https://github.com/chriskempson/tomorrow-theme/blob/master/LICENSE.md), and the main repo is available at [chriskempson/tomorrow-theme](https://github.com/chriskempson/tomorrow-theme).
|
||||
* Likewise, the Owl syntax themes use colours from Sarah Drasner's code editor themes, licensed with the [MIT License](https://github.com/sdras/night-owl-vscode-theme/blob/master/LICENSE), and the main repo is available at [sdras/night-owl-vscode-theme](https://github.com/sdras/night-owl-vscode-theme).
|
||||
|
||||
|
||||
## Markdown Flavour
|
||||
|
||||
novelWriter is **not** a full-feature Markdown editor.
|
||||
@@ -49,31 +63,42 @@ In addition, novelWriter adds the following, which is otherwise not supported by
|
||||
* Non-breaking spaces are supported as long as your system is using at least Qt 5.9.
|
||||
For earlier version, non-breaking spaces are converted to normal spaces when saving the document.
|
||||
This is done by the Qt library.
|
||||
From Qt 5.9 and on, it is possible to extract the raw text from a document, which preserves non-breaking spaces.
|
||||
* Tabs may be rendered, depending on export format.
|
||||
With Qt 5.10 or higher, the width of a tab in pixels can be changed in Preferences.
|
||||
|
||||
The core export format of novelWriter is HTML5.
|
||||
You can also export the entire project as a single novelWriter flavour document.
|
||||
In addition, other exports to Open Document, PDF, and plain text is offered through the Qt library, although with limitations to formatting.
|
||||
|
||||
Even though novelWriter can export to Open Document, the result is actually better when using the HTML output and then importing the HTML document into for instance Libre Office.
|
||||
The HTML output is also suitable for conversion with tools like Pandoc.
|
||||
|
||||
The core export format that should render properly all supported features is the HTML export.
|
||||
This format also forms the basis of conversion to Office type document formats with Pandoc.
|
||||
Note that Pandoc itself strips some formatting from the document during conversion, so the final result may be different than expected.
|
||||
|
||||
## Implementation
|
||||
|
||||
The application is written in Python3 using Qt5 via PyQt5.
|
||||
It is developed on Linux, but it should in principle work fine on other operating systems as well as long as dependencies are met.
|
||||
It is regularly tested on Windows 10.
|
||||
|
||||
The application can be started from the source folder with the command:
|
||||
The application can be started from the source folder with one of the commands:
|
||||
```
|
||||
./novelWriter.py
|
||||
python novelWriter.py
|
||||
python3 novelWriter.py
|
||||
```
|
||||
|
||||
It also takes a few parameters for debugging and such, which can be listed with the switch `--help`.
|
||||
|
||||
There are no launcher icons yet.
|
||||
Consult your operating system documentation for how to make those.
|
||||
These will be added at some point, and I would appreciate any assistance from people working on Windows and MacOS as I don't use either of those operating systems.
|
||||
In the root assets folder there are icons and scripts and a template for setting up a launcher on Gnome desktops.
|
||||
You may need to modify those scripts slightly, but as they are, they work on Debian and Ubuntu.
|
||||
For other operating systems, please consult your operating system documentation for how to make those.
|
||||
Feel free to submit more if you are able to make them.
|
||||
|
||||
|
||||
## Package Dependencies
|
||||
|
||||
It is recommended that novelWriter runs with Qt 5.9 or later, and Python 3.6 or later.
|
||||
It is recommended that novelWriter runs with Qt 5.10 or later, and Python 3.6 or later.
|
||||
Running with Qt as low as 5.2.1 and Python 3.4.3 has been tested, and worked in the past, but there are no guarantees that this will keep working as these are not a part of the test builds.
|
||||
|
||||
For the apt package manager on Debian systems, the following Python3 packages are needed:
|
||||
@@ -88,15 +113,15 @@ These are optional, but recommended:
|
||||
|
||||
Alternatively, the packages can be installed with `pip` by running
|
||||
```
|
||||
python3 -m pip install -r requirements.txt
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
in the application folder.
|
||||
|
||||
You can also do them one at a time, skipping the ones you don't need:
|
||||
```
|
||||
python3 -m pip install pyqt5
|
||||
python3 -m pip install lxml
|
||||
python3 -m pip install pyenchant
|
||||
pip install pyqt5
|
||||
pip install lxml
|
||||
pip install pyenchant
|
||||
```
|
||||
|
||||
PyQt/Qt should be at least 5.2.1, but ideally 5.10 or higher for nearly all features to work.
|
||||
@@ -136,7 +161,7 @@ Have a look in the existing folders for examples of how to define the colours.
|
||||
|
||||
### Auto-Saving and Document Stats
|
||||
|
||||
Open documents and the project file itself is saved regularly on a timer – if they have been altered.
|
||||
Open documents and the project file itself is saved regularly on a timer.
|
||||
The status of this is indicated by two indicators on the right hand side of the status bar.
|
||||
Unsaved changes are in yellow, and saved is indicated by green in the default theme.
|
||||
Latest word count for the document and project is shown next to these indicators in the status bar.
|
||||
@@ -168,7 +193,7 @@ These are optional files.
|
||||
|
||||
### Visualisation of Story Elements
|
||||
|
||||
The different notes can be assigned tags, which the novel files can refer back to using special meta keywords.
|
||||
The different notes can be assigned tags, which other files can refer back to using special meta keywords.
|
||||
This information can be used to display an outline of the story, showing where each scene connects to the plot, and which characters, etc. occur in them.
|
||||
In addition, the tags themselves are clickable in the document view pane, and control-clickable in the editor.
|
||||
They make it possible to quickly navigate between the documents while editing.
|
||||
|
||||
+2
-2
@@ -24,9 +24,9 @@ copyright = "2018-2020, Veronica Berglyd Olsen"
|
||||
author = "Veronica Berglyd Olsen"
|
||||
|
||||
# The short X.Y version
|
||||
version = "0.7.1"
|
||||
version = "0.8.0"
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = "0.7.1"
|
||||
release = "0.8.0rc1"
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
+5
-5
@@ -40,9 +40,9 @@ __package__ = "novelWriter"
|
||||
__author__ = "Veronica Berglyd Olsen"
|
||||
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
|
||||
__license__ = "GPLv3"
|
||||
__version__ = "0.7.1"
|
||||
__hexversion__ = "0x000701f0"
|
||||
__date__ = "2020-06-06"
|
||||
__version__ = "0.8.0rc1"
|
||||
__hexversion__ = "0x000800c1"
|
||||
__date__ = "2020-06-01"
|
||||
__maintainer__ = "Veronica Berglyd Olsen"
|
||||
__email__ = "code@vkbo.net"
|
||||
__status__ = "Pre-Release"
|
||||
@@ -172,7 +172,7 @@ def main(sysArgs=None):
|
||||
debugLevel = logging.INFO
|
||||
elif inOpt == "--debug":
|
||||
debugLevel = logging.DEBUG
|
||||
logFormat = "[{asctime:}] {name:>20}:{lineno:<4d} {levelname:8} {message:}"
|
||||
logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}"
|
||||
elif inOpt == "--logfile":
|
||||
logFile = inArg
|
||||
toFile = True
|
||||
@@ -180,7 +180,7 @@ def main(sysArgs=None):
|
||||
toStd = False
|
||||
elif inOpt == "--verbose":
|
||||
debugLevel = VERBOSE
|
||||
logFormat = "[{asctime:}] {name:>20}:{lineno:<4d} {levelname:8} {message:}"
|
||||
logFormat = "[{asctime:}] {name:>22}:{lineno:<4d} {levelname:8} {message:}"
|
||||
elif inOpt == "--style":
|
||||
qtStyle = inArg
|
||||
elif inOpt == "--config":
|
||||
|
||||
+77
-14
@@ -88,6 +88,7 @@ class Config:
|
||||
self.guiLang = "en" # Hardcoded for now
|
||||
self.guiFont = ""
|
||||
self.guiFontSize = 11
|
||||
self.guiScale = 1.0 # Set automatically by Theme class
|
||||
|
||||
## Sizes
|
||||
self.winGeometry = [1100, 650]
|
||||
@@ -95,6 +96,7 @@ class Config:
|
||||
self.projColWidth = [140, 55, 140]
|
||||
self.mainPanePos = [300, 800]
|
||||
self.docPanePos = [400, 400]
|
||||
self.outlnPanePos = [500, 150]
|
||||
self.isFullScreen = False
|
||||
|
||||
## Project
|
||||
@@ -124,8 +126,8 @@ class Config:
|
||||
self.highlightQuotes = True
|
||||
|
||||
self.fmtApostrophe = nwUnicode.U_RSQUO
|
||||
self.fmtSingleQuotes = [nwUnicode.U_LSQUO,nwUnicode.U_RSQUO]
|
||||
self.fmtDoubleQuotes = [nwUnicode.U_LDQUO,nwUnicode.U_RDQUO]
|
||||
self.fmtSingleQuotes = [nwUnicode.U_LSQUO, nwUnicode.U_RSQUO]
|
||||
self.fmtDoubleQuotes = [nwUnicode.U_LDQUO, nwUnicode.U_RDQUO]
|
||||
|
||||
self.spellTool = None
|
||||
self.spellLanguage = None
|
||||
@@ -196,7 +198,23 @@ class Config:
|
||||
return
|
||||
|
||||
##
|
||||
# Actions
|
||||
# Methods
|
||||
##
|
||||
|
||||
def pxInt(self, theSize):
|
||||
"""Used to scale fixed gui sizes by the screen scale factor.
|
||||
This function returns an int, which is always rounded down.
|
||||
"""
|
||||
return int(theSize*self.guiScale)
|
||||
|
||||
def rpxInt(self, theSize):
|
||||
"""Used to un-scale fixed gui sizes by the screen scale factor.
|
||||
This function returns an int, which is always rounded down.
|
||||
"""
|
||||
return int(theSize/self.guiScale)
|
||||
|
||||
##
|
||||
# Config Actions
|
||||
##
|
||||
|
||||
def initConfig(self, confPath=None, dataPath=None):
|
||||
@@ -342,6 +360,9 @@ class Config:
|
||||
self.docPanePos = self._parseLine(
|
||||
cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos
|
||||
)
|
||||
self.outlnPanePos = self._parseLine(
|
||||
cnfParse, cnfSec, "outlinepane", self.CNF_LIST, self.outlnPanePos
|
||||
)
|
||||
self.isFullScreen = self._parseLine(
|
||||
cnfParse, cnfSec, "fullscreen", self.CNF_BOOL, self.isFullScreen
|
||||
)
|
||||
@@ -481,12 +502,13 @@ class Config:
|
||||
## Sizes
|
||||
cnfSec = "Sizes"
|
||||
cnfParse.add_section(cnfSec)
|
||||
cnfParse.set(cnfSec,"geometry", self._packList(self.winGeometry))
|
||||
cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth))
|
||||
cnfParse.set(cnfSec,"projcols", self._packList(self.projColWidth))
|
||||
cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos))
|
||||
cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos))
|
||||
cnfParse.set(cnfSec,"fullscreen", str(self.isFullScreen))
|
||||
cnfParse.set(cnfSec,"geometry", self._packList(self.winGeometry))
|
||||
cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth))
|
||||
cnfParse.set(cnfSec,"projcols", self._packList(self.projColWidth))
|
||||
cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos))
|
||||
cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos))
|
||||
cnfParse.set(cnfSec,"outlinepane", self._packList(self.outlnPanePos))
|
||||
cnfParse.set(cnfSec,"fullscreen", str(self.isFullScreen))
|
||||
|
||||
## Project
|
||||
cnfSec = "Project"
|
||||
@@ -642,7 +664,7 @@ class Config:
|
||||
return True
|
||||
|
||||
##
|
||||
# Setters and Getters
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setConfPath(self, newPath):
|
||||
@@ -672,6 +694,8 @@ class Config:
|
||||
return True
|
||||
|
||||
def setWinSize(self, newWidth, newHeight):
|
||||
newWidth = int(newWidth/self.guiScale)
|
||||
newHeight = int(newHeight/self.guiScale)
|
||||
if abs(self.winGeometry[0] - newWidth) > 5:
|
||||
self.winGeometry[0] = newWidth
|
||||
self.confChanged = True
|
||||
@@ -681,25 +705,30 @@ class Config:
|
||||
return True
|
||||
|
||||
def setTreeColWidths(self, colWidths):
|
||||
self.treeColWidth = colWidths
|
||||
self.treeColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setProjColWidths(self, colWidths):
|
||||
self.projColWidth = colWidths
|
||||
self.projColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setMainPanePos(self, panePos):
|
||||
self.mainPanePos = panePos
|
||||
self.mainPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setDocPanePos(self, panePos):
|
||||
self.docPanePos = panePos
|
||||
self.docPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setOutlinePanePos(self, panePos):
|
||||
self.outlnPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setShowRefPanel(self, checkState):
|
||||
self.showRefPanel = checkState
|
||||
self.confChanged = True
|
||||
@@ -716,6 +745,40 @@ class Config:
|
||||
self.errData = []
|
||||
return errMessage
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
def getWinSize(self):
|
||||
return [int(x*self.guiScale) for x in self.winGeometry]
|
||||
|
||||
def getTreeColWidths(self):
|
||||
return [int(x*self.guiScale) for x in self.treeColWidth]
|
||||
|
||||
def getProjColWidths(self):
|
||||
return [int(x*self.guiScale) for x in self.projColWidth]
|
||||
|
||||
def getMainPanePos(self):
|
||||
return [int(x*self.guiScale) for x in self.mainPanePos]
|
||||
|
||||
def getDocPanePos(self):
|
||||
return [int(x*self.guiScale) for x in self.docPanePos]
|
||||
|
||||
def getOutlinePanePos(self):
|
||||
return [int(x*self.guiScale) for x in self.outlnPanePos]
|
||||
|
||||
def getTextWidth(self):
|
||||
return self.pxInt(self.textWidth)
|
||||
|
||||
def getTextMargin(self):
|
||||
return self.pxInt(self.textMargin)
|
||||
|
||||
def getTabWidth(self):
|
||||
return self.pxInt(self.tabWidth)
|
||||
|
||||
def getZenWidth(self):
|
||||
return self.pxInt(self.zenWidth)
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Project Item Class
|
||||
|
||||
novelWriter – Project Item Class
|
||||
==================================
|
||||
Class holding the data of a project tree item
|
||||
|
||||
File History:
|
||||
Created: 2018-10-27 [0.0.1]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2020, 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/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import nw
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from nw.common import checkInt
|
||||
from nw.constants import nwItemType, nwItemClass, nwItemLayout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NWItem():
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.theProject = theProject
|
||||
|
||||
self.itemName = ""
|
||||
self.itemHandle = None
|
||||
self.parHandle = None
|
||||
self.itemOrder = None
|
||||
self.itemType = nwItemType.NO_TYPE
|
||||
self.itemClass = nwItemClass.NO_CLASS
|
||||
self.itemLayout = nwItemLayout.NO_LAYOUT
|
||||
self.itemStatus = None
|
||||
self.isExpanded = False
|
||||
self.isExported = True
|
||||
|
||||
# Document Meta Data
|
||||
self.charCount = 0
|
||||
self.wordCount = 0
|
||||
self.paraCount = 0
|
||||
self.cursorPos = 0
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# XML Pack/Unpack
|
||||
##
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Packs all the data in the class instance into an XML object.
|
||||
"""
|
||||
xPack = etree.SubElement(xParent,"item",attrib={
|
||||
"handle" : str(self.itemHandle),
|
||||
"order" : str(self.itemOrder),
|
||||
"parent" : str(self.parHandle),
|
||||
})
|
||||
xSub = self._subPack(xPack,"name", text=str(self.itemName))
|
||||
xSub = self._subPack(xPack,"type", text=str(self.itemType.name))
|
||||
xSub = self._subPack(xPack,"class", text=str(self.itemClass.name))
|
||||
xSub = self._subPack(xPack,"status", text=str(self.itemStatus))
|
||||
if self.itemType == nwItemType.FILE:
|
||||
xSub = self._subPack(xPack,"exported", text=str(self.isExported))
|
||||
xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name))
|
||||
xSub = self._subPack(xPack,"charCount", text=str(self.charCount), none=False)
|
||||
xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False)
|
||||
xSub = self._subPack(xPack,"paraCount", text=str(self.paraCount), none=False)
|
||||
xSub = self._subPack(xPack,"cursorPos", text=str(self.cursorPos), none=False)
|
||||
else:
|
||||
xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded))
|
||||
return
|
||||
|
||||
def unpackXML(self, xItem):
|
||||
"""Sets the values from an XML entry of type 'item'.
|
||||
"""
|
||||
if xItem.tag != "item":
|
||||
logger.error("XML entry is not an NWItem")
|
||||
return False
|
||||
|
||||
if "handle" in xItem.attrib:
|
||||
self.itemHandle = xItem.attrib["handle"]
|
||||
else:
|
||||
logger.error("XML item entry does not have a handle")
|
||||
return False
|
||||
|
||||
if "parent" in xItem.attrib:
|
||||
self.parHandle = xItem.attrib["parent"]
|
||||
|
||||
setMap = {
|
||||
"name" : self.setName,
|
||||
"order" : self.setOrder,
|
||||
"type" : self.setType,
|
||||
"class" : self.setClass,
|
||||
"layout" : self.setLayout,
|
||||
"status" : self.setStatus,
|
||||
"expanded" : self.setExpanded,
|
||||
"exported" : self.setExported,
|
||||
"charCount" : self.setCharCount,
|
||||
"wordCount" : self.setWordCount,
|
||||
"paraCount" : self.setParaCount,
|
||||
"cursorPos" : self.setCursorPos,
|
||||
}
|
||||
for xValue in xItem:
|
||||
if xValue.tag in setMap:
|
||||
setMap[xValue.tag](xValue.text)
|
||||
else:
|
||||
logger.error("Unknown tag '%s'" % xValue.tag)
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _subPack(xParent, name, attrib=None, text=None, none=True):
|
||||
"""Packs the values into an xml element.
|
||||
"""
|
||||
if not none and (text == None or text == "None"):
|
||||
return None
|
||||
xSub = etree.SubElement(xParent, name, attrib=attrib)
|
||||
if text is not None:
|
||||
xSub.text = text
|
||||
return xSub
|
||||
|
||||
##
|
||||
# Set Item Values
|
||||
##
|
||||
|
||||
def setName(self, theName):
|
||||
"""Set the item name.
|
||||
"""
|
||||
self.itemName = theName.strip()
|
||||
return
|
||||
|
||||
def setHandle(self, theHandle):
|
||||
"""Set the item handle, and ensure it is valid.
|
||||
"""
|
||||
if isinstance(theHandle, str):
|
||||
if len(theHandle) == 13:
|
||||
self.itemHandle = theHandle
|
||||
else:
|
||||
self.itemHandle = None
|
||||
else:
|
||||
self.itemHandle = None
|
||||
return
|
||||
|
||||
def setParent(self, theParent):
|
||||
"""Set the parent handle, and ensure that it is valid.
|
||||
"""
|
||||
if theParent is None:
|
||||
self.parHandle = None
|
||||
elif isinstance(theParent, str):
|
||||
if len(theParent) == 13:
|
||||
self.parHandle = theParent
|
||||
else:
|
||||
self.parHandle = None
|
||||
else:
|
||||
self.parHandle = None
|
||||
return
|
||||
|
||||
def setOrder(self, theOrder):
|
||||
"""Set the item order, and ensure that it is valid. This value
|
||||
is purely a meta value, not actually used by novelWriter.
|
||||
"""
|
||||
self.itemOrder = checkInt(theOrder, 0)
|
||||
return
|
||||
|
||||
def setType(self, theType):
|
||||
"""Set the item type from either a proper nwItemType, or set it
|
||||
from a string representing a nwItemType.
|
||||
"""
|
||||
if isinstance(theType, nwItemType):
|
||||
self.itemType = theType
|
||||
elif theType in nwItemType.__members__:
|
||||
self.itemType = nwItemType[theType]
|
||||
else:
|
||||
logger.error("Unrecognised item type '%s'" % theType)
|
||||
self.itemType = nwItemType.NO_TYPE
|
||||
return
|
||||
|
||||
def setClass(self, theClass):
|
||||
"""Set the item class from either a proper nwItemClass, or set
|
||||
it from a string representing a nwItemClass.
|
||||
"""
|
||||
if isinstance(theClass, nwItemClass):
|
||||
self.itemClass = theClass
|
||||
elif theClass in nwItemClass.__members__:
|
||||
self.itemClass = nwItemClass[theClass]
|
||||
else:
|
||||
logger.error("Unrecognised item class '%s'" % theClass)
|
||||
self.itemClass = nwItemClass.NO_CLASS
|
||||
return
|
||||
|
||||
def setLayout(self, theLayout):
|
||||
"""Set the item layout from either a proper nwItemLayout, or set
|
||||
it from a string representing a nwItemLayout.
|
||||
"""
|
||||
if isinstance(theLayout, nwItemLayout):
|
||||
self.itemLayout = theLayout
|
||||
elif theLayout in nwItemLayout.__members__:
|
||||
self.itemLayout = nwItemLayout[theLayout]
|
||||
else:
|
||||
logger.error("Unrecognised item layout '%s'" % theLayout)
|
||||
self.itemLayout = nwItemLayout.NO_LAYOUT
|
||||
return
|
||||
|
||||
def setStatus(self, theStatus):
|
||||
"""Set the item status by looking it up in the valid status
|
||||
items of the current project.
|
||||
"""
|
||||
if self.itemClass == nwItemClass.NOVEL:
|
||||
self.itemStatus = self.theProject.statusItems.checkEntry(theStatus)
|
||||
else:
|
||||
self.itemStatus = self.theProject.importItems.checkEntry(theStatus)
|
||||
return
|
||||
|
||||
def setExpanded(self, expState):
|
||||
"""Save the expanded status of an item in the project tree.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self.isExpanded = expState == str(True)
|
||||
else:
|
||||
self.isExpanded = expState == True
|
||||
return
|
||||
|
||||
def setExported(self, expState):
|
||||
"""Save the export flag.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self.isExported = expState == str(True)
|
||||
else:
|
||||
self.isExported = expState == True
|
||||
return
|
||||
|
||||
##
|
||||
# Set Document Meta Data
|
||||
##
|
||||
|
||||
def setCharCount(self, theCount):
|
||||
"""Set the character count, and ensure that it is an integer.
|
||||
"""
|
||||
self.charCount = checkInt(theCount, 0)
|
||||
return
|
||||
|
||||
def setWordCount(self, theCount):
|
||||
"""Set the word count, and ensure that it is an integer.
|
||||
"""
|
||||
self.wordCount = checkInt(theCount, 0)
|
||||
return
|
||||
|
||||
def setParaCount(self, theCount):
|
||||
"""Set the paragraph count, and ensure that it is an integer.
|
||||
"""
|
||||
self.paraCount = checkInt(theCount, 0)
|
||||
return
|
||||
|
||||
def setCursorPos(self, thePosition):
|
||||
"""Set the cursor position, and ensure that it is an integer.
|
||||
"""
|
||||
self.cursorPos = checkInt(thePosition, 0)
|
||||
return
|
||||
|
||||
# END Class NWItem
|
||||
@@ -0,0 +1,251 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Project Options Cache
|
||||
|
||||
novelWriter – Project Options Cache
|
||||
=====================================
|
||||
Class wrapping the project options state
|
||||
|
||||
File History:
|
||||
Created: 2019-10-21 [0.3.1]
|
||||
Rewritten: 2020-02-19 [0.4.5]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2020, 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/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import json
|
||||
import nw
|
||||
|
||||
from os import path
|
||||
|
||||
from nw.constants import nwFiles
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class OptionState():
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theProject = theProject
|
||||
|
||||
self.theState = {}
|
||||
self.validMap = {
|
||||
"GuiSession": set([
|
||||
"widthCol0",
|
||||
"widthCol1",
|
||||
"widthCol2",
|
||||
"sortCol",
|
||||
"sortOrder",
|
||||
"hideZeros",
|
||||
"hideNegative",
|
||||
]),
|
||||
"GuiDocSplit": set([
|
||||
"spLevel",
|
||||
]),
|
||||
"GuiBuildNovel": set([
|
||||
"winWidth",
|
||||
"winHeight",
|
||||
"addNovel",
|
||||
"addNotes",
|
||||
"ignoreFlag",
|
||||
"justifyText",
|
||||
"excludeBody",
|
||||
"textFont",
|
||||
"textSize",
|
||||
"noStyling",
|
||||
"incSynopsis",
|
||||
"incComments",
|
||||
"incKeywords",
|
||||
"incBodyText",
|
||||
]),
|
||||
"GuiOutline": set([
|
||||
"headerOrder",
|
||||
"columnWidth",
|
||||
"columnHidden",
|
||||
])
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Load and Save Cache
|
||||
##
|
||||
|
||||
def loadSettings(self):
|
||||
"""Load the options dictionary from the project settings file.
|
||||
"""
|
||||
if self.theProject.projMeta is None:
|
||||
return False
|
||||
|
||||
stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
|
||||
theState = {}
|
||||
|
||||
if path.isfile(stateFile):
|
||||
logger.debug("Loading GUI options file")
|
||||
try:
|
||||
with open(stateFile, mode="r", encoding="utf8") as inFile:
|
||||
theJson = inFile.read()
|
||||
theState = json.loads(theJson)
|
||||
except Exception as e:
|
||||
logger.error("Failed to load GUI options file")
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
# Filter out unused variables
|
||||
for aGroup in theState:
|
||||
if aGroup in self.validMap:
|
||||
self.theState[aGroup] = {}
|
||||
for anOpt in theState[aGroup]:
|
||||
if anOpt in self.validMap[aGroup]:
|
||||
self.theState[aGroup][anOpt] = theState[aGroup][anOpt]
|
||||
|
||||
return True
|
||||
|
||||
def saveSettings(self):
|
||||
"""Save the options dictionary to the project settings file.
|
||||
"""
|
||||
if self.theProject.projMeta is None:
|
||||
return False
|
||||
|
||||
stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE)
|
||||
logger.debug("Saving GUI options file")
|
||||
|
||||
try:
|
||||
with open(stateFile, mode="w+", encoding="utf8") as outFile:
|
||||
outFile.write(json.dumps(self.theState, indent=2))
|
||||
except Exception as e:
|
||||
logger.error("Failed to save GUI options file")
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setValue(self, setGroup, setName, setValue):
|
||||
"""Saves a value, with a given group and name.
|
||||
"""
|
||||
if not setGroup in self.validMap:
|
||||
logger.error("Unknown option group '%s'" % setGroup)
|
||||
return False
|
||||
|
||||
if not setName in self.validMap[setGroup]:
|
||||
logger.error("Unknown option name '%s'" % setName)
|
||||
return False
|
||||
|
||||
if not setGroup in self.theState:
|
||||
self.theState[setGroup] = {}
|
||||
|
||||
self.theState[setGroup][setName] = setValue
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
def getValue(self, getGroup, getName, defaultValue):
|
||||
"""Return an arbitrary type value, if it exists. Otherwise,
|
||||
return the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return self.theState[getGroup][getName]
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return defaultValue
|
||||
|
||||
def getString(self, getGroup, getName, defaultValue):
|
||||
"""Return the value as a string, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return str(self.theState[getGroup][getName])
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return defaultValue
|
||||
|
||||
def getInt(self, getGroup, getName, defaultValue):
|
||||
"""Return the value as an int, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return int(self.theState[getGroup][getName])
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return defaultValue
|
||||
|
||||
def getFloat(self, getGroup, getName, defaultValue):
|
||||
"""Return the value as a float, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return float(self.theState[getGroup][getName])
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return defaultValue
|
||||
|
||||
def getBool(self, getGroup, getName, defaultValue):
|
||||
"""Return the value as a bool, if it exists. Otherwise, return
|
||||
the default value.
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return bool(self.theState[getGroup][getName])
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return defaultValue
|
||||
|
||||
##
|
||||
# Validators
|
||||
##
|
||||
|
||||
def validIntRange(self, theValue, intA, intB, intDefault):
|
||||
"""Check that an int is in a given range. If it isn't, return
|
||||
the default value.
|
||||
"""
|
||||
if isinstance(theValue, int):
|
||||
if theValue >= intA and theValue <= intB:
|
||||
return theValue
|
||||
return intDefault
|
||||
|
||||
def validIntTuple(self, theValue, theTuple, intDefault):
|
||||
"""Check that an int is an element of a tuple. If it isn't,
|
||||
return the default value.
|
||||
"""
|
||||
if isinstance(theValue, int):
|
||||
if theValue in theTuple:
|
||||
return theValue
|
||||
return intDefault
|
||||
|
||||
# END Class OptionState
|
||||
+31
-967
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,194 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Project Item Status Class
|
||||
|
||||
novelWriter – Project Item Status Class
|
||||
=========================================
|
||||
Class holding the status elements of a project item
|
||||
|
||||
File History:
|
||||
Created: 2019-05-19 [0.1.3]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2020, 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/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import nw
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from nw.common import checkInt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NWStatus():
|
||||
|
||||
def __init__(self):
|
||||
self.theLabels = []
|
||||
self.theColours = []
|
||||
self.theCounts = []
|
||||
self.theMap = {}
|
||||
self.theLength = 0
|
||||
self.theIndex = 0
|
||||
return
|
||||
|
||||
def addEntry(self, theLabel, theColours):
|
||||
"""Add a status entry to the status object, but ensure it isn't
|
||||
a duplicate.
|
||||
"""
|
||||
theLabel = theLabel.strip()
|
||||
if self.lookupEntry(theLabel) is None:
|
||||
self.theLabels.append(theLabel)
|
||||
self.theColours.append(theColours)
|
||||
self.theCounts.append(0)
|
||||
self.theMap[theLabel] = self.theLength
|
||||
self.theLength += 1
|
||||
return True
|
||||
|
||||
def lookupEntry(self, theLabel):
|
||||
"""Look up a status entry in the object lists, and return it if
|
||||
it exists.
|
||||
"""
|
||||
if theLabel is None:
|
||||
return None
|
||||
theLabel = theLabel.strip()
|
||||
if theLabel in self.theMap.keys():
|
||||
return self.theMap[theLabel]
|
||||
return None
|
||||
|
||||
def checkEntry(self, theStatus):
|
||||
"""Check if a status value is valid, and returns the safe
|
||||
reference to be used internally.
|
||||
"""
|
||||
if isinstance(theStatus, str):
|
||||
theStatus = theStatus.strip()
|
||||
if self.lookupEntry(theStatus) is not None:
|
||||
return theStatus
|
||||
theStatus = checkInt(theStatus, 0, False)
|
||||
if theStatus >= 0 and theStatus < self.theLength:
|
||||
return self.theLabels[theStatus]
|
||||
|
||||
def setNewEntries(self, newList):
|
||||
"""Update the list of entries after they have been modified by
|
||||
the GUI tool.
|
||||
"""
|
||||
replaceMap = {}
|
||||
|
||||
if newList is not None:
|
||||
self.theLabels = []
|
||||
self.theColours = []
|
||||
self.theCounts = []
|
||||
self.theMap = {}
|
||||
self.theLength = 0
|
||||
self.theIndex = 0
|
||||
|
||||
for nName, nR, nG, nB, oName in newList:
|
||||
self.addEntry(nName, (nR, nG, nB))
|
||||
if nName != oName and oName is not None:
|
||||
replaceMap[oName] = nName
|
||||
|
||||
return replaceMap
|
||||
|
||||
def resetCounts(self):
|
||||
"""Clear the counts of references to the status entries.
|
||||
"""
|
||||
self.theCounts = [0]*self.theLength
|
||||
return
|
||||
|
||||
def countEntry(self, theLabel):
|
||||
"""Lookup the usage count of a given entry.
|
||||
"""
|
||||
theIndex = self.lookupEntry(theLabel)
|
||||
if theIndex is not None:
|
||||
self.theCounts[theIndex] += 1
|
||||
return
|
||||
|
||||
def packEntries(self, xParent):
|
||||
"""Pack the status entries into an XML object for saving to the
|
||||
main project file.
|
||||
"""
|
||||
for n in range(self.theLength):
|
||||
xSub = etree.SubElement(xParent,"entry",attrib={
|
||||
"blue" : str(self.theColours[n][2]),
|
||||
"green" : str(self.theColours[n][1]),
|
||||
"red" : str(self.theColours[n][0]),
|
||||
})
|
||||
xSub.text = self.theLabels[n]
|
||||
return True
|
||||
|
||||
def unpackEntries(self, xParent):
|
||||
"""Unpack an XML tree and set the class values.
|
||||
"""
|
||||
theLabels = []
|
||||
theColours = []
|
||||
|
||||
for xChild in xParent:
|
||||
theLabels.append(xChild.text)
|
||||
if "red" in xChild.attrib:
|
||||
cR = checkInt(xChild.attrib["red"],0,False)
|
||||
else:
|
||||
cR = 0
|
||||
if "green" in xChild.attrib:
|
||||
cG = checkInt(xChild.attrib["green"],0,False)
|
||||
else:
|
||||
cG = 0
|
||||
if "blue" in xChild.attrib:
|
||||
cB = checkInt(xChild.attrib["blue"],0,False)
|
||||
else:
|
||||
cB = 0
|
||||
theColours.append((cR,cG,cB))
|
||||
|
||||
if len(theLabels) > 0:
|
||||
self.theLabels = []
|
||||
self.theColours = []
|
||||
self.theCounts = []
|
||||
self.theMap = {}
|
||||
self.theLength = 0
|
||||
self.theIndex = 0
|
||||
|
||||
for n in range(len(theLabels)):
|
||||
self.addEntry(theLabels[n], theColours[n])
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Iterator Bits
|
||||
##
|
||||
|
||||
def __getitem__(self, n):
|
||||
"""Return an entry by its index.
|
||||
"""
|
||||
if n >= 0 and n < self.theLength:
|
||||
return self.theLabels[n], self.theColours[n], self.theCounts[n]
|
||||
return None, None, None
|
||||
|
||||
def __iter__(self):
|
||||
"""Initialise the iterator.
|
||||
"""
|
||||
self.theIndex = 0
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
"""Return the next entry for the iterator.
|
||||
"""
|
||||
if self.theIndex < self.theLength:
|
||||
theLabel, theColour, theCount = self.__getitem__(self.theIndex)
|
||||
self.theIndex += 1
|
||||
return theLabel, theColour, theCount
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
# END Class NWStatus
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter Project Tree Class
|
||||
|
||||
novelWriter – Project Tree Class
|
||||
==================================
|
||||
Class holding the data of the project tree
|
||||
|
||||
File History:
|
||||
Created: 2020-05-07 [0.4.5]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2020, 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/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import json
|
||||
import nw
|
||||
|
||||
from os import path
|
||||
from lxml import etree
|
||||
from hashlib import sha256
|
||||
from time import time
|
||||
|
||||
from nw.core.item import NWItem
|
||||
from nw.common import checkString
|
||||
from nw.constants import nwFiles, nwItemType, nwItemClass
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class NWTree():
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.theProject = theProject
|
||||
|
||||
self._projTree = {} # Holds all the items of the project
|
||||
self._treeOrder = [] # The order of the tree items on the tree view
|
||||
self._treeRoots = [] # The root items of the tree
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
self._theLength = 0 # Always the length of _treeOrder
|
||||
self._theIndex = 0 # The current iterator index
|
||||
self._treeChanged = False # True if tree structure has changed
|
||||
self._handleSeed = None # Used for generating handles for testing
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def clear(self):
|
||||
"""Clear the item tree entirely.
|
||||
"""
|
||||
self._projTree = {}
|
||||
self._treeOrder = []
|
||||
self._treeRoots = []
|
||||
self._trashRoot = None
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
self._treeChanged = False
|
||||
return
|
||||
|
||||
def handles(self):
|
||||
"""Returns a copy of the list of all the active handles.
|
||||
"""
|
||||
return self._treeOrder.copy()
|
||||
|
||||
def append(self, tHandle, pHandle, nwItem):
|
||||
"""Add a new item to the end of the tree.
|
||||
"""
|
||||
tHandle = checkString(tHandle, None, True)
|
||||
pHandle = checkString(pHandle, None, True)
|
||||
if tHandle is None:
|
||||
tHandle = self._makeHandle()
|
||||
|
||||
logger.verbose("Adding entry %s with parent %s" % (str(tHandle), str(pHandle)))
|
||||
|
||||
nwItem.setHandle(tHandle)
|
||||
nwItem.setParent(pHandle)
|
||||
|
||||
self._projTree[tHandle] = nwItem
|
||||
self._treeOrder.append(tHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.ROOT:
|
||||
logger.verbose("Entry %s is a root item" % str(tHandle))
|
||||
self._treeRoots.append(tHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.TRASH:
|
||||
if self._trashRoot is None:
|
||||
logger.verbose("Entry %s is the trash folder" % str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Pack the content of the tree into an XML object.
|
||||
"""
|
||||
xContent = etree.SubElement(xParent, "content", attrib={
|
||||
"count":str(self._theLength)}
|
||||
)
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
tItem.packXML(xContent)
|
||||
return
|
||||
|
||||
def unpackXML(self, xContent):
|
||||
"""Iterate through all items of a content XML object and add
|
||||
them to the project tree.
|
||||
"""
|
||||
if xContent.tag != "content":
|
||||
logger.error("XML entry is not a NWTree")
|
||||
return False
|
||||
|
||||
self.clear()
|
||||
for xItem in xContent:
|
||||
nwItem = NWItem(self.theProject)
|
||||
if nwItem.unpackXML(xItem):
|
||||
self.append(nwItem.itemHandle, nwItem.parHandle, nwItem)
|
||||
|
||||
return True
|
||||
|
||||
def writeToCFiles(self):
|
||||
"""Write the convenience table of contents files in the root of
|
||||
the project directory. These files are there to assist the user
|
||||
if they wish to browse the stored files.
|
||||
"""
|
||||
tocText = path.join(self.theProject.projPath, nwFiles.TOC_TXT)
|
||||
tocJson = path.join(self.theProject.projPath, nwFiles.TOC_JSON)
|
||||
|
||||
jsonData = []
|
||||
try:
|
||||
# Dump the text
|
||||
with open(tocText, mode="w", encoding="utf8") as outFile:
|
||||
outFile.write("\n")
|
||||
outFile.write(" Table of Contents\n")
|
||||
outFile.write("===================\n")
|
||||
outFile.write("\n")
|
||||
outFile.write(" %-25s %-9s %s\n" %("File Name","Class","Document Label"))
|
||||
outFile.write("-"*80+"\n")
|
||||
for tHandle in sorted(self._treeOrder):
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
tFile = tHandle+".nwd"
|
||||
if path.isfile(path.join(self.theProject.projContent, tFile)):
|
||||
outFile.write(" %-25s %-9s %s\n" %(
|
||||
path.join("content", tFile),
|
||||
tItem.itemClass.name,
|
||||
tItem.itemName,
|
||||
))
|
||||
jsonData.append([
|
||||
path.join("content", tFile),
|
||||
tItem.itemClass.name,
|
||||
tItem.itemName,
|
||||
])
|
||||
outFile.write("\n")
|
||||
|
||||
# Dump the JSON
|
||||
with open(tocJson, mode="w+", encoding="utf8") as outFile:
|
||||
outFile.write(json.dumps(jsonData, indent=2))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Tree Structure Methods
|
||||
##
|
||||
|
||||
def trashRoot(self):
|
||||
"""Returns the handle of the trash folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._trashRoot:
|
||||
return self._trashRoot
|
||||
return None
|
||||
|
||||
def findRoot(self, theClass):
|
||||
"""Find the root item for a given class.
|
||||
Note: This returns the first item for class CUSTOM.
|
||||
"""
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return tItem.itemHandle
|
||||
return None
|
||||
|
||||
def checkRootUnique(self, theClass):
|
||||
"""Checks if there already is a root entry of class 'theClass'
|
||||
in the root of the project tree. CUSTOM class is skipped as it
|
||||
is not required to be unique.
|
||||
"""
|
||||
if theClass == nwItemClass.CUSTOM:
|
||||
return True
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if theClass == tItem.itemClass:
|
||||
return False
|
||||
return True
|
||||
|
||||
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):
|
||||
if tItem.parHandle is None:
|
||||
return tHandle
|
||||
else:
|
||||
tHandle = tItem.parHandle
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return tHandle
|
||||
return None
|
||||
|
||||
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
|
||||
infinite loops impossible.
|
||||
"""
|
||||
tTree = []
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
tTree.append(tHandle)
|
||||
for i in range(200):
|
||||
if tItem.parHandle is None:
|
||||
return tTree
|
||||
else:
|
||||
tHandle = tItem.parHandle
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return tTree
|
||||
else:
|
||||
tTree.append(tHandle)
|
||||
return tTree
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setOrder(self, newOrder):
|
||||
"""Reorders the tree based on a list of items.
|
||||
"""
|
||||
tmpOrder = []
|
||||
|
||||
# Add all known elements to a new temp list
|
||||
for tHandle in newOrder:
|
||||
if tHandle in self._projTree:
|
||||
tmpOrder.append(tHandle)
|
||||
else:
|
||||
logger.error("Handle %s in new tree order is not in project tree" % tHandle)
|
||||
|
||||
# Do a reverse lookup to check for items that will be lost
|
||||
# This is mainly for debugging purposes
|
||||
for tHandle in self._treeOrder:
|
||||
if tHandle not in tmpOrder:
|
||||
logger.warning("Handle %s in old tree order is not in new tree order" % tHandle)
|
||||
|
||||
# Save the temp list
|
||||
self._treeOrder = tmpOrder
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
logger.verbose("Project tree order updated")
|
||||
|
||||
return
|
||||
|
||||
def setSeed(self, theSeed):
|
||||
"""Used for debugging!
|
||||
Sets a seed for generating handles so that they always come out
|
||||
in a predictable order.
|
||||
"""
|
||||
self._handleSeed = theSeed
|
||||
return
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
def countTypes(self):
|
||||
"""Count the number of files, folders and roots in the project.
|
||||
"""
|
||||
nRoot = 0
|
||||
nFolder = 0
|
||||
nFile = 0
|
||||
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
elif tItem.itemType == nwItemType.ROOT:
|
||||
nRoot += 1
|
||||
elif tItem.itemType == nwItemType.FOLDER:
|
||||
nFolder += 1
|
||||
elif tItem.itemType == nwItemType.FILE:
|
||||
nFile += 1
|
||||
|
||||
return nRoot, nFolder, nFile
|
||||
|
||||
##
|
||||
# Meta Methods
|
||||
##
|
||||
|
||||
def __len__(self):
|
||||
"""Return the length counter. Does not check that it is correct!
|
||||
"""
|
||||
return self._theLength
|
||||
|
||||
def __bool__(self):
|
||||
"""Returns True if the tree has any entries.
|
||||
"""
|
||||
return self._theLength > 0
|
||||
|
||||
##
|
||||
# Item Access Methods
|
||||
##
|
||||
|
||||
def __getitem__(self, tHandle):
|
||||
"""Return a project item based on its handle. Returns None if
|
||||
the handle doesn't exist in the project.
|
||||
"""
|
||||
if tHandle in self._projTree:
|
||||
return self._projTree[tHandle]
|
||||
logger.error("No tree item with handle %s" % str(tHandle))
|
||||
return None
|
||||
|
||||
def __delitem__(self, tHandle):
|
||||
"""This only removes the item from the order list, but not from
|
||||
the project tree.
|
||||
"""
|
||||
if tHandle not in self._treeOrder:
|
||||
logger.warning(
|
||||
"Could not remove item %s from project tree as it does not exist" % tHandle
|
||||
)
|
||||
return False
|
||||
self._treeOrder.remove(tHandle)
|
||||
self._theLength = len(self._treeOrder)
|
||||
self._setTreeChanged(True)
|
||||
return True
|
||||
|
||||
def __contains__(self, tHandle):
|
||||
"""Checks if a handle exists in the tree.
|
||||
"""
|
||||
return tHandle in self._treeOrder
|
||||
|
||||
##
|
||||
# Iterator Methods
|
||||
##
|
||||
|
||||
def __iter__(self):
|
||||
"""Initiates the iterator.
|
||||
"""
|
||||
self._theIndex = 0
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
"""Returns the item from the next entry in the _treeOrder list.
|
||||
"""
|
||||
if self._theIndex < self._theLength:
|
||||
theItem = self.__getitem__(self._treeOrder[self._theIndex])
|
||||
self._theIndex += 1
|
||||
return theItem
|
||||
else:
|
||||
raise StopIteration
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _setTreeChanged(self, theState):
|
||||
"""Set the changed flag to theState, and if being set to True,
|
||||
propagate that state change to the parent NWProject class.
|
||||
"""
|
||||
self._treeChanged = theState
|
||||
if theState:
|
||||
self.theProject.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def _makeHandle(self, addSeed=""):
|
||||
"""Generate a unique item handle. In the unlikely event that the
|
||||
key already exists, salt the seed and generate a new handle.
|
||||
"""
|
||||
if self._handleSeed is None:
|
||||
newSeed = str(time()) + addSeed
|
||||
else:
|
||||
# This is used for debugging
|
||||
newSeed = str(self._handleSeed)
|
||||
self._handleSeed += 1
|
||||
logger.verbose("Generating handle with seed '%s'" % newSeed)
|
||||
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
|
||||
if itemHandle in self._projTree:
|
||||
logger.warning("Duplicate handle encountered! Retrying ...")
|
||||
itemHandle = self._makeHandle(addSeed+"!")
|
||||
return itemHandle
|
||||
|
||||
# END Class NWTree
|
||||
+6
-7
@@ -2,9 +2,7 @@
|
||||
|
||||
from nw.gui.about import GuiAbout
|
||||
from nw.gui.build import GuiBuildNovel
|
||||
from nw.gui.docbars import GuiDocTitleBar
|
||||
from nw.gui.docbars import GuiNoticeBar
|
||||
from nw.gui.docbars import GuiSearchBar
|
||||
from nw.gui.docbars import GuiDocTitleBar, GuiNoticeBar, GuiSearchBar
|
||||
from nw.gui.docdetails import GuiDocViewDetails
|
||||
from nw.gui.doceditor import GuiDocEditor
|
||||
from nw.gui.docmerge import GuiDocMerge
|
||||
@@ -13,15 +11,15 @@ from nw.gui.docviewer import GuiDocViewer
|
||||
from nw.gui.itemdetails import GuiItemDetails
|
||||
from nw.gui.itemeditor import GuiItemEditor
|
||||
from nw.gui.mainmenu import GuiMainMenu
|
||||
from nw.gui.outline import GuiProjectOutline
|
||||
from nw.gui.outline import GuiOutline
|
||||
from nw.gui.outlinedetails import GuiOutlineDetails
|
||||
from nw.gui.preferences import GuiPreferences
|
||||
from nw.gui.projload import GuiProjectLoad
|
||||
from nw.gui.projsettings import GuiProjectSettings
|
||||
from nw.gui.projtree import GuiProjectTree
|
||||
from nw.gui.sessionlog import GuiSessionLogView
|
||||
from nw.gui.statusbar import GuiMainStatus
|
||||
from nw.gui.theme import GuiIcons
|
||||
from nw.gui.theme import GuiTheme
|
||||
from nw.gui.theme import GuiIcons, GuiTheme
|
||||
|
||||
__all__ = [
|
||||
"GuiAbout",
|
||||
@@ -37,7 +35,8 @@ __all__ = [
|
||||
"GuiItemDetails",
|
||||
"GuiItemEditor",
|
||||
"GuiMainMenu",
|
||||
"GuiProjectOutline",
|
||||
"GuiOutline",
|
||||
"GuiOutlineDetails",
|
||||
"GuiPreferences",
|
||||
"GuiProjectLoad",
|
||||
"GuiProjectSettings",
|
||||
|
||||
+10
-13
@@ -48,22 +48,24 @@ class GuiAbout(QDialog):
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.innerBox = QHBoxLayout()
|
||||
self.innerBox.setSpacing(16)
|
||||
self.innerBox.setSpacing(self.mainConf.pxInt(16))
|
||||
|
||||
self.setWindowTitle("About %s" % nw.__package__)
|
||||
self.setMinimumWidth(700)
|
||||
self.setMinimumHeight(600)
|
||||
self.setMinimumWidth(self.mainConf.pxInt(650))
|
||||
self.setMinimumHeight(self.mainConf.pxInt(600))
|
||||
|
||||
self.guiDeco = self.theParent.theTheme.loadDecoration("nwicon", (96, 96))
|
||||
iPx = self.mainConf.pxInt(96)
|
||||
self.guiDeco = self.theParent.theTheme.loadDecoration("nwicon", (iPx, iPx))
|
||||
self.lblName = QLabel("<b>%s</b>" % nw.__package__)
|
||||
self.lblVers = QLabel("v%s" % nw.__version__)
|
||||
self.lblDate = QLabel(datetime.strptime(nw.__date__, "%Y-%m-%d").strftime("%x"))
|
||||
|
||||
self.leftBox = QVBoxLayout()
|
||||
self.leftBox.setSpacing(4)
|
||||
self.leftBox = QVBoxLayout()
|
||||
self.leftBox.setSpacing(self.mainConf.pxInt(4))
|
||||
self.leftBox.addWidget(self.guiDeco, 0, Qt.AlignCenter)
|
||||
self.leftBox.addWidget(self.lblName, 0, Qt.AlignCenter)
|
||||
self.leftBox.addWidget(self.lblVers, 0, Qt.AlignCenter)
|
||||
@@ -74,20 +76,15 @@ class GuiAbout(QDialog):
|
||||
# Pages
|
||||
self.pageAbout = QTextBrowser()
|
||||
self.pageAbout.setOpenExternalLinks(True)
|
||||
self.pageAbout.document().setDocumentMargin(16)
|
||||
|
||||
# self.pageCredit = QTextBrowser()
|
||||
# self.pageCredit.setOpenExternalLinks(True)
|
||||
# self.pageCredit.document().setDocumentMargin(16)
|
||||
self.pageAbout.document().setDocumentMargin(self.mainConf.pxInt(16))
|
||||
|
||||
self.pageLicense = QTextBrowser()
|
||||
self.pageLicense.setOpenExternalLinks(True)
|
||||
self.pageLicense.document().setDocumentMargin(16)
|
||||
self.pageLicense.document().setDocumentMargin(self.mainConf.pxInt(16))
|
||||
|
||||
# Main Tab Area
|
||||
self.tabBox = QTabWidget()
|
||||
self.tabBox.addTab(self.pageAbout, "About")
|
||||
# self.tabBox.addTab(self.pageCredit, "Credit")
|
||||
self.tabBox.addTab(self.pageLicense, "License")
|
||||
self.innerBox.addWidget(self.tabBox)
|
||||
|
||||
|
||||
+55
-34
@@ -78,12 +78,12 @@ class GuiBuildNovel(QDialog):
|
||||
self.nwdText = [] # List of markdown documents
|
||||
|
||||
self.setWindowTitle("Build Novel Project")
|
||||
self.setMinimumWidth(900)
|
||||
self.setMinimumHeight(800)
|
||||
self.setMinimumWidth(self.mainConf.pxInt(900))
|
||||
self.setMinimumHeight(self.mainConf.pxInt(800))
|
||||
|
||||
self.resize(
|
||||
self.optState.getInt("GuiBuildNovel", "winWidth", 900),
|
||||
self.optState.getInt("GuiBuildNovel", "winHeight", 800)
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winWidth", 900)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winHeight", 800))
|
||||
)
|
||||
|
||||
self.outerBox = QHBoxLayout()
|
||||
@@ -112,10 +112,11 @@ class GuiBuildNovel(QDialog):
|
||||
r"be centred automatically and only appear between sections of "
|
||||
r"the same type."
|
||||
)
|
||||
xFmt = self.mainConf.pxInt(220)
|
||||
|
||||
self.fmtTitle = QLineEdit()
|
||||
self.fmtTitle.setMaxLength(200)
|
||||
self.fmtTitle.setFixedWidth(220)
|
||||
self.fmtTitle.setFixedWidth(xFmt)
|
||||
self.fmtTitle.setToolTip(fmtHelp)
|
||||
self.fmtTitle.setText(
|
||||
self._reFmtCodes(self.theProject.titleFormat["title"])
|
||||
@@ -123,7 +124,7 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.fmtChapter = QLineEdit()
|
||||
self.fmtChapter.setMaxLength(200)
|
||||
self.fmtChapter.setFixedWidth(220)
|
||||
self.fmtChapter.setFixedWidth(xFmt)
|
||||
self.fmtChapter.setToolTip(fmtHelp)
|
||||
self.fmtChapter.setText(
|
||||
self._reFmtCodes(self.theProject.titleFormat["chapter"])
|
||||
@@ -131,7 +132,7 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.fmtUnnumbered = QLineEdit()
|
||||
self.fmtUnnumbered.setMaxLength(200)
|
||||
self.fmtUnnumbered.setFixedWidth(220)
|
||||
self.fmtUnnumbered.setFixedWidth(xFmt)
|
||||
self.fmtUnnumbered.setToolTip(fmtHelp)
|
||||
self.fmtUnnumbered.setText(
|
||||
self._reFmtCodes(self.theProject.titleFormat["unnumbered"])
|
||||
@@ -139,7 +140,7 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.fmtScene = QLineEdit()
|
||||
self.fmtScene.setMaxLength(200)
|
||||
self.fmtScene.setFixedWidth(220)
|
||||
self.fmtScene.setFixedWidth(xFmt)
|
||||
self.fmtScene.setToolTip(fmtHelp + fmtScHelp)
|
||||
self.fmtScene.setText(
|
||||
self._reFmtCodes(self.theProject.titleFormat["scene"])
|
||||
@@ -147,7 +148,7 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.fmtSection = QLineEdit()
|
||||
self.fmtSection.setMaxLength(200)
|
||||
self.fmtSection.setFixedWidth(220)
|
||||
self.fmtSection.setFixedWidth(xFmt)
|
||||
self.fmtSection.setToolTip(fmtHelp + fmtScHelp)
|
||||
self.fmtSection.setText(
|
||||
self._reFmtCodes(self.theProject.titleFormat["section"])
|
||||
@@ -176,7 +177,7 @@ class GuiBuildNovel(QDialog):
|
||||
## Font Family
|
||||
self.textFont = QLineEdit()
|
||||
self.textFont.setReadOnly(True)
|
||||
self.textFont.setFixedWidth(182)
|
||||
self.textFont.setFixedWidth(self.mainConf.pxInt(182))
|
||||
self.textFont.setText(
|
||||
self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
|
||||
)
|
||||
@@ -185,7 +186,7 @@ class GuiBuildNovel(QDialog):
|
||||
self.fontButton.clicked.connect(self._selectFont)
|
||||
|
||||
self.textSize = QSpinBox(self)
|
||||
self.textSize.setFixedWidth(60)
|
||||
self.textSize.setFixedWidth(5*self.theTheme.textNWidth)
|
||||
self.textSize.setMinimum(6)
|
||||
self.textSize.setMaximum(72)
|
||||
self.textSize.setSingleStep(1)
|
||||
@@ -236,26 +237,32 @@ class GuiBuildNovel(QDialog):
|
||||
self.includeSynopsis.setToolTip(
|
||||
"Include synopsis comments in the output."
|
||||
)
|
||||
self.includeSynopsis.setChecked(self.theProject.titleFormat["withSynopsis"])
|
||||
self.includeSynopsis.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incSynopsis", False)
|
||||
)
|
||||
|
||||
self.includeComments = QSwitch()
|
||||
self.includeComments.setToolTip(
|
||||
"Include plain comments in the output."
|
||||
)
|
||||
self.includeComments.setChecked(self.theProject.titleFormat["withComments"])
|
||||
self.includeComments.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incComments", False)
|
||||
)
|
||||
|
||||
self.includeKeywords = QSwitch()
|
||||
self.includeKeywords.setToolTip(
|
||||
"Include meta keywords (tags, references) in the output."
|
||||
)
|
||||
self.includeKeywords.setChecked(self.theProject.titleFormat["withKeywords"])
|
||||
self.includeKeywords.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incKeywords", False)
|
||||
)
|
||||
|
||||
self.includeBody = QSwitch()
|
||||
self.includeBody.setToolTip(
|
||||
"Include body text in the output."
|
||||
)
|
||||
self.includeBody.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "includeBody", True)
|
||||
self.optState.getBool("GuiBuildNovel", "incBodyText", True)
|
||||
)
|
||||
|
||||
self.textForm.addWidget(QLabel("Include synopsis"), 0, 0, 1, 1, Qt.AlignLeft)
|
||||
@@ -847,27 +854,41 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
# Formatting
|
||||
self.theProject.setTitleFormat({
|
||||
"title" : self.fmtTitle.text().strip(),
|
||||
"chapter" : self.fmtChapter.text().strip(),
|
||||
"unnumbered" : self.fmtUnnumbered.text().strip(),
|
||||
"scene" : self.fmtScene.text().strip(),
|
||||
"section" : self.fmtSection.text().strip(),
|
||||
"withSynopsis" : self.includeSynopsis.isChecked(),
|
||||
"withComments" : self.includeComments.isChecked(),
|
||||
"withKeywords" : self.includeKeywords.isChecked(),
|
||||
"title" : self.fmtTitle.text().strip(),
|
||||
"chapter" : self.fmtChapter.text().strip(),
|
||||
"unnumbered" : self.fmtUnnumbered.text().strip(),
|
||||
"scene" : self.fmtScene.text().strip(),
|
||||
"section" : self.fmtSection.text().strip(),
|
||||
})
|
||||
|
||||
winWidth = self.mainConf.pxInt(self.width())
|
||||
winHeight = self.mainConf.pxInt(self.height())
|
||||
justifyText = self.justifyText.isChecked()
|
||||
noStyling = self.noStyling.isChecked()
|
||||
textFont = self.textFont.text()
|
||||
textSize = self.textSize.value()
|
||||
novelFiles = self.novelFiles.isChecked()
|
||||
noteFiles = self.noteFiles.isChecked()
|
||||
ignoreFlag = self.ignoreFlag.isChecked()
|
||||
incSynopsis = self.includeSynopsis.isChecked()
|
||||
incComments = self.includeComments.isChecked()
|
||||
incKeywords = self.includeKeywords.isChecked()
|
||||
incBodyText = self.includeBody.isChecked()
|
||||
|
||||
# GUI Settings
|
||||
self.optState.setValue("GuiBuildNovel", "winWidth", self.width())
|
||||
self.optState.setValue("GuiBuildNovel", "winHeight", self.height())
|
||||
self.optState.setValue("GuiBuildNovel", "justifyText", self.justifyText.isChecked())
|
||||
self.optState.setValue("GuiBuildNovel", "noStyling", self.noStyling.isChecked())
|
||||
self.optState.setValue("GuiBuildNovel", "textFont", self.textFont.text())
|
||||
self.optState.setValue("GuiBuildNovel", "textSize", self.textSize.value())
|
||||
self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked())
|
||||
self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked())
|
||||
self.optState.setValue("GuiBuildNovel", "ignoreFlag", self.ignoreFlag.isChecked())
|
||||
self.optState.setValue("GuiBuildNovel", "includeBody", self.includeBody.isChecked())
|
||||
self.optState.setValue("GuiBuildNovel", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiBuildNovel", "winHeight", winHeight)
|
||||
self.optState.setValue("GuiBuildNovel", "justifyText", justifyText)
|
||||
self.optState.setValue("GuiBuildNovel", "noStyling", noStyling)
|
||||
self.optState.setValue("GuiBuildNovel", "textFont", textFont)
|
||||
self.optState.setValue("GuiBuildNovel", "textSize", textSize)
|
||||
self.optState.setValue("GuiBuildNovel", "addNovel", novelFiles)
|
||||
self.optState.setValue("GuiBuildNovel", "addNotes", noteFiles)
|
||||
self.optState.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
|
||||
self.optState.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
|
||||
self.optState.setValue("GuiBuildNovel", "incComments", incComments)
|
||||
self.optState.setValue("GuiBuildNovel", "incKeywords", incKeywords)
|
||||
self.optState.setValue("GuiBuildNovel", "incBodyText", incBodyText)
|
||||
self.optState.saveSettings()
|
||||
|
||||
return
|
||||
@@ -894,7 +915,7 @@ class GuiBuildNovelDocView(QTextBrowser):
|
||||
self.theProject = theProject
|
||||
self.theParent = theParent
|
||||
|
||||
self.setMinimumWidth(400)
|
||||
self.setMinimumWidth(40*self.theParent.theTheme.textNWidth)
|
||||
self.setOpenExternalLinks(False)
|
||||
|
||||
self.qDocument = self.document()
|
||||
|
||||
+22
-14
@@ -215,14 +215,22 @@ class QHelpLabel(QLabel):
|
||||
|
||||
class QSwitch(QAbstractButton):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent=None, width=40, height=20):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._xW = int(nw.CONFIG.guiScale*width)
|
||||
self._xH = int(nw.CONFIG.guiScale*height)
|
||||
self._xR = int(self._xH*0.5)
|
||||
self._xT = int(self._xH*0.6)
|
||||
self._rB = int(nw.CONFIG.guiScale*2)
|
||||
self._rH = self._xH - 2*self._rB
|
||||
self._rR = self._xR - self._rB
|
||||
|
||||
self.setCheckable(True)
|
||||
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
self.setFixedWidth(40)
|
||||
self.setFixedHeight(20)
|
||||
self._offset = 10
|
||||
self.setFixedWidth(self._xW)
|
||||
self.setFixedHeight(self._xH)
|
||||
self._offset = self._xR
|
||||
|
||||
return
|
||||
|
||||
@@ -249,9 +257,9 @@ class QSwitch(QAbstractButton):
|
||||
"""
|
||||
super().setChecked(isChecked)
|
||||
if isChecked:
|
||||
self.offset = 30
|
||||
self.offset = self._xW - self._xR
|
||||
else:
|
||||
self.offset = 10
|
||||
self.offset = self._xR
|
||||
return
|
||||
|
||||
##
|
||||
@@ -263,9 +271,9 @@ class QSwitch(QAbstractButton):
|
||||
"""
|
||||
super().resizeEvent(theEvent)
|
||||
if self.isChecked():
|
||||
self.offset = 30
|
||||
self.offset = self._xW - self._xR
|
||||
else:
|
||||
self.offset = 10
|
||||
self.offset = self._xR
|
||||
return
|
||||
|
||||
def paintEvent(self, event):
|
||||
@@ -297,17 +305,17 @@ class QSwitch(QAbstractButton):
|
||||
|
||||
qPaint.setBrush(trackBrush)
|
||||
qPaint.setOpacity(trackOpacity)
|
||||
qPaint.drawRoundedRect(0, 0, 40, 20, 10, 10)
|
||||
qPaint.drawRoundedRect(0, 0, self._xW, self._xH, self._xR, self._xR)
|
||||
|
||||
qPaint.setBrush(thumbBrush)
|
||||
qPaint.drawEllipse(self.offset - 8, 2, 16, 16)
|
||||
qPaint.drawEllipse(self.offset - self._rR, self._rB, self._rH, self._rH)
|
||||
|
||||
theFont = qPaint.font()
|
||||
theFont.setPixelSize(12)
|
||||
theFont.setPixelSize(self._xT)
|
||||
qPaint.setPen(textColor)
|
||||
qPaint.setFont(theFont)
|
||||
qPaint.drawText(
|
||||
QRectF(self.offset - 8, 2, 16, 16),
|
||||
QRectF(self.offset - self._rR, self._rB, self._rH, self._rH),
|
||||
Qt.AlignCenter, thumbText
|
||||
)
|
||||
|
||||
@@ -322,9 +330,9 @@ class QSwitch(QAbstractButton):
|
||||
doAnim.setDuration(120)
|
||||
doAnim.setStartValue(self.offset)
|
||||
if self.isChecked():
|
||||
doAnim.setEndValue(30)
|
||||
doAnim.setEndValue(self._xW - self._xR)
|
||||
else:
|
||||
doAnim.setEndValue(10)
|
||||
doAnim.setEndValue(self._xR)
|
||||
doAnim.start()
|
||||
return
|
||||
|
||||
|
||||
+26
-22
@@ -53,7 +53,7 @@ class GuiSearchBar(QFrame):
|
||||
self.theTheme = theParent.theTheme
|
||||
self.repVisible = False
|
||||
|
||||
self.setContentsMargins(0,0,0,0)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.mainBox = QGridLayout(self)
|
||||
self.setLayout(self.mainBox)
|
||||
@@ -72,24 +72,25 @@ class GuiSearchBar(QFrame):
|
||||
self.searchBox.returnPressed.connect(self._doSearch)
|
||||
self.replaceBox.returnPressed.connect(self._doSearch)
|
||||
|
||||
self.mainBox.addWidget(QLabel(""), 0,0)
|
||||
self.mainBox.addWidget(self.searchLabel, 0,1)
|
||||
self.mainBox.addWidget(self.searchBox, 0,2)
|
||||
self.mainBox.addWidget(self.searchButton, 0,3)
|
||||
self.mainBox.addWidget(self.closeButton, 0,4)
|
||||
self.mainBox.addWidget(self.replaceLabel, 1,1)
|
||||
self.mainBox.addWidget(self.replaceBox, 1,2)
|
||||
self.mainBox.addWidget(self.replaceButton, 1,3)
|
||||
self.mainBox.addWidget(QLabel(""), 0, 0)
|
||||
self.mainBox.addWidget(self.searchLabel, 0, 1)
|
||||
self.mainBox.addWidget(self.searchBox, 0, 2)
|
||||
self.mainBox.addWidget(self.searchButton, 0, 3)
|
||||
self.mainBox.addWidget(self.closeButton, 0, 4)
|
||||
self.mainBox.addWidget(self.replaceLabel, 1, 1)
|
||||
self.mainBox.addWidget(self.replaceBox, 1, 2)
|
||||
self.mainBox.addWidget(self.replaceButton, 1, 3)
|
||||
|
||||
self.mainBox.setColumnStretch(0,1)
|
||||
self.mainBox.setColumnStretch(1,0)
|
||||
self.mainBox.setColumnStretch(2,0)
|
||||
self.mainBox.setColumnStretch(3,0)
|
||||
self.mainBox.setColumnStretch(4,0)
|
||||
self.mainBox.setContentsMargins(0,0,0,0)
|
||||
self.mainBox.setColumnStretch(0, 1)
|
||||
self.mainBox.setColumnStretch(1, 0)
|
||||
self.mainBox.setColumnStretch(2, 0)
|
||||
self.mainBox.setColumnStretch(3, 0)
|
||||
self.mainBox.setColumnStretch(4, 0)
|
||||
self.mainBox.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.searchBox.setMinimumWidth(180)
|
||||
self.replaceBox.setMinimumWidth(180)
|
||||
boxWidth = 16*self.theTheme.textNWidth
|
||||
self.searchBox.setMinimumWidth(boxWidth)
|
||||
self.replaceBox.setMinimumWidth(boxWidth)
|
||||
|
||||
self._replaceVisible(False)
|
||||
|
||||
@@ -175,15 +176,18 @@ class GuiNoticeBar(QFrame):
|
||||
|
||||
logger.debug("Initialising GuiNoticeBar ...")
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
|
||||
self.setContentsMargins(0,0,0,0)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setFrameShape(QFrame.Box)
|
||||
|
||||
m8 = self.mainConf.pxInt(8)
|
||||
m2 = self.mainConf.pxInt(2)
|
||||
|
||||
self.mainBox = QHBoxLayout(self)
|
||||
self.mainBox.setContentsMargins(8,2,2,2)
|
||||
self.mainBox.setContentsMargins(m8, m2, m2, m2)
|
||||
|
||||
self.noteLabel = QLabel("")
|
||||
|
||||
|
||||
+12
-7
@@ -37,19 +37,24 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiDocViewDetails(QWidget):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising DocViewDetails ...")
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theProject = theParent.theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.currHandle = None
|
||||
|
||||
x4 = self.mainConf.pxInt(4)
|
||||
x80 = self.mainConf.pxInt(80)
|
||||
iPx = self.theTheme.textIconSize
|
||||
|
||||
self.outerBox = QGridLayout(self)
|
||||
self.outerBox.setContentsMargins(0,0,0,0)
|
||||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.outerBox.setHorizontalSpacing(0)
|
||||
self.outerBox.setVerticalSpacing(4)
|
||||
self.outerBox.setVerticalSpacing(x4)
|
||||
|
||||
self.refLabel = QLabel("Referenced By", self)
|
||||
|
||||
@@ -58,7 +63,7 @@ class GuiDocViewDetails(QWidget):
|
||||
self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.showHide.setArrowType(Qt.DownArrow)
|
||||
self.showHide.setCheckable(True)
|
||||
self.showHide.setIconSize(QSize(16,16))
|
||||
self.showHide.setIconSize(QSize(iPx, iPx))
|
||||
self.showHide.toggled.connect(self._doShowHide)
|
||||
|
||||
self.isSticky = QCheckBox("Sticky")
|
||||
@@ -75,7 +80,7 @@ class GuiDocViewDetails(QWidget):
|
||||
self.scrollBox.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.scrollBox.setFrameStyle(QFrame.NoFrame)
|
||||
self.scrollBox.setWidgetResizable(True)
|
||||
self.scrollBox.setFixedHeight(80)
|
||||
self.scrollBox.setFixedHeight(x80)
|
||||
self.scrollBox.setWidget(self.refList)
|
||||
|
||||
self.outerBox.addWidget(self.showHide, 0, 0)
|
||||
@@ -85,7 +90,7 @@ class GuiDocViewDetails(QWidget):
|
||||
self.outerBox.setColumnStretch(1, 1)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
self.setContentsMargins(0,0,0,0)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self._doShowHide(self.mainConf.showRefPanel)
|
||||
|
||||
|
||||
+15
-15
@@ -50,16 +50,16 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiDocEditor(QTextEdit):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QTextEdit.__init__(self)
|
||||
def __init__(self, theParent):
|
||||
QTextEdit.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiDocEditor ...")
|
||||
|
||||
# Class Variables
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theProject = theProject
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.docChanged = False
|
||||
self.spellCheck = False
|
||||
self.nwDocument = NWDoc(self.theProject, self.theParent)
|
||||
@@ -81,7 +81,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
# Core Elements
|
||||
self.qDocument = self.document()
|
||||
self.qDocument.setDocumentMargin(self.mainConf.textMargin)
|
||||
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
|
||||
self.qDocument.contentsChange.connect(self._docChange)
|
||||
|
||||
# Document Title
|
||||
@@ -98,7 +98,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
# Editor State
|
||||
self.hasSelection = False
|
||||
self.setMinimumWidth(300)
|
||||
self.setMinimumWidth(self.mainConf.pxInt(300))
|
||||
self.setAcceptRichText(False)
|
||||
|
||||
# Custom Shortcuts
|
||||
@@ -192,14 +192,13 @@ class GuiDocEditor(QTextEdit):
|
||||
self.setPalette(docPalette)
|
||||
|
||||
# Set default text margins
|
||||
self.qDocument.setDocumentMargin(self.mainConf.textMargin)
|
||||
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
|
||||
|
||||
# Also set the document text options for the document text flow
|
||||
theOpt = QTextOption()
|
||||
|
||||
if self.mainConf.tabWidth is not None:
|
||||
if self.mainConf.verQtValue >= 51000:
|
||||
theOpt.setTabStopDistance(self.mainConf.tabWidth)
|
||||
if self.mainConf.verQtValue >= 51000:
|
||||
theOpt.setTabStopDistance(self.mainConf.getTabWidth())
|
||||
if self.mainConf.doJustify:
|
||||
theOpt.setAlignment(Qt.AlignJustify)
|
||||
if self.mainConf.showTabsNSpaces:
|
||||
@@ -319,6 +318,7 @@ class GuiDocEditor(QTextEdit):
|
||||
Config.textFixedW is enabled or we're in Zen mode. Otherwise,
|
||||
just ensure the margins are set correctly.
|
||||
"""
|
||||
cM = self.mainConf.getTextMargin()
|
||||
if self.mainConf.textFixedW or self.theParent.isZenMode:
|
||||
vBar = self.verticalScrollBar()
|
||||
if vBar.isVisible():
|
||||
@@ -326,20 +326,20 @@ class GuiDocEditor(QTextEdit):
|
||||
else:
|
||||
sW = 0
|
||||
if self.theParent.isZenMode:
|
||||
tW = self.mainConf.zenWidth
|
||||
tW = self.mainConf.getZenWidth()
|
||||
else:
|
||||
tW = self.mainConf.textWidth
|
||||
tW = self.mainConf.getTextWidth()
|
||||
wW = self.width()
|
||||
tM = int((wW - sW - tW)/2)
|
||||
if tM < self.mainConf.textMargin:
|
||||
tM = self.mainConf.textMargin
|
||||
if tM < cM:
|
||||
tM = cM
|
||||
else:
|
||||
tM = self.mainConf.textMargin
|
||||
tM = cM
|
||||
|
||||
tB = self.lineWidth()
|
||||
tW = self.width() - 2*tB
|
||||
tH = self.docTitle.height()
|
||||
tT = self.mainConf.textMargin - tH
|
||||
tT = cM - tH
|
||||
self.docTitle.setGeometry(tB, tB, tW, tH)
|
||||
self.setViewportMargins(0, tH, 0, 0)
|
||||
|
||||
|
||||
+4
-4
@@ -62,8 +62,8 @@ class GuiDocMerge(QDialog):
|
||||
|
||||
self.listBox = QListWidget()
|
||||
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.listBox.setMinimumWidth(400)
|
||||
self.listBox.setMinimumHeight(180)
|
||||
self.listBox.setMinimumWidth(self.mainConf.pxInt(400))
|
||||
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
|
||||
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.buttonBox.accepted.connect(self._doMerge)
|
||||
@@ -72,9 +72,9 @@ class GuiDocMerge(QDialog):
|
||||
self.outerBox.setSpacing(0)
|
||||
self.outerBox.addWidget(self.headLabel)
|
||||
self.outerBox.addWidget(self.helpLabel)
|
||||
self.outerBox.addSpacing(8)
|
||||
self.outerBox.addSpacing(self.mainConf.pxInt(8))
|
||||
self.outerBox.addWidget(self.listBox)
|
||||
self.outerBox.addSpacing(12)
|
||||
self.outerBox.addSpacing(self.mainConf.pxInt(12))
|
||||
self.outerBox.addWidget(self.buttonBox)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
|
||||
+4
-4
@@ -62,8 +62,8 @@ class GuiDocSplit(QDialog):
|
||||
|
||||
self.listBox = QListWidget()
|
||||
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
|
||||
self.listBox.setMinimumWidth(400)
|
||||
self.listBox.setMinimumHeight(180)
|
||||
self.listBox.setMinimumWidth(self.mainConf.pxInt(400))
|
||||
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
|
||||
|
||||
self.splitLevel = QComboBox(self)
|
||||
self.splitLevel.addItem("Split on Header Level 1 (Title)", 1)
|
||||
@@ -84,10 +84,10 @@ class GuiDocSplit(QDialog):
|
||||
self.outerBox.setSpacing(0)
|
||||
self.outerBox.addWidget(self.headLabel)
|
||||
self.outerBox.addWidget(self.helpLabel)
|
||||
self.outerBox.addSpacing(8)
|
||||
self.outerBox.addSpacing(self.mainConf.pxInt(8))
|
||||
self.outerBox.addWidget(self.listBox)
|
||||
self.outerBox.addWidget(self.splitLevel)
|
||||
self.outerBox.addSpacing(12)
|
||||
self.outerBox.addSpacing(self.mainConf.pxInt(12))
|
||||
self.outerBox.addWidget(self.buttonBox)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
|
||||
+23
-19
@@ -40,20 +40,20 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiDocViewer(QTextBrowser):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QTextBrowser.__init__(self)
|
||||
def __init__(self, theParent):
|
||||
QTextBrowser.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising DocViewer ...")
|
||||
logger.debug("Initialising GuiDocViewer ...")
|
||||
|
||||
# Class Variables
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theProject = theProject
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.theHandle = None
|
||||
|
||||
self.qDocument = self.document()
|
||||
self.setMinimumWidth(300)
|
||||
self.setMinimumWidth(self.mainConf.pxInt(300))
|
||||
self.setOpenExternalLinks(False)
|
||||
self.initViewer()
|
||||
|
||||
@@ -70,7 +70,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.anchorClicked.connect(self._linkClicked)
|
||||
self.setFocusPolicy(Qt.StrongFocus)
|
||||
|
||||
logger.debug("DocViewer initialisation complete")
|
||||
logger.debug("GuiDocViewer initialisation complete")
|
||||
|
||||
# Connect Functions
|
||||
self.setSelectedHandle = self.theParent.treeView.setSelectedHandle
|
||||
@@ -105,7 +105,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
docPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
self.setPalette(docPalette)
|
||||
|
||||
self.qDocument.setDocumentMargin(self.mainConf.textMargin)
|
||||
self.qDocument.setDocumentMargin(self.mainConf.getTextMargin())
|
||||
theOpt = QTextOption()
|
||||
if self.mainConf.doJustify:
|
||||
theOpt.setAlignment(Qt.AlignJustify)
|
||||
@@ -216,7 +216,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
tB = self.lineWidth()
|
||||
tW = self.width() - 2*tB
|
||||
tH = self.docTitle.height()
|
||||
tT = self.mainConf.textMargin - tH
|
||||
tT = self.mainConf.getTextMargin() - tH
|
||||
self.docTitle.setGeometry(tB, tB, tW, tH)
|
||||
self.setViewportMargins(0, tH, 0, 0)
|
||||
|
||||
@@ -268,6 +268,21 @@ class GuiDocViewer(QTextBrowser):
|
||||
logger.verbose("Cursor moved to line %d" % theLine)
|
||||
return True
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _linkClicked(self, theURL):
|
||||
"""Slot for a link in the document being clicked.
|
||||
"""
|
||||
theLink = theURL.url()
|
||||
logger.verbose("Clicked link: '%s'" % theLink)
|
||||
if len(theLink) > 0:
|
||||
theBits = theLink.split("=")
|
||||
if len(theBits) == 2:
|
||||
self.loadFromTag(theBits[1])
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -293,17 +308,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.setTextCursor(theCursor)
|
||||
return
|
||||
|
||||
def _linkClicked(self, theURL):
|
||||
"""Slot for a link in the document being clicked.
|
||||
"""
|
||||
theLink = theURL.url()
|
||||
logger.verbose("Clicked link: '%s'" % theLink)
|
||||
if len(theLink) > 0:
|
||||
theBits = theLink.split("=")
|
||||
if len(theBits) == 2:
|
||||
self.loadFromTag(theBits[1])
|
||||
return
|
||||
|
||||
def _makeStyleSheet(self):
|
||||
"""Generate an appropriate style sheet for the document viewer,
|
||||
based on the current syntax highlighter theme,
|
||||
|
||||
+10
-10
@@ -30,7 +30,7 @@ import nw
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QFont, QIcon, QPixmap
|
||||
from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel
|
||||
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
|
||||
|
||||
from nw.constants import (
|
||||
nwLabels, nwItemClass, nwItemType, nwItemLayout, nwUnicode
|
||||
@@ -38,15 +38,15 @@ from nw.constants import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiItemDetails(QFrame):
|
||||
class GuiItemDetails(QWidget):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QFrame.__init__(self, theParent)
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiItemDetails ...")
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theProject = theParent.theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theHandle = None
|
||||
|
||||
@@ -170,11 +170,11 @@ class GuiItemDetails(QFrame):
|
||||
self.mainBox.addWidget(self.pCountName, 3, 3, 1, 1)
|
||||
self.mainBox.addWidget(self.pCountData, 3, 4, 1, 1)
|
||||
|
||||
self.mainBox.setColumnStretch(0,0)
|
||||
self.mainBox.setColumnStretch(1,0)
|
||||
self.mainBox.setColumnStretch(2,1)
|
||||
self.mainBox.setColumnStretch(3,0)
|
||||
self.mainBox.setColumnStretch(4,0)
|
||||
self.mainBox.setColumnStretch(0, 0)
|
||||
self.mainBox.setColumnStretch(1, 0)
|
||||
self.mainBox.setColumnStretch(2, 1)
|
||||
self.mainBox.setColumnStretch(3, 0)
|
||||
self.mainBox.setColumnStretch(4, 0)
|
||||
|
||||
# Make sure the columns for flags and counts don't resize too often
|
||||
flagWidth = self.theTheme.getTextWidth("Mm", self.fntValue)
|
||||
|
||||
@@ -61,8 +61,8 @@ class GuiItemEditor(QDialog):
|
||||
|
||||
# Item Label
|
||||
self.editName = QLineEdit()
|
||||
self.editName.setMinimumWidth(220)
|
||||
self.editName.setMaxLength(200)
|
||||
self.editName.setMinimumWidth(self.mainConf.pxInt(220))
|
||||
self.editName.setMaxLength(self.mainConf.pxInt(200))
|
||||
|
||||
# Item Status
|
||||
self.editStatus = QComboBox()
|
||||
@@ -135,7 +135,7 @@ class GuiItemEditor(QDialog):
|
||||
self.mainForm.addWidget(self.textExport, 3, 0, 1, 2)
|
||||
self.mainForm.addWidget(self.editExport, 3, 2, 1, 1)
|
||||
|
||||
self.outerBox.setSpacing(16)
|
||||
self.outerBox.setSpacing(self.mainConf.pxInt(16))
|
||||
self.outerBox.addLayout(self.mainForm)
|
||||
self.outerBox.addStretch(1)
|
||||
self.outerBox.addWidget(self.buttonBox)
|
||||
|
||||
+6
-6
@@ -39,13 +39,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiMainMenu(QMenuBar):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
def __init__(self, theParent):
|
||||
QMenuBar.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising Main Menu ...")
|
||||
logger.debug("Initialising GuiMainMenu ...")
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theProject = theParent.theProject
|
||||
|
||||
self._buildProjectMenu()
|
||||
self._buildDocumentMenu()
|
||||
@@ -60,7 +60,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self._moveTreeItem = self.theParent.treeView.moveTreeItem
|
||||
self._newTreeItem = self.theParent.treeView.newTreeItem
|
||||
|
||||
logger.debug("Main Menu initialisation complete")
|
||||
logger.debug("GuiMainMenu initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
@@ -233,14 +233,14 @@ class GuiMainMenu(QMenuBar):
|
||||
self.projMenu.addSeparator()
|
||||
|
||||
# Project > Edit
|
||||
self.aEditItem = QAction("&Edit Item", self)
|
||||
self.aEditItem = QAction("&Edit Project Item", self)
|
||||
self.aEditItem.setStatusTip("Change item settings")
|
||||
self.aEditItem.setShortcuts(["Ctrl+E", "F2"])
|
||||
self.aEditItem.triggered.connect(self.theParent.editItem)
|
||||
self.projMenu.addAction(self.aEditItem)
|
||||
|
||||
# Project > Delete
|
||||
self.aDeleteItem = QAction("&Delete Item", self)
|
||||
self.aDeleteItem = QAction("&Delete Project Item", self)
|
||||
self.aDeleteItem.setStatusTip("Delete selected item")
|
||||
self.aDeleteItem.setShortcut("Ctrl+Del")
|
||||
self.aDeleteItem.triggered.connect(lambda : self.theParent.treeView.deleteItem(None))
|
||||
|
||||
+29
-16
@@ -39,7 +39,7 @@ from nw.constants import nwKeyWords, nwLabels, nwOutline
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiProjectOutline(QTreeWidget):
|
||||
class GuiOutline(QTreeWidget):
|
||||
|
||||
DEF_WIDTH = {
|
||||
nwOutline.TITLE : 200,
|
||||
@@ -79,17 +79,17 @@ class GuiProjectOutline(QTreeWidget):
|
||||
nwOutline.SYNOP : False,
|
||||
}
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
def __init__(self, theParent):
|
||||
QTreeWidget.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising ProjectOutline ...")
|
||||
logger.debug("Initialising GuiOutline ...")
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theProject = theParent.theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.optState = theProject.optState
|
||||
self.optState = theParent.theProject.optState
|
||||
self.headerMenu = GuiOutlineHeaderMenu(self)
|
||||
|
||||
self.firstView = True
|
||||
@@ -100,6 +100,7 @@ class GuiProjectOutline(QTreeWidget):
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.setDragEnabled(False)
|
||||
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||
self.itemSelectionChanged.connect(self._itemSelected)
|
||||
|
||||
iPx = self.theTheme.textIconSize
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
@@ -120,7 +121,7 @@ class GuiProjectOutline(QTreeWidget):
|
||||
self.clearOutline()
|
||||
self.headerMenu.setHiddenState(self.colHidden)
|
||||
|
||||
logger.debug("ProjectOutline initialisation complete")
|
||||
logger.debug("GuiOutline initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
@@ -198,6 +199,17 @@ class GuiProjectOutline(QTreeWidget):
|
||||
self.theParent.openDocument(tHandle, tLine - 1)
|
||||
return
|
||||
|
||||
def _itemSelected(self):
|
||||
"""Extract the handle and line number of the currently selected
|
||||
title, and send it to the details panel.
|
||||
"""
|
||||
selItems = self.selectedItems()
|
||||
if selItems:
|
||||
tHandle = selItems[0].data(self.colIndex[nwOutline.TITLE], Qt.UserRole)
|
||||
sTitle = selItems[0].data(self.colIndex[nwOutline.LINE], Qt.UserRole)
|
||||
self.theParent.projMeta.showItem(tHandle, sTitle)
|
||||
return
|
||||
|
||||
def _headerRightClick(self, clickPos):
|
||||
"""Show the header column menu.
|
||||
"""
|
||||
@@ -233,7 +245,7 @@ class GuiProjectOutline(QTreeWidget):
|
||||
# Load whatever we saved last time, regardless of wether it
|
||||
# contains the correct names or number of columns. The names
|
||||
# must be valid though.
|
||||
tempOrder = self.optState.getValue("GuiProjectOutline", "headerOrder", [])
|
||||
tempOrder = self.optState.getValue("GuiOutline", "headerOrder", [])
|
||||
treeOrder = []
|
||||
for hName in tempOrder:
|
||||
try:
|
||||
@@ -256,14 +268,14 @@ class GuiProjectOutline(QTreeWidget):
|
||||
|
||||
# We load whatever column widths and hidden states we find in
|
||||
# the file, and leave the rest in their default state.
|
||||
tmpWidth = self.optState.getValue("GuiProjectOutline", "columnWidth", {})
|
||||
tmpWidth = self.optState.getValue("GuiOutline", "columnWidth", {})
|
||||
for hName in tmpWidth:
|
||||
try:
|
||||
self.colWidth[nwOutline[hName]] = tmpWidth[hName]
|
||||
self.colWidth[nwOutline[hName]] = self.mainConf.pxInt(tmpWidth[hName])
|
||||
except:
|
||||
logger.warning("Ignored unknown outline column '%s'" % str(hName))
|
||||
|
||||
tmpHidden = self.optState.getValue("GuiProjectOutline", "columnHidden", {})
|
||||
tmpHidden = self.optState.getValue("GuiOutline", "columnHidden", {})
|
||||
for hName in tmpHidden:
|
||||
try:
|
||||
self.colHidden[nwOutline[hName]] = tmpHidden[hName]
|
||||
@@ -289,7 +301,7 @@ class GuiProjectOutline(QTreeWidget):
|
||||
colHidden = {}
|
||||
|
||||
for hItem in nwOutline:
|
||||
colWidth[hItem.name] = self.colWidth[hItem]
|
||||
colWidth[hItem.name] = self.mainConf.rpxInt(self.colWidth[hItem])
|
||||
colHidden[hItem.name] = self.colHidden[hItem]
|
||||
|
||||
for iCol in range(self.columnCount()):
|
||||
@@ -297,16 +309,16 @@ class GuiProjectOutline(QTreeWidget):
|
||||
treeOrder.append(hName)
|
||||
|
||||
iLog = self.treeHead.logicalIndex(iCol)
|
||||
logWidth = self.columnWidth(iLog)
|
||||
logWidth = self.mainConf.rpxInt(self.columnWidth(iLog))
|
||||
logHidden = self.isColumnHidden(iLog)
|
||||
|
||||
colHidden[hName] = logHidden
|
||||
if not logHidden and logWidth > 0:
|
||||
colWidth[hName] = logWidth
|
||||
|
||||
self.optState.setValue("GuiProjectOutline", "headerOrder", treeOrder)
|
||||
self.optState.setValue("GuiProjectOutline", "columnWidth", colWidth)
|
||||
self.optState.setValue("GuiProjectOutline", "columnHidden", colHidden)
|
||||
self.optState.setValue("GuiOutline", "headerOrder", treeOrder)
|
||||
self.optState.setValue("GuiOutline", "columnWidth", colWidth)
|
||||
self.optState.setValue("GuiOutline", "columnHidden", colHidden)
|
||||
self.optState.saveSettings()
|
||||
|
||||
return
|
||||
@@ -413,6 +425,7 @@ class GuiProjectOutline(QTreeWidget):
|
||||
newItem.setText(self.colIndex[nwOutline.LABEL], nwItem.itemName)
|
||||
newItem.setIcon(self.colIndex[nwOutline.LABEL], self.theTheme.getIcon("proj_document"))
|
||||
newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:].lstrip("0"))
|
||||
newItem.setData(self.colIndex[nwOutline.LINE], Qt.UserRole, sTitle)
|
||||
newItem.setText(self.colIndex[nwOutline.SYNOP], novIdx["synopsis"])
|
||||
newItem.setText(self.colIndex[nwOutline.CCOUNT], str(novIdx["cCount"]))
|
||||
newItem.setText(self.colIndex[nwOutline.WCOUNT], str(novIdx["wCount"]))
|
||||
@@ -433,7 +446,7 @@ class GuiProjectOutline(QTreeWidget):
|
||||
|
||||
return newItem
|
||||
|
||||
# END Class GuiProjectOutline
|
||||
# END Class GuiOutline
|
||||
|
||||
class GuiOutlineHeaderMenu(QMenu):
|
||||
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""novelWriter GUI Project Outline
|
||||
|
||||
novelWriter – GUI Project Outline
|
||||
===================================
|
||||
Class holding the project outline view
|
||||
|
||||
File History:
|
||||
Created: 2020-06-02 [0.7.0]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2020, 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/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import nw
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QScrollArea, QWidget, QGridLayout, QHBoxLayout, QGroupBox, QLabel, QSizePolicy
|
||||
)
|
||||
|
||||
from nw.constants import nwLabels, nwKeyWords
|
||||
from nw.common import checkInt
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiOutlineDetails(QScrollArea):
|
||||
|
||||
LVL_MAP = {
|
||||
"H1" : "Title",
|
||||
"H2" : "Chapter",
|
||||
"H3" : "Scene",
|
||||
"H4" : "Section"
|
||||
}
|
||||
|
||||
def __init__(self, theParent):
|
||||
QScrollArea.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiOutlineDetails ...")
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.optState = theParent.theProject.optState
|
||||
|
||||
# Sizes
|
||||
minTitle = 30*self.theTheme.textNWidth
|
||||
maxTitle = 40*self.theTheme.textNWidth
|
||||
wCount = self.theTheme.getTextWidth("999,999")
|
||||
hSpace = int(self.mainConf.pxInt(10))
|
||||
vSpace = int(self.mainConf.pxInt(4))
|
||||
|
||||
# Details Area
|
||||
self.titleLabel = QLabel("<b>Title</b>")
|
||||
self.fileLabel = QLabel("<b>Document</b>")
|
||||
self.itemLabel = QLabel("<b>Status</b>")
|
||||
self.titleValue = QLabel("")
|
||||
self.fileValue = QLabel("")
|
||||
self.itemValue = QLabel("")
|
||||
|
||||
self.titleValue.setMinimumWidth(minTitle)
|
||||
self.titleValue.setMaximumWidth(maxTitle)
|
||||
self.fileValue.setMinimumWidth(minTitle)
|
||||
self.fileValue.setMaximumWidth(maxTitle)
|
||||
self.itemValue.setMinimumWidth(minTitle)
|
||||
self.itemValue.setMaximumWidth(maxTitle)
|
||||
|
||||
# Stats Area
|
||||
self.cCLabel = QLabel("<b>Characters</b>")
|
||||
self.wCLabel = QLabel("<b>Words</b>")
|
||||
self.pCLabel = QLabel("<b>Paragraphs</b>")
|
||||
self.cCValue = QLabel("")
|
||||
self.wCValue = QLabel("")
|
||||
self.pCValue = QLabel("")
|
||||
|
||||
self.cCValue.setMinimumWidth(wCount)
|
||||
self.wCValue.setMinimumWidth(wCount)
|
||||
self.pCValue.setMinimumWidth(wCount)
|
||||
self.cCValue.setAlignment(Qt.AlignRight)
|
||||
self.wCValue.setAlignment(Qt.AlignRight)
|
||||
self.pCValue.setAlignment(Qt.AlignRight)
|
||||
|
||||
# Synopsis
|
||||
self.synopLabel = QLabel("<b>Synopsis</b>")
|
||||
self.synopValue = QLabel("")
|
||||
self.synopLWrap = QHBoxLayout()
|
||||
self.synopValue.setWordWrap(True)
|
||||
self.synopValue.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||
self.synopLWrap.addWidget(self.synopValue, 1)
|
||||
|
||||
# Tags
|
||||
self.povKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
|
||||
self.chrKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY])
|
||||
self.pltKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
|
||||
self.timKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.TIME_KEY])
|
||||
self.wldKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY])
|
||||
self.objKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY])
|
||||
self.entKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY])
|
||||
self.cstKeyLabel = QLabel("<b>%s</b>" % nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY])
|
||||
|
||||
self.povKeyLWrap = QHBoxLayout()
|
||||
self.chrKeyLWrap = QHBoxLayout()
|
||||
self.pltKeyLWrap = QHBoxLayout()
|
||||
self.timKeyLWrap = QHBoxLayout()
|
||||
self.wldKeyLWrap = QHBoxLayout()
|
||||
self.objKeyLWrap = QHBoxLayout()
|
||||
self.entKeyLWrap = QHBoxLayout()
|
||||
self.cstKeyLWrap = QHBoxLayout()
|
||||
|
||||
self.povKeyValue = QLabel("")
|
||||
self.chrKeyValue = QLabel("")
|
||||
self.pltKeyValue = QLabel("")
|
||||
self.timKeyValue = QLabel("")
|
||||
self.wldKeyValue = QLabel("")
|
||||
self.objKeyValue = QLabel("")
|
||||
self.entKeyValue = QLabel("")
|
||||
self.cstKeyValue = QLabel("")
|
||||
|
||||
self.povKeyValue.setWordWrap(True)
|
||||
self.chrKeyValue.setWordWrap(True)
|
||||
self.pltKeyValue.setWordWrap(True)
|
||||
self.timKeyValue.setWordWrap(True)
|
||||
self.wldKeyValue.setWordWrap(True)
|
||||
self.objKeyValue.setWordWrap(True)
|
||||
self.entKeyValue.setWordWrap(True)
|
||||
self.cstKeyValue.setWordWrap(True)
|
||||
|
||||
self.povKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.chrKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.pltKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.timKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.wldKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.objKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.entKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.cstKeyValue.linkActivated.connect(self._tagClicked)
|
||||
|
||||
self.povKeyLWrap.addWidget(self.povKeyValue, 1)
|
||||
self.chrKeyLWrap.addWidget(self.chrKeyValue, 1)
|
||||
self.pltKeyLWrap.addWidget(self.pltKeyValue, 1)
|
||||
self.timKeyLWrap.addWidget(self.timKeyValue, 1)
|
||||
self.wldKeyLWrap.addWidget(self.wldKeyValue, 1)
|
||||
self.objKeyLWrap.addWidget(self.objKeyValue, 1)
|
||||
self.entKeyLWrap.addWidget(self.entKeyValue, 1)
|
||||
self.cstKeyLWrap.addWidget(self.cstKeyValue, 1)
|
||||
|
||||
# Selected Item Details
|
||||
self.mainGroup = QGroupBox("Title Details", self)
|
||||
self.mainForm = QGridLayout()
|
||||
self.mainGroup.setLayout(self.mainForm)
|
||||
|
||||
self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft)
|
||||
|
||||
self.mainForm.setColumnStretch(1, 1)
|
||||
self.mainForm.setRowStretch(4, 1)
|
||||
self.mainForm.setHorizontalSpacing(hSpace)
|
||||
self.mainForm.setVerticalSpacing(vSpace)
|
||||
|
||||
# Selected Item Tags
|
||||
self.tagsGroup = QGroupBox("Reference Tags", self)
|
||||
self.tagsForm = QGridLayout()
|
||||
self.tagsGroup.setLayout(self.tagsForm)
|
||||
|
||||
self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.chrKeyLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.chrKeyLWrap, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.pltKeyLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.pltKeyLWrap, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.timKeyLabel, 3, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.timKeyLWrap, 3, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.wldKeyLabel, 4, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.wldKeyLWrap, 4, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.objKeyLabel, 5, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.objKeyLWrap, 5, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.entKeyLabel, 6, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.entKeyLWrap, 6, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.cstKeyLabel, 7, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.cstKeyLWrap, 7, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
|
||||
self.tagsForm.setColumnStretch(1, 1)
|
||||
self.tagsForm.setRowStretch(7, 1)
|
||||
self.tagsForm.setHorizontalSpacing(hSpace)
|
||||
self.tagsForm.setVerticalSpacing(vSpace)
|
||||
|
||||
# Assemble
|
||||
self.outerWidget = QWidget()
|
||||
self.outerBox = QHBoxLayout()
|
||||
self.outerBox.addWidget(self.mainGroup, 0)
|
||||
self.outerBox.addWidget(self.tagsGroup, 1)
|
||||
# self.outerBox.addStretch(1)
|
||||
|
||||
self.outerWidget.setLayout(self.outerBox)
|
||||
self.setWidget(self.outerWidget)
|
||||
self.show()
|
||||
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setWidgetResizable(True)
|
||||
|
||||
logger.debug("GuiOutlineDetails initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def showItem(self, tHandle, sTitle):
|
||||
"""Update the content of the tree with the given handle and line
|
||||
number pointing to a header.
|
||||
"""
|
||||
try:
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
novIdx = self.theIndex.novelIndex[tHandle][sTitle]
|
||||
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||
except:
|
||||
return False
|
||||
|
||||
if novIdx["level"] in self.LVL_MAP:
|
||||
self.titleLabel.setText("<b>%s</b>" % self.LVL_MAP[novIdx["level"]])
|
||||
else:
|
||||
self.titleLabel.setText("<b>Title</b>")
|
||||
self.titleValue.setText(novIdx["title"])
|
||||
|
||||
self.fileValue.setText(nwItem.itemName)
|
||||
self.itemValue.setText(nwItem.itemStatus)
|
||||
|
||||
self.cCValue.setText("{:n}".format(checkInt(novIdx["cCount"], 0)))
|
||||
self.wCValue.setText("{:n}".format(checkInt(novIdx["pCount"], 0)))
|
||||
self.pCValue.setText("{:n}".format(checkInt(novIdx["wCount"], 0)))
|
||||
|
||||
self.synopValue.setText(novIdx["synopsis"])
|
||||
|
||||
self.povKeyValue.setText(self._formatTags(theRefs, nwKeyWords.POV_KEY))
|
||||
self.chrKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CHAR_KEY))
|
||||
self.pltKeyValue.setText(self._formatTags(theRefs, nwKeyWords.PLOT_KEY))
|
||||
self.timKeyValue.setText(self._formatTags(theRefs, nwKeyWords.TIME_KEY))
|
||||
self.wldKeyValue.setText(self._formatTags(theRefs, nwKeyWords.WORLD_KEY))
|
||||
self.objKeyValue.setText(self._formatTags(theRefs, nwKeyWords.OBJECT_KEY))
|
||||
self.entKeyValue.setText(self._formatTags(theRefs, nwKeyWords.ENTITY_KEY))
|
||||
self.cstKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CUSTOM_KEY))
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _tagClicked(self, theLink):
|
||||
"""Capture the click of a tag in the right-most column.
|
||||
"""
|
||||
logger.verbose("Clicked link: '%s'" % theLink)
|
||||
if len(theLink) > 0:
|
||||
theBits = theLink.split("=")
|
||||
if len(theBits) == 2:
|
||||
self.theParent.docViewer.loadFromTag(theBits[1])
|
||||
self.theParent.tabWidget.setCurrentWidget(self.theParent.splitView)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _formatTags(self, theRefs, theKey):
|
||||
"""Format the tags as clickable links.
|
||||
"""
|
||||
if theKey not in theKey:
|
||||
return ""
|
||||
refTags = []
|
||||
for tTag in theRefs[theKey]:
|
||||
refTags.append("<a href='#%s=%s'>%s</a>" % (
|
||||
theKey[1:], tTag, tTag
|
||||
))
|
||||
return ", ".join(refTags)
|
||||
|
||||
# END Class GuiOutlineDetails
|
||||
+13
-9
@@ -143,7 +143,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
||||
|
||||
## Select Theme
|
||||
self.selectTheme = QComboBox()
|
||||
self.selectTheme.setMinimumWidth(200)
|
||||
self.selectTheme.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.theThemes = self.theTheme.listThemes()
|
||||
for themeDir, themeName in self.theThemes:
|
||||
self.selectTheme.addItem(themeName, themeDir)
|
||||
@@ -159,7 +159,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
||||
|
||||
## Select Icon Theme
|
||||
self.selectIcons = QComboBox()
|
||||
self.selectIcons.setMinimumWidth(200)
|
||||
self.selectIcons.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.theIcons = self.theTheme.theIcons.listThemes()
|
||||
for iconDir, iconName in self.theIcons:
|
||||
self.selectIcons.addItem(iconName, iconDir)
|
||||
@@ -185,7 +185,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
||||
## Font Family
|
||||
self.guiFont = QLineEdit()
|
||||
self.guiFont.setReadOnly(True)
|
||||
self.guiFont.setFixedWidth(162)
|
||||
self.guiFont.setFixedWidth(self.mainConf.pxInt(162))
|
||||
self.guiFont.setText(self.mainConf.guiFont)
|
||||
self.fontButton = QPushButton("...")
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||
@@ -329,6 +329,8 @@ class GuiConfigEditGeneralTab(QWidget):
|
||||
##
|
||||
|
||||
def _backupFolder(self):
|
||||
"""Open a dialog to select the backup folder.
|
||||
"""
|
||||
|
||||
currDir = self.backupPath
|
||||
if not path.isdir(currDir):
|
||||
@@ -389,7 +391,7 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
## Font Family
|
||||
self.textStyleFont = QLineEdit()
|
||||
self.textStyleFont.setReadOnly(True)
|
||||
self.textStyleFont.setFixedWidth(162)
|
||||
self.textStyleFont.setFixedWidth(self.mainConf.pxInt(162))
|
||||
self.textStyleFont.setText(self.mainConf.textFont)
|
||||
self.fontButton = QPushButton("...")
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||
@@ -575,7 +577,7 @@ class GuiConfigEditEditingTab(QWidget):
|
||||
|
||||
## Syntax Highlighting
|
||||
self.selectSyntax = QComboBox()
|
||||
self.selectSyntax.setMinimumWidth(200)
|
||||
self.selectSyntax.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.theSyntaxes = self.theTheme.listSyntax()
|
||||
for syntaxFile, syntaxName in self.theSyntaxes:
|
||||
self.selectSyntax.addItem(syntaxName, syntaxFile)
|
||||
@@ -789,10 +791,12 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
# ===============
|
||||
self.mainForm.addGroupLabel("Quotation Style")
|
||||
|
||||
qWidth = self.mainConf.pxInt(40)
|
||||
|
||||
## Single Quote Style
|
||||
self.quoteSingleStyleO = QLineEdit()
|
||||
self.quoteSingleStyleO.setMaxLength(1)
|
||||
self.quoteSingleStyleO.setFixedWidth(40)
|
||||
self.quoteSingleStyleO.setFixedWidth(qWidth)
|
||||
self.quoteSingleStyleO.setAlignment(Qt.AlignCenter)
|
||||
self.quoteSingleStyleO.setText(self.mainConf.fmtSingleQuotes[0])
|
||||
self.mainForm.addRow(
|
||||
@@ -803,7 +807,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
|
||||
self.quoteSingleStyleC = QLineEdit()
|
||||
self.quoteSingleStyleC.setMaxLength(1)
|
||||
self.quoteSingleStyleC.setFixedWidth(40)
|
||||
self.quoteSingleStyleC.setFixedWidth(qWidth)
|
||||
self.quoteSingleStyleC.setAlignment(Qt.AlignCenter)
|
||||
self.quoteSingleStyleC.setText(self.mainConf.fmtSingleQuotes[1])
|
||||
self.mainForm.addRow(
|
||||
@@ -815,7 +819,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
## Double Quote Style
|
||||
self.quoteDoubleStyleO = QLineEdit()
|
||||
self.quoteDoubleStyleO.setMaxLength(1)
|
||||
self.quoteDoubleStyleO.setFixedWidth(40)
|
||||
self.quoteDoubleStyleO.setFixedWidth(qWidth)
|
||||
self.quoteDoubleStyleO.setAlignment(Qt.AlignCenter)
|
||||
self.quoteDoubleStyleO.setText(self.mainConf.fmtDoubleQuotes[0])
|
||||
self.mainForm.addRow(
|
||||
@@ -826,7 +830,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
|
||||
self.quoteDoubleStyleC = QLineEdit()
|
||||
self.quoteDoubleStyleC.setMaxLength(1)
|
||||
self.quoteDoubleStyleC.setFixedWidth(40)
|
||||
self.quoteDoubleStyleC.setFixedWidth(qWidth)
|
||||
self.quoteDoubleStyleC.setAlignment(Qt.AlignCenter)
|
||||
self.quoteDoubleStyleC.setText(self.mainConf.fmtDoubleQuotes[1])
|
||||
self.mainForm.addRow(
|
||||
|
||||
+17
-14
@@ -61,17 +61,20 @@ class GuiProjectLoad(QDialog):
|
||||
self.openState = self.NONE_STATE
|
||||
self.openPath = None
|
||||
|
||||
sPx = self.mainConf.pxInt(16)
|
||||
iPx = self.mainConf.pxInt(96)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.innerBox = QHBoxLayout()
|
||||
self.outerBox.setSpacing(16)
|
||||
self.innerBox.setSpacing(16)
|
||||
self.outerBox.setSpacing(sPx)
|
||||
self.innerBox.setSpacing(sPx)
|
||||
|
||||
self.setWindowTitle("Open Project")
|
||||
self.setMinimumWidth(650)
|
||||
self.setMinimumHeight(400)
|
||||
self.setMinimumWidth(self.mainConf.pxInt(650))
|
||||
self.setMinimumHeight(self.mainConf.pxInt(400))
|
||||
self.setModal(True)
|
||||
|
||||
self.guiDeco = self.theTheme.loadDecoration("nwicon", (96, 96))
|
||||
self.guiDeco = self.theTheme.loadDecoration("nwicon", (iPx, iPx))
|
||||
self.innerBox.addWidget(self.guiDeco, 0, Qt.AlignTop)
|
||||
|
||||
self.projectForm = QGridLayout()
|
||||
@@ -81,10 +84,9 @@ class GuiProjectLoad(QDialog):
|
||||
self.listBox = QTreeWidget()
|
||||
self.listBox.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
|
||||
self.listBox.setColumnCount(4)
|
||||
self.listBox.setHeaderLabels(["Working Title","Words","Last Opened","Path"])
|
||||
self.listBox.setColumnCount(3)
|
||||
self.listBox.setHeaderLabels(["Working Title", "Words", "Last Opened"])
|
||||
self.listBox.setRootIsDecorated(False)
|
||||
self.listBox.setColumnHidden(3, True)
|
||||
self.listBox.itemSelectionChanged.connect(self._doSelectRecent)
|
||||
self.listBox.itemDoubleClicked.connect(self._doOpenRecent)
|
||||
self.listBox.setIconSize(QSize(iPx, iPx))
|
||||
@@ -110,8 +112,8 @@ class GuiProjectLoad(QDialog):
|
||||
self.projectForm.setColumnStretch(0, 0)
|
||||
self.projectForm.setColumnStretch(1, 1)
|
||||
self.projectForm.setColumnStretch(2, 0)
|
||||
self.projectForm.setVerticalSpacing(4)
|
||||
self.projectForm.setHorizontalSpacing(8)
|
||||
self.projectForm.setVerticalSpacing(self.mainConf.pxInt(4))
|
||||
self.projectForm.setHorizontalSpacing(self.mainConf.pxInt(8))
|
||||
|
||||
self.innerBox.addLayout(self.projectForm)
|
||||
|
||||
@@ -148,7 +150,7 @@ class GuiProjectLoad(QDialog):
|
||||
self._saveDialogState()
|
||||
selItems = self.listBox.selectedItems()
|
||||
if selItems:
|
||||
self.openPath = selItems[0].text(3)
|
||||
self.openPath = selItems[0].data(0, Qt.UserRole)
|
||||
self.openState = self.OPEN_STATE
|
||||
self.accept()
|
||||
else:
|
||||
@@ -161,7 +163,7 @@ class GuiProjectLoad(QDialog):
|
||||
"""
|
||||
selList = self.listBox.selectedItems()
|
||||
if selList:
|
||||
self.selPath.setText(selList[0].text(3))
|
||||
self.selPath.setText(selList[0].data(0, Qt.UserRole))
|
||||
return
|
||||
|
||||
def _doBrowse(self):
|
||||
@@ -251,9 +253,9 @@ class GuiProjectLoad(QDialog):
|
||||
newItem = QTreeWidgetItem([""]*4)
|
||||
newItem.setIcon(0, self.theParent.theTheme.getIcon("proj_nwx"))
|
||||
newItem.setText(0, listData[timeStamp][0])
|
||||
newItem.setData(0, Qt.UserRole, listData[timeStamp][2])
|
||||
newItem.setText(1, formatInt(listData[timeStamp][1]))
|
||||
newItem.setText(2, datetime.fromtimestamp(timeStamp).strftime("%x %X"))
|
||||
newItem.setText(3, listData[timeStamp][2])
|
||||
newItem.setTextAlignment(0, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
newItem.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter)
|
||||
newItem.setTextAlignment(2, Qt.AlignRight | Qt.AlignVCenter)
|
||||
@@ -262,8 +264,9 @@ class GuiProjectLoad(QDialog):
|
||||
newItem.setSelected(True)
|
||||
hasSelection = True
|
||||
|
||||
projColWidth = self.mainConf.getProjColWidths()
|
||||
for i in range(3):
|
||||
self.listBox.setColumnWidth(i, self.mainConf.projColWidth[i])
|
||||
self.listBox.setColumnWidth(i, projColWidth[i])
|
||||
|
||||
return
|
||||
|
||||
|
||||
+32
-21
@@ -119,6 +119,7 @@ class GuiProjectEditMain(QWidget):
|
||||
def __init__(self, theParent, theProject):
|
||||
QWidget.__init__(self, theParent)
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
|
||||
@@ -129,9 +130,12 @@ class GuiProjectEditMain(QWidget):
|
||||
|
||||
self.mainForm.addGroupLabel("Project Settings")
|
||||
|
||||
xW = self.mainConf.pxInt(250)
|
||||
xH = self.mainConf.pxInt(100)
|
||||
|
||||
self.editName = QLineEdit()
|
||||
self.editName.setMaxLength(200)
|
||||
self.editName.setFixedWidth(250)
|
||||
self.editName.setFixedWidth(xW)
|
||||
self.editName.setText(self.theProject.projName)
|
||||
self.mainForm.addRow(
|
||||
"Working title",
|
||||
@@ -141,7 +145,7 @@ class GuiProjectEditMain(QWidget):
|
||||
|
||||
self.editTitle = QLineEdit()
|
||||
self.editTitle.setMaxLength(200)
|
||||
self.editTitle.setFixedWidth(250)
|
||||
self.editTitle.setFixedWidth(xW)
|
||||
self.editTitle.setText(self.theProject.bookTitle)
|
||||
self.mainForm.addRow(
|
||||
"Novel title",
|
||||
@@ -154,8 +158,8 @@ class GuiProjectEditMain(QWidget):
|
||||
for bookAuthor in self.theProject.bookAuthors:
|
||||
bookAuthors += bookAuthor+"\n"
|
||||
self.editAuthors.setPlainText(bookAuthors)
|
||||
self.editAuthors.setFixedHeight(100)
|
||||
self.editAuthors.setFixedWidth(250)
|
||||
self.editAuthors.setFixedHeight(xH)
|
||||
self.editAuthors.setFixedWidth(xW)
|
||||
self.mainForm.addRow(
|
||||
"Author(s)",
|
||||
self.editAuthors,
|
||||
@@ -179,9 +183,12 @@ class GuiProjectEditMeta(QWidget):
|
||||
def __init__(self, theParent, theProject):
|
||||
QWidget.__init__(self, theParent)
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
|
||||
xInd = self.mainConf.pxInt(8)
|
||||
|
||||
# The Form
|
||||
self.mainForm = QGridLayout()
|
||||
self.setLayout(self.mainForm)
|
||||
@@ -189,17 +196,17 @@ class GuiProjectEditMeta(QWidget):
|
||||
self.headLabel = QLabel("<b>Project Details</b>")
|
||||
|
||||
self.nameLabel = QLabel("Working title:")
|
||||
self.nameLabel.setIndent(8)
|
||||
self.nameLabel.setIndent(xInd)
|
||||
self.nameValue = QLabel(self.theProject.projName)
|
||||
self.nameValue.setWordWrap(True)
|
||||
|
||||
self.pathLabel = QLabel("Project path:")
|
||||
self.pathLabel.setIndent(8)
|
||||
self.pathLabel.setIndent(xInd)
|
||||
self.pathValue = QLabel(self.theProject.projPath)
|
||||
self.pathValue.setWordWrap(True)
|
||||
|
||||
self.revLabel = QLabel("Revision count:")
|
||||
self.revLabel.setIndent(8)
|
||||
self.revLabel.setIndent(xInd)
|
||||
self.revValue = QLabel("{:n}".format(self.theProject.saveCount))
|
||||
|
||||
self.statsLabel = QLabel("<b>Project Stats</b>")
|
||||
@@ -207,19 +214,19 @@ class GuiProjectEditMeta(QWidget):
|
||||
nR, nD, nF = self.theProject.projTree.countTypes()
|
||||
|
||||
self.nRootLabel = QLabel("Root folders:")
|
||||
self.nRootLabel.setIndent(8)
|
||||
self.nRootLabel.setIndent(xInd)
|
||||
self.nRootValue = QLabel("{:n}".format(nR))
|
||||
|
||||
self.nDirLabel = QLabel("Folders:")
|
||||
self.nDirLabel.setIndent(8)
|
||||
self.nDirLabel.setIndent(xInd)
|
||||
self.nDirValue = QLabel("{:n}".format(nD))
|
||||
|
||||
self.nFileLabel = QLabel("Documents:")
|
||||
self.nFileLabel.setIndent(8)
|
||||
self.nFileLabel.setIndent(xInd)
|
||||
self.nFileValue = QLabel("{:n}".format(nF))
|
||||
|
||||
self.wordsLabel = QLabel("Word count:")
|
||||
self.wordsLabel.setIndent(8)
|
||||
self.wordsLabel.setIndent(xInd)
|
||||
self.wordsValue = QLabel("{:n}".format(self.theProject.currWCount))
|
||||
|
||||
self.mainForm.addWidget(self.headLabel, 0, 0, 1, 2, Qt.AlignTop)
|
||||
@@ -240,8 +247,8 @@ class GuiProjectEditMeta(QWidget):
|
||||
self.mainForm.addWidget(self.wordsLabel, 8, 0, 1, 1, Qt.AlignTop)
|
||||
self.mainForm.addWidget(self.wordsValue, 8, 1, 1, 1, Qt.AlignTop)
|
||||
|
||||
self.mainForm.setVerticalSpacing(6)
|
||||
self.mainForm.setHorizontalSpacing(12)
|
||||
self.mainForm.setVerticalSpacing(self.mainConf.pxInt(6))
|
||||
self.mainForm.setHorizontalSpacing(self.mainConf.pxInt(12))
|
||||
self.mainForm.setColumnStretch(0, 0)
|
||||
self.mainForm.setColumnStretch(1, 1)
|
||||
self.mainForm.setRowStretch(10, 1)
|
||||
@@ -255,8 +262,10 @@ class GuiProjectEditStatus(QWidget):
|
||||
def __init__(self, theParent, theProject, isStatus):
|
||||
QWidget.__init__(self, theParent)
|
||||
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
if isStatus:
|
||||
self.theStatus = self.theProject.statusItems
|
||||
else:
|
||||
@@ -267,6 +276,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.colChanged = False
|
||||
self.selColour = None
|
||||
|
||||
self.iPx = self.theTheme.textIconSize
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.mainBox = QHBoxLayout()
|
||||
self.mainForm = QVBoxLayout()
|
||||
@@ -285,8 +296,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.newButton = QPushButton("New")
|
||||
self.delButton = QPushButton("Delete")
|
||||
self.saveButton = QPushButton("Save")
|
||||
self.colPixmap = QPixmap(16,16)
|
||||
self.colPixmap.fill(QColor(120,120,120))
|
||||
self.colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
self.colPixmap.fill(QColor(120, 120, 120))
|
||||
self.colButton = QPushButton(QIcon(self.colPixmap),"Colour")
|
||||
self.colButton.setIconSize(self.colPixmap.rect().size())
|
||||
|
||||
@@ -339,7 +350,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
)
|
||||
if newCol:
|
||||
self.selColour = newCol
|
||||
colPixmap = QPixmap(16,16)
|
||||
colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
colPixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(colPixmap))
|
||||
self.colButton.setIconSize(colPixmap.rect().size())
|
||||
@@ -348,7 +359,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
def _newItem(self):
|
||||
logger.verbose("New item button clicked")
|
||||
newItem = self._addItem("New Item", (0, 0, 0), None, 0)
|
||||
newItem.setBackground(QBrush(QColor(0,255,0,80)))
|
||||
newItem.setBackground(QBrush(QColor(0, 255, 0, 80)))
|
||||
self.colChanged = True
|
||||
return
|
||||
|
||||
@@ -387,14 +398,14 @@ class GuiProjectEditStatus(QWidget):
|
||||
return
|
||||
|
||||
def _addItem(self, iName, iCol, oName, nUse):
|
||||
newIcon = QPixmap(16,16)
|
||||
newIcon = QPixmap(self.iPx, self.iPx)
|
||||
newIcon.fill(QColor(*iCol))
|
||||
newItem = QListWidgetItem()
|
||||
newItem.setText("%s [%d]" % (iName, nUse))
|
||||
newItem.setIcon(QIcon(newIcon))
|
||||
newItem.setData(Qt.UserRole, len(self.colData))
|
||||
self.listBox.addItem(newItem)
|
||||
self.colData.append((iName,iCol[0],iCol[1],iCol[2],oName))
|
||||
self.colData.append((iName, iCol[0], iCol[1], iCol[2], oName))
|
||||
self.colCounts.append(nUse)
|
||||
return newItem
|
||||
|
||||
@@ -404,8 +415,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
if selItem is not None:
|
||||
selIdx = selItem.data(Qt.UserRole)
|
||||
selVal = self.colData[selIdx]
|
||||
self.selColour = QColor(selVal[1],selVal[2],selVal[3])
|
||||
newIcon = QPixmap(16,16)
|
||||
self.selColour = QColor(selVal[1], selVal[2], selVal[3])
|
||||
newIcon = QPixmap(self.iPx, self.iPx)
|
||||
newIcon.fill(self.selColour)
|
||||
self.editName.setText(selVal[0])
|
||||
self.colButton.setIcon(QIcon(newIcon))
|
||||
|
||||
+269
-87
@@ -6,7 +6,8 @@
|
||||
Class holding the left side document tree view
|
||||
|
||||
File History:
|
||||
Created: 2018-09-29 [0.0.1]
|
||||
Created: 2018-09-29 [0.0.1] GuiProjectTree
|
||||
Created: 2020-06-04 [0.7.0] GuiProjectTreeMenu
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2020, Veronica Berglyd Olsen
|
||||
@@ -32,7 +33,7 @@ from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtGui import QFont, QColor, QIcon
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMessageBox,
|
||||
QHeaderView
|
||||
QHeaderView, QMenu, QAction
|
||||
)
|
||||
|
||||
from nw.core import NWDoc
|
||||
@@ -49,19 +50,20 @@ class GuiProjectTree(QTreeWidget):
|
||||
C_EXPORT = 2
|
||||
C_FLAGS = 3
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
def __init__(self, theParent):
|
||||
QTreeWidget.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiProjectTree ...")
|
||||
self.mainConf = nw.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theProject
|
||||
self.theProject = theParent.theProject
|
||||
|
||||
# Tree Settings
|
||||
self.theMap = None
|
||||
self.orphRoot = None
|
||||
|
||||
self.ctxMenu = GuiProjectTreeMenu(self)
|
||||
self.clearTree()
|
||||
|
||||
# Build GUI
|
||||
@@ -71,6 +73,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
self.setIndentation(iPx)
|
||||
self.setColumnCount(4)
|
||||
self.setHeaderLabels(["Label", "Words", "Inc", "Flags"])
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self._rightClickMenu)
|
||||
|
||||
treeHeadItem = self.headerItem()
|
||||
treeHeadItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
|
||||
@@ -100,8 +104,9 @@ class GuiProjectTree(QTreeWidget):
|
||||
# self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
|
||||
# Get user's column width preferences for NAME and COUNT
|
||||
if len(self.mainConf.treeColWidth) <= 4:
|
||||
for colN, colW in enumerate(self.mainConf.treeColWidth):
|
||||
treeColWidth = self.mainConf.getTreeColWidths()
|
||||
if len(treeColWidth) <= 4:
|
||||
for colN, colW in enumerate(treeColWidth):
|
||||
self.setColumnWidth(colN, colW)
|
||||
|
||||
# The last column should just auto-scale
|
||||
@@ -133,6 +138,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
meta data is set correctly to ensure a valid project tree.
|
||||
"""
|
||||
pHandle = self.getSelectedHandle()
|
||||
nHandle = None
|
||||
|
||||
if not self.theParent.hasProject:
|
||||
return False
|
||||
@@ -181,6 +187,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
# the new file will be a sibling
|
||||
pItem = self.theProject.projTree[pHandle]
|
||||
if pItem.itemType == nwItemType.FILE:
|
||||
nHandle = pHandle
|
||||
pHandle = pItem.parHandle
|
||||
|
||||
# If we again has no home, give up
|
||||
@@ -206,18 +213,18 @@ class GuiProjectTree(QTreeWidget):
|
||||
return False
|
||||
|
||||
# Add the new item to the tree
|
||||
self.revealTreeItem(tHandle)
|
||||
self.revealTreeItem(tHandle, nHandle)
|
||||
self.theParent.editItem()
|
||||
|
||||
return True
|
||||
|
||||
def revealTreeItem(self, tHandle):
|
||||
def revealTreeItem(self, tHandle, nHandle=None):
|
||||
"""Reveal a newly added project item in the project tree.
|
||||
"""
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
trItem = self._addTreeItem(nwItem)
|
||||
trItem = self._addTreeItem(nwItem, nHandle)
|
||||
pHandle = nwItem.parHandle
|
||||
if pHandle is not None and pHandle in self.theMap.keys():
|
||||
if pHandle is not None and pHandle in self.theMap:
|
||||
self.theMap[pHandle].setExpanded(True)
|
||||
self.clearSelection()
|
||||
trItem.setSelected(True)
|
||||
@@ -327,7 +334,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
return True
|
||||
|
||||
def deleteItem(self, tHandle=None, alreadyAsked=False):
|
||||
def deleteItem(self, tHandle=None, alreadyAsked=False, askForTrash=False):
|
||||
"""Delete items from the tree. Note that this does not delete
|
||||
the item from the item tree in the project object. However,
|
||||
since this is only meta data, there isn't really a need to do
|
||||
@@ -358,7 +365,6 @@ class GuiProjectTree(QTreeWidget):
|
||||
if pHandle is not None and pHandle == self.theProject.projTree.trashRoot():
|
||||
# If the file is in the trash folder already, as the
|
||||
# user if they want to permanently delete the file.
|
||||
|
||||
doPermanent = False
|
||||
if self.mainConf.showGUI and not alreadyAsked:
|
||||
msgBox = QMessageBox()
|
||||
@@ -387,17 +393,28 @@ class GuiProjectTree(QTreeWidget):
|
||||
else:
|
||||
# The file is not already in the trash folder, so we
|
||||
# move it there.
|
||||
doTrash = False
|
||||
if self.mainConf.showGUI and askForTrash:
|
||||
msgBox = QMessageBox()
|
||||
msgRes = msgBox.question(
|
||||
self, "Delete File", "Move file '%s' to Trash?" % nwItemS.itemName
|
||||
)
|
||||
if msgRes == QMessageBox.Yes:
|
||||
doTrash = True
|
||||
else:
|
||||
doTrash = True
|
||||
|
||||
if pHandle is None:
|
||||
logger.warning("File has no parent item")
|
||||
if doTrash:
|
||||
if pHandle is None:
|
||||
logger.warning("File has no parent item")
|
||||
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
trItemC = trItemP.takeChild(tIndex)
|
||||
trItemT.addChild(trItemC)
|
||||
nwItemS.setParent(self.theProject.projTree.trashRoot())
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
trItemC = trItemP.takeChild(tIndex)
|
||||
trItemT.addChild(trItemC)
|
||||
nwItemS.setParent(self.theProject.projTree.trashRoot())
|
||||
|
||||
self.theProject.setProjectChanged(True)
|
||||
self.theParent.theIndex.deleteHandle(tHandle)
|
||||
self.theProject.setProjectChanged(True)
|
||||
self.theParent.theIndex.deleteHandle(tHandle)
|
||||
|
||||
elif nwItemS.itemType == nwItemType.FOLDER:
|
||||
logger.debug("User requested folder %s deleted" % tHandle)
|
||||
@@ -548,6 +565,89 @@ class GuiProjectTree(QTreeWidget):
|
||||
return True
|
||||
return False
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _rightClickMenu(self, clickPos):
|
||||
"""The user right clicked an element in the project tree, so we
|
||||
open a context menu in-place.
|
||||
"""
|
||||
selItem = self.itemAt(clickPos)
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
self.setSelectedHandle(tHandle) # Just to be safe
|
||||
if self.ctxMenu.filterActions(tItem):
|
||||
# Only open menu if any actions remain after filter
|
||||
self.ctxMenu.exec_(self.viewport().mapToGlobal(clickPos))
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
|
||||
def mousePressEvent(self, theEvent):
|
||||
"""Overload mousePressEvent to clear selection if clicking the
|
||||
mouse in a blank area of the tree view.
|
||||
"""
|
||||
QTreeWidget.mousePressEvent(self, theEvent)
|
||||
selItem = self.indexAt(theEvent.pos())
|
||||
if not selItem.isValid():
|
||||
self.clearSelection()
|
||||
return
|
||||
|
||||
def dropEvent(self, theEvent):
|
||||
"""Overload the drop of dragged item event to check whether the
|
||||
drop is allowed or not. Disallowed drops are cancelled.
|
||||
"""
|
||||
sHandle = self.getSelectedHandle()
|
||||
if sHandle is None:
|
||||
logger.error("No handle selected")
|
||||
return
|
||||
|
||||
dIndex = self.indexAt(theEvent.pos())
|
||||
if not dIndex.isValid():
|
||||
logger.error("Invalid drop index")
|
||||
return
|
||||
|
||||
dItem = self.itemFromIndex(dIndex)
|
||||
dHandle = dItem.data(self.C_NAME, Qt.UserRole)
|
||||
snItem = self.theProject.projTree[sHandle]
|
||||
dnItem = self.theProject.projTree[dHandle]
|
||||
if dnItem is None:
|
||||
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
|
||||
return
|
||||
|
||||
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
|
||||
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)
|
||||
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.debug("Drag'n'drop of item %s not accepted" % sHandle)
|
||||
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
@@ -572,7 +672,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
self._scanChildren(theList, theItem.child(i), i)
|
||||
return theList
|
||||
|
||||
def _addTreeItem(self, nwItem):
|
||||
def _addTreeItem(self, nwItem, nHandle=None):
|
||||
"""Create a QTreeWidgetItem from an NWItem and add it to the
|
||||
project tree.
|
||||
"""
|
||||
@@ -604,7 +704,16 @@ class GuiProjectTree(QTreeWidget):
|
||||
self._addOrphanedRoot()
|
||||
self.orphRoot.addChild(newItem)
|
||||
else:
|
||||
self.theMap[pHandle].addChild(newItem)
|
||||
byIndex = -1
|
||||
if nHandle is not None and nHandle in self.theMap:
|
||||
try:
|
||||
byIndex = self.theMap[pHandle].indexOfChild(self.theMap[nHandle])
|
||||
except:
|
||||
logger.error("Failed to get index of item with handle %s" % nHandle)
|
||||
if byIndex >= 0:
|
||||
self.theMap[pHandle].insertChild(byIndex+1, newItem)
|
||||
else:
|
||||
self.theMap[pHandle].addChild(newItem)
|
||||
self.propagateCount(tHandle, nwItem.wordCount)
|
||||
|
||||
self.setTreeItemValues(tHandle)
|
||||
@@ -705,69 +814,142 @@ class GuiProjectTree(QTreeWidget):
|
||||
self.theProject.setProjectChanged(True)
|
||||
return
|
||||
|
||||
##
|
||||
# Event Overloading
|
||||
##
|
||||
|
||||
def mousePressEvent(self, theEvent):
|
||||
"""Overload mousePressEvent to clear selection if clicking the
|
||||
mouse in a blank area of the tree view.
|
||||
"""
|
||||
QTreeWidget.mousePressEvent(self, theEvent)
|
||||
selItem = self.indexAt(theEvent.pos())
|
||||
if not selItem.isValid():
|
||||
self.clearSelection()
|
||||
return
|
||||
|
||||
def dropEvent(self, theEvent):
|
||||
"""Overload the drop of dragged item event to check whether the
|
||||
drop is allowed or not. Disallowed drops are cancelled.
|
||||
"""
|
||||
sHandle = self.getSelectedHandle()
|
||||
if sHandle is None:
|
||||
logger.error("No handle selected")
|
||||
return
|
||||
|
||||
dIndex = self.indexAt(theEvent.pos())
|
||||
if not dIndex.isValid():
|
||||
logger.error("Invalid drop index")
|
||||
return
|
||||
|
||||
dItem = self.itemFromIndex(dIndex)
|
||||
dHandle = dItem.data(self.C_NAME, Qt.UserRole)
|
||||
snItem = self.theProject.projTree[sHandle]
|
||||
dnItem = self.theProject.projTree[dHandle]
|
||||
if dnItem is None:
|
||||
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
|
||||
return
|
||||
|
||||
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
|
||||
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)
|
||||
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.debug("Drag'n'drop of item %s not accepted" % sHandle)
|
||||
self.makeAlert("The item cannot be moved to that location.", nwAlert.ERROR)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiProjectTree
|
||||
|
||||
class GuiProjectTreeMenu(QMenu):
|
||||
|
||||
def __init__(self, theTree):
|
||||
QMenu.__init__(self, theTree)
|
||||
|
||||
self.theTree = theTree
|
||||
self.theItem = None
|
||||
|
||||
self.openItem = QAction("Open Document", self)
|
||||
self.openItem.triggered.connect(self._doOpenItem)
|
||||
self.addAction(self.openItem)
|
||||
|
||||
self.viewItem = QAction("View Document", self)
|
||||
self.viewItem.triggered.connect(self._doViewItem)
|
||||
self.addAction(self.viewItem)
|
||||
|
||||
self.editItem = QAction("Edit Project Item", self)
|
||||
self.editItem.triggered.connect(self._doEditItem)
|
||||
self.addAction(self.editItem)
|
||||
|
||||
self.toggleExp = QAction("Toggle Included Flag", self)
|
||||
self.toggleExp.triggered.connect(self._doToggleExported)
|
||||
self.addAction(self.toggleExp)
|
||||
|
||||
self.newFile = QAction("New File", self)
|
||||
self.newFile.triggered.connect(self._doMakeFile)
|
||||
self.addAction(self.newFile)
|
||||
|
||||
self.newFolder = QAction("New Folder", self)
|
||||
self.newFolder.triggered.connect(self._doMakeFolder)
|
||||
self.addAction(self.newFolder)
|
||||
|
||||
self.deleteItem = QAction("Delete Item", self)
|
||||
self.deleteItem.triggered.connect(self._doDeleteItem)
|
||||
self.addAction(self.deleteItem)
|
||||
|
||||
self.emptyTrash = QAction("Empty Trash", self)
|
||||
self.emptyTrash.triggered.connect(self._doEmptyTrash)
|
||||
self.addAction(self.emptyTrash)
|
||||
|
||||
return
|
||||
|
||||
def filterActions(self, theItem):
|
||||
"""Update item settings from the nwItem.
|
||||
"""
|
||||
self.theItem = theItem
|
||||
trashHandle = self.theTree.theProject.projTree.trashRoot()
|
||||
|
||||
if theItem is None:
|
||||
return False
|
||||
|
||||
inTrash = theItem.parHandle == trashHandle
|
||||
isTrash = theItem.itemHandle == trashHandle
|
||||
isFile = theItem.itemType == nwItemType.FILE
|
||||
isOrph = isFile and theItem.parHandle is None
|
||||
|
||||
showOpen = isFile
|
||||
showView = isFile
|
||||
showEdit = not isTrash and not isOrph
|
||||
showExport = isFile and not inTrash and not isOrph
|
||||
showNewFile = not isTrash and not inTrash and not isOrph
|
||||
showNewFolder = not isTrash and not inTrash and not isOrph
|
||||
showDelete = not isTrash
|
||||
showEmpty = isTrash
|
||||
|
||||
self.openItem.setVisible(showOpen)
|
||||
self.viewItem.setVisible(showView)
|
||||
self.editItem.setVisible(showEdit)
|
||||
self.toggleExp.setVisible(showExport)
|
||||
self.newFile.setVisible(showNewFile)
|
||||
self.newFolder.setVisible(showNewFolder)
|
||||
self.deleteItem.setVisible(showDelete)
|
||||
self.emptyTrash.setVisible(showEmpty)
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _doOpenItem(self):
|
||||
"""Forward the open document call to the main GUI window.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theTree.theParent.openDocument(self.theItem.itemHandle)
|
||||
return
|
||||
|
||||
def _doViewItem(self):
|
||||
"""Forward the view document call to the main GUI window.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theTree.theParent.viewDocument(self.theItem.itemHandle)
|
||||
return
|
||||
|
||||
def _doEditItem(self):
|
||||
"""Forward the edit item call to the main GUI window.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theTree.theParent.editItem()
|
||||
return
|
||||
|
||||
def _doMakeFile(self):
|
||||
"""Forward the new file call to the project tree.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theTree.newTreeItem(nwItemType.FILE, None)
|
||||
return
|
||||
|
||||
def _doMakeFolder(self):
|
||||
"""Forward the new folder call to the project tree.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theTree.newTreeItem(nwItemType.FOLDER, None)
|
||||
return
|
||||
|
||||
def _doToggleExported(self):
|
||||
"""Flip the isExported flag of the current item.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theItem.setExported(not self.theItem.isExported)
|
||||
self.theTree.setTreeItemValues(self.theItem.itemHandle)
|
||||
return
|
||||
|
||||
def _doDeleteItem(self):
|
||||
"""Forward the delete item call to the project tree.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theTree.deleteItem(askForTrash=True)
|
||||
return
|
||||
|
||||
def _doEmptyTrash(self):
|
||||
"""Forward the delete item call to the project tree.
|
||||
"""
|
||||
self.theTree.emptyTrash()
|
||||
return
|
||||
|
||||
# END Class GuiProjectTreeMenu
|
||||
|
||||
+19
-25
@@ -61,32 +61,26 @@ class GuiSessionLogView(QDialog):
|
||||
self.bottomBox = QHBoxLayout()
|
||||
|
||||
self.setWindowTitle("Session Log")
|
||||
self.setMinimumWidth(420)
|
||||
self.setMinimumHeight(400)
|
||||
self.setMinimumWidth(self.mainConf.pxInt(420))
|
||||
self.setMinimumHeight(self.mainConf.pxInt(400))
|
||||
|
||||
widthCol0 = self.optState.validIntRange(
|
||||
self.optState.getInt("GuiSession", "widthCol0", 180), 30, 999, 180
|
||||
)
|
||||
widthCol1 = self.optState.validIntRange(
|
||||
self.optState.getInt("GuiSession", "widthCol1", 80), 30, 999, 80
|
||||
)
|
||||
widthCol2 = self.optState.validIntRange(
|
||||
self.optState.getInt("GuiSession", "widthCol2", 80), 30, 999, 80
|
||||
)
|
||||
wCol0 = self.mainConf.pxInt(self.optState.getInt("GuiSession", "widthCol0", 180))
|
||||
wCol1 = self.mainConf.pxInt(self.optState.getInt("GuiSession", "widthCol1", 80))
|
||||
wCol2 = self.mainConf.pxInt(self.optState.getInt("GuiSession", "widthCol2", 80))
|
||||
|
||||
self.listBox = QTreeWidget()
|
||||
self.listBox.setHeaderLabels(["Session Start","Length","Words",""])
|
||||
self.listBox.setIndentation(0)
|
||||
self.listBox.setColumnWidth(0,widthCol0)
|
||||
self.listBox.setColumnWidth(1,widthCol1)
|
||||
self.listBox.setColumnWidth(2,widthCol2)
|
||||
self.listBox.setColumnWidth(3,0)
|
||||
self.listBox.setColumnWidth(0, wCol0)
|
||||
self.listBox.setColumnWidth(1, wCol1)
|
||||
self.listBox.setColumnWidth(2, wCol2)
|
||||
self.listBox.setColumnWidth(3, 0)
|
||||
|
||||
hHeader = self.listBox.headerItem()
|
||||
hHeader.setTextAlignment(1,Qt.AlignRight)
|
||||
hHeader.setTextAlignment(2,Qt.AlignRight)
|
||||
|
||||
self.monoFont = QFont("Monospace",10)
|
||||
self.monoFont = QFont("Monospace", 10)
|
||||
|
||||
sortValid = (Qt.AscendingOrder, Qt.DescendingOrder)
|
||||
sortCol = self.optState.validIntRange(
|
||||
@@ -226,20 +220,20 @@ class GuiSessionLogView(QDialog):
|
||||
|
||||
def _doClose(self):
|
||||
|
||||
widthCol0 = self.listBox.columnWidth(0)
|
||||
widthCol1 = self.listBox.columnWidth(1)
|
||||
widthCol2 = self.listBox.columnWidth(2)
|
||||
widthCol0 = self.mainConf.rpxInt(self.listBox.columnWidth(0))
|
||||
widthCol1 = self.mainConf.rpxInt(self.listBox.columnWidth(1))
|
||||
widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2))
|
||||
sortCol = self.listBox.sortColumn()
|
||||
sortOrder = self.listBox.header().sortIndicatorOrder()
|
||||
hideZeros = self.hideZeros.isChecked()
|
||||
hideNegative = self.hideNegative.isChecked()
|
||||
|
||||
self.optState.setValue("GuiSession", "widthCol0", widthCol0)
|
||||
self.optState.setValue("GuiSession", "widthCol1", widthCol1)
|
||||
self.optState.setValue("GuiSession", "widthCol2", widthCol2)
|
||||
self.optState.setValue("GuiSession", "sortCol", sortCol)
|
||||
self.optState.setValue("GuiSession", "sortOrder", sortOrder)
|
||||
self.optState.setValue("GuiSession", "hideZeros", hideZeros)
|
||||
self.optState.setValue("GuiSession", "widthCol0", widthCol0)
|
||||
self.optState.setValue("GuiSession", "widthCol1", widthCol1)
|
||||
self.optState.setValue("GuiSession", "widthCol2", widthCol2)
|
||||
self.optState.setValue("GuiSession", "sortCol", sortCol)
|
||||
self.optState.setValue("GuiSession", "sortOrder", sortOrder)
|
||||
self.optState.setValue("GuiSession", "hideZeros", hideZeros)
|
||||
self.optState.setValue("GuiSession", "hideNegative", hideNegative)
|
||||
|
||||
self.optState.saveSettings()
|
||||
|
||||
+6
-4
@@ -66,12 +66,14 @@ class GuiMainStatus(QStatusBar):
|
||||
# Permanent Widgets
|
||||
# =================
|
||||
|
||||
xM = self.mainConf.pxInt(8)
|
||||
|
||||
## The Spell Checker Language
|
||||
self.langIcon = QLabel("")
|
||||
self.langText = QLabel("None")
|
||||
self.langIcon.setPixmap(self.theTheme.getPixmap("status_lang", (iPx, iPx)))
|
||||
self.langIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.langText.setContentsMargins(0, 0, 8, 0)
|
||||
self.langText.setContentsMargins(0, 0, xM, 0)
|
||||
self.addPermanentWidget(self.langIcon)
|
||||
self.addPermanentWidget(self.langText)
|
||||
|
||||
@@ -79,7 +81,7 @@ class GuiMainStatus(QStatusBar):
|
||||
self.docIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self)
|
||||
self.docText = QLabel("Editor")
|
||||
self.docIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.docText.setContentsMargins(0, 0, 8, 0)
|
||||
self.docText.setContentsMargins(0, 0, xM, 0)
|
||||
self.addPermanentWidget(self.docIcon)
|
||||
self.addPermanentWidget(self.docText)
|
||||
|
||||
@@ -87,7 +89,7 @@ class GuiMainStatus(QStatusBar):
|
||||
self.projIcon = StatusLED(colNone, colTrue, colFalse, iPx, iPx, self)
|
||||
self.projText = QLabel("Project")
|
||||
self.projIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.projText.setContentsMargins(0, 0, 8, 0)
|
||||
self.projText.setContentsMargins(0, 0, xM, 0)
|
||||
self.addPermanentWidget(self.projIcon)
|
||||
self.addPermanentWidget(self.projText)
|
||||
|
||||
@@ -96,7 +98,7 @@ class GuiMainStatus(QStatusBar):
|
||||
self.statsText = QLabel("")
|
||||
self.statsIcon.setPixmap(self.theTheme.getPixmap("status_stats", (iPx, iPx)))
|
||||
self.statsIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.statsText.setContentsMargins(0, 0, 8, 0)
|
||||
self.statsText.setContentsMargins(0, 0, xM, 0)
|
||||
self.addPermanentWidget(self.statsIcon)
|
||||
self.addPermanentWidget(self.statsText)
|
||||
|
||||
|
||||
+10
-2
@@ -132,20 +132,28 @@ class GuiTheme:
|
||||
self.loadDecoration = self.theIcons.loadDecoration
|
||||
|
||||
# Extract Other Info
|
||||
self.guiDPI = qApp.primaryScreen().physicalDotsPerInch()
|
||||
self.guiFont = qApp.font()
|
||||
self.guiDPI = qApp.primaryScreen().logicalDotsPerInchX()
|
||||
self.guiScale = qApp.primaryScreen().logicalDotsPerInchX()/96.0
|
||||
self.mainConf.guiScale = self.guiScale
|
||||
logger.verbose("GUI DPI: %.1f" % self.guiDPI)
|
||||
logger.verbose("GUI Scale: %.2f" % self.guiScale)
|
||||
|
||||
self.guiFont = qApp.font()
|
||||
qMetric = QFontMetrics(self.guiFont)
|
||||
self.fontPointSize = self.guiFont.pointSizeF()
|
||||
self.fontPixelSize = int(round(qMetric.height()))
|
||||
self.baseIconSize = int(round(qMetric.ascent()))
|
||||
self.textIconSize = int(round(qMetric.ascent() + qMetric.leading()))
|
||||
self.textNHeight = qMetric.boundingRect("N").height()
|
||||
self.textNWidth = qMetric.boundingRect("N").width()
|
||||
|
||||
logger.verbose("GUI Font Family: %s" % self.guiFont.family())
|
||||
logger.verbose("GUI Font Point Size: %.2f" % self.fontPointSize)
|
||||
logger.verbose("GUI Font Pixel Size: %d" % self.fontPixelSize)
|
||||
logger.verbose("GUI Base Icon Size: %d" % self.baseIconSize)
|
||||
logger.verbose("GUI Text Icon Size: %d" % self.textIconSize)
|
||||
logger.verbose("Text 'N' Height: %d" % self.textNHeight)
|
||||
logger.verbose("Text 'N' Width: %d" % self.textNWidth)
|
||||
|
||||
return
|
||||
|
||||
|
||||
+35
-28
@@ -35,15 +35,15 @@ from time import time
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QShortcut,
|
||||
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
||||
QMessageBox, QDialog, QTabWidget
|
||||
)
|
||||
|
||||
from nw.gui import (
|
||||
GuiMainMenu, GuiMainStatus, GuiTheme, GuiProjectTree, GuiDocEditor,
|
||||
GuiDocViewer, GuiItemDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails,
|
||||
GuiPreferences, GuiProjectSettings, GuiItemEditor, GuiProjectOutline,
|
||||
GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad, GuiBuildNovel
|
||||
GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails,
|
||||
GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus,
|
||||
GuiNoticeBar, GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad,
|
||||
GuiProjectSettings, GuiProjectTree, GuiSearchBar, GuiSessionLogView, GuiTheme
|
||||
)
|
||||
from nw.core import NWProject, NWDoc, NWIndex
|
||||
from nw.constants import nwFiles, nwItemType, nwAlert
|
||||
@@ -81,7 +81,7 @@ class GuiMain(QMainWindow):
|
||||
self.isZenMode = False
|
||||
|
||||
# Prepare main window
|
||||
self.resize(*self.mainConf.winGeometry)
|
||||
self.resize(*self.mainConf.getWinSize())
|
||||
self._setWindowTitle()
|
||||
self.setWindowIcon(QIcon(self.mainConf.appIcon))
|
||||
|
||||
@@ -91,40 +91,41 @@ class GuiMain(QMainWindow):
|
||||
# Main GUI Elements
|
||||
self.statusBar = GuiMainStatus(self)
|
||||
self.noticeBar = GuiNoticeBar(self)
|
||||
self.treeView = GuiProjectTree(self, self.theProject)
|
||||
self.docEditor = GuiDocEditor(self, self.theProject)
|
||||
self.docViewer = GuiDocViewer(self, self.theProject)
|
||||
self.viewMeta = GuiDocViewDetails(self, self.theProject)
|
||||
self.treeView = GuiProjectTree(self)
|
||||
self.docEditor = GuiDocEditor(self)
|
||||
self.docViewer = GuiDocViewer(self)
|
||||
self.viewMeta = GuiDocViewDetails(self)
|
||||
self.searchBar = GuiSearchBar(self)
|
||||
self.treeMeta = GuiItemDetails(self, self.theProject)
|
||||
self.projView = GuiProjectOutline(self, self.theProject)
|
||||
self.mainMenu = GuiMainMenu(self, self.theProject)
|
||||
self.treeMeta = GuiItemDetails(self)
|
||||
self.projView = GuiOutline(self)
|
||||
self.projMeta = GuiOutlineDetails(self)
|
||||
self.mainMenu = GuiMainMenu(self)
|
||||
|
||||
# Minor Gui Elements
|
||||
self.statusIcons = []
|
||||
self.importIcons = []
|
||||
|
||||
# Assemble Main Window
|
||||
self.treePane = QFrame()
|
||||
self.treePane = QWidget()
|
||||
self.treeBox = QVBoxLayout()
|
||||
self.treeBox.setContentsMargins(0,0,0,0)
|
||||
self.treeBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.treeBox.addWidget(self.treeView)
|
||||
self.treeBox.addWidget(self.treeMeta)
|
||||
self.treePane.setLayout(self.treeBox)
|
||||
|
||||
self.editPane = QFrame()
|
||||
self.editPane = QWidget()
|
||||
self.docEdit = QVBoxLayout()
|
||||
self.docEdit.setContentsMargins(0,0,0,0)
|
||||
self.docEdit.setSpacing(2)
|
||||
self.docEdit.setContentsMargins(0, 0, 0, 0)
|
||||
self.docEdit.setSpacing(self.mainConf.pxInt(2))
|
||||
self.docEdit.addWidget(self.searchBar)
|
||||
self.docEdit.addWidget(self.noticeBar)
|
||||
self.docEdit.addWidget(self.docEditor)
|
||||
self.editPane.setLayout(self.docEdit)
|
||||
|
||||
self.viewPane = QFrame()
|
||||
self.viewPane = QWidget()
|
||||
self.docView = QVBoxLayout()
|
||||
self.docView.setContentsMargins(0,0,0,0)
|
||||
self.docView.setSpacing(2)
|
||||
self.docView.setContentsMargins(0, 0, 0, 0)
|
||||
self.docView.setSpacing(self.mainConf.pxInt(2))
|
||||
self.docView.addWidget(self.docViewer)
|
||||
self.docView.addWidget(self.viewMeta)
|
||||
self.docView.setStretch(0, 1)
|
||||
@@ -137,6 +138,8 @@ class GuiMain(QMainWindow):
|
||||
|
||||
self.splitOutline = QSplitter(Qt.Vertical)
|
||||
self.splitOutline.addWidget(self.projView)
|
||||
self.splitOutline.addWidget(self.projMeta)
|
||||
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
|
||||
|
||||
self.tabWidget = QTabWidget()
|
||||
self.tabWidget.setTabPosition(QTabWidget.East)
|
||||
@@ -145,12 +148,13 @@ class GuiMain(QMainWindow):
|
||||
self.tabWidget.addTab(self.splitOutline, "Outline")
|
||||
self.tabWidget.currentChanged.connect(self._mainTabChanged)
|
||||
|
||||
xCM = self.mainConf.pxInt(4)
|
||||
self.splitMain = QSplitter(Qt.Horizontal)
|
||||
self.splitMain.setContentsMargins(4,4,4,4)
|
||||
self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM)
|
||||
self.splitMain.setOpaqueResize(False)
|
||||
self.splitMain.addWidget(self.treePane)
|
||||
self.splitMain.addWidget(self.tabWidget)
|
||||
self.splitMain.setSizes(self.mainConf.mainPanePos)
|
||||
self.splitMain.setSizes(self.mainConf.getMainPanePos())
|
||||
|
||||
self.setCentralWidget(self.splitMain)
|
||||
|
||||
@@ -512,9 +516,9 @@ class GuiMain(QMainWindow):
|
||||
if not self.viewPane.isVisible():
|
||||
bPos = self.splitMain.sizes()
|
||||
self.viewPane.setVisible(True)
|
||||
vPos = [0,0]
|
||||
vPos = [0, 0]
|
||||
vPos[0] = int(bPos[1]/2)
|
||||
vPos[1] = bPos[1]-vPos[0]
|
||||
vPos[1] = bPos[1] - vPos[0]
|
||||
self.splitView.setSizes(vPos)
|
||||
self.docViewer.navigateTo(navLink)
|
||||
|
||||
@@ -849,6 +853,7 @@ class GuiMain(QMainWindow):
|
||||
if not self.isZenMode:
|
||||
self.mainConf.setMainPanePos(self.splitMain.sizes())
|
||||
self.mainConf.setDocPanePos(self.splitView.sizes())
|
||||
self.mainConf.setOutlinePanePos(self.splitOutline.sizes())
|
||||
self.mainConf.saveConfig()
|
||||
self.reportConfErr()
|
||||
|
||||
@@ -875,7 +880,7 @@ class GuiMain(QMainWindow):
|
||||
self.theProject.setLastViewed(None)
|
||||
bPos = self.splitMain.sizes()
|
||||
self.viewPane.setVisible(False)
|
||||
vPos = [bPos[1],0]
|
||||
vPos = [bPos[1], 0]
|
||||
self.splitView.setSizes(vPos)
|
||||
return not self.viewPane.isVisible()
|
||||
|
||||
@@ -986,16 +991,18 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def _makeStatusIcons(self):
|
||||
self.statusIcons = {}
|
||||
iPx = self.mainConf.pxInt(32)
|
||||
for sLabel, sCol, _ in self.theProject.statusItems:
|
||||
theIcon = QPixmap(32,32)
|
||||
theIcon = QPixmap(iPx, iPx)
|
||||
theIcon.fill(QColor(*sCol))
|
||||
self.statusIcons[sLabel] = QIcon(theIcon)
|
||||
return
|
||||
|
||||
def _makeImportIcons(self):
|
||||
self.importIcons = {}
|
||||
iPx = self.mainConf.pxInt(32)
|
||||
for sLabel, sCol, _ in self.theProject.importItems:
|
||||
theIcon = QPixmap(32,32)
|
||||
theIcon = QPixmap(iPx, iPx)
|
||||
theIcon.fill(QColor(*sCol))
|
||||
self.importIcons[sLabel] = QIcon(theIcon)
|
||||
return
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.7.0" hexVersion="0x000700f0" fileVersion="1.1" saveCount="228" autoCount="31" timeStamp="2020-06-01 12:54:18" editTime="540">
|
||||
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 20:53:20">
|
||||
<project>
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
<author>Jay Doh</author>
|
||||
<backup>False</backup>
|
||||
<saveCount>273</saveCount>
|
||||
<autoCount>40</autoCount>
|
||||
<editTime>4125</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
<spellCheck>True</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>636b6aa9b697b</lastEdited>
|
||||
@@ -24,9 +27,6 @@
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>Scene %ch%.%sc%: %title%</scene>
|
||||
<section></section>
|
||||
<withSynopsis>True</withSynopsis>
|
||||
<withComments>True</withComments>
|
||||
<withKeywords>False</withKeywords>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
|
||||
@@ -6,7 +6,7 @@ with open("README.md", "r") as inFile:
|
||||
|
||||
setuptools.setup(
|
||||
name = "novelWriter",
|
||||
version = "0.7.1",
|
||||
version = "0.8rc1",
|
||||
author = "Veronica Berglyd Olsen",
|
||||
author_email = "code@vkbo.net",
|
||||
description = "A markdown-like document editor for writing novels",
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="2" autoCount="0" timeStamp="2020-05-29 23:05:28">
|
||||
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:05:07">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<backup>True</backup>
|
||||
<saveCount>2</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
<spellCheck>False</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
@@ -18,9 +21,6 @@
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
<withSynopsis>False</withSynopsis>
|
||||
<withComments>False</withComments>
|
||||
<withKeywords>False</withKeywords>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="4" autoCount="1" timeStamp="2020-05-30 15:31:30" editTime="12">
|
||||
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:06:00">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<backup>True</backup>
|
||||
<saveCount>5</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>11</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
<spellCheck>True</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>31489056e0916</lastEdited>
|
||||
@@ -18,9 +21,6 @@
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
<withSynopsis>False</withSynopsis>
|
||||
<withComments>False</withComments>
|
||||
<withKeywords>False</withKeywords>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="2" autoCount="0" timeStamp="2020-05-29 23:07:05">
|
||||
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:07:03">
|
||||
<project>
|
||||
<name>Project Name</name>
|
||||
<title>Project Title</title>
|
||||
<author>Jane Doe</author>
|
||||
<author>John Doh</author>
|
||||
<backup>True</backup>
|
||||
<saveCount>2</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>1</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
<spellCheck>False</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
@@ -22,9 +25,6 @@
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
<withSynopsis>False</withSynopsis>
|
||||
<withComments>False</withComments>
|
||||
<withKeywords>False</withKeywords>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="2" autoCount="0" timeStamp="2020-05-29 23:07:37">
|
||||
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:07:51">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<backup>True</backup>
|
||||
<saveCount>2</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
<spellCheck>False</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
@@ -18,9 +21,6 @@
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
<withSynopsis>False</withSynopsis>
|
||||
<withComments>False</withComments>
|
||||
<withKeywords>False</withKeywords>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[Main]
|
||||
timestamp = 2020-05-21 14:52:36
|
||||
timestamp = 2020-06-03 20:42:03
|
||||
theme = default
|
||||
syntax = default_light
|
||||
icons = typicons_grey_light
|
||||
@@ -13,6 +13,7 @@ treecols = 120, 30, 50
|
||||
projcols = 140, 55, 140
|
||||
mainpane = 300, 800
|
||||
docpane = 400, 400
|
||||
outlinepane = 500, 150
|
||||
fullscreen = False
|
||||
|
||||
[Project]
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="1" autoCount="0" timeStamp="2020-05-29 23:03:58">
|
||||
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 20:58:52">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<backup>True</backup>
|
||||
<saveCount>1</saveCount>
|
||||
<autoCount>0</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
<spellCheck>False</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
@@ -18,9 +21,6 @@
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
<withSynopsis>False</withSynopsis>
|
||||
<withComments>False</withComments>
|
||||
<withKeywords>False</withKeywords>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="4" autoCount="0" timeStamp="2020-05-29 23:04:31">
|
||||
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:03:18">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<backup>True</backup>
|
||||
<saveCount>4</saveCount>
|
||||
<autoCount>0</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
<spellCheck>False</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
@@ -18,9 +21,6 @@
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
<withSynopsis>False</withSynopsis>
|
||||
<withComments>False</withComments>
|
||||
<withKeywords>False</withKeywords>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="0.7.0rc1" hexVersion="0x000700c1" fileVersion="1.1" saveCount="5" autoCount="0" timeStamp="2020-05-29 23:04:50">
|
||||
<novelWriterXML appVersion="0.8.0rc1" hexVersion="0x000800c1" fileVersion="1.1" timeStamp="2020-06-05 21:04:10">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<backup>True</backup>
|
||||
<saveCount>5</saveCount>
|
||||
<autoCount>0</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
<spellCheck>False</spellCheck>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>None</lastEdited>
|
||||
@@ -18,9 +21,6 @@
|
||||
<unnumbered>%title%</unnumbered>
|
||||
<scene>* * *</scene>
|
||||
<section></section>
|
||||
<withSynopsis>False</withSynopsis>
|
||||
<withComments>False</withComments>
|
||||
<withKeywords>False</withKeywords>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry blue="100" green="100" red="100">New</entry>
|
||||
|
||||
+4
-4
@@ -45,7 +45,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
|
||||
|
||||
# Check the files
|
||||
projFile = path.join(nwTempGUI,"nwProject.nwx")
|
||||
assert cmpFiles(projFile, path.join(nwRef,"gui","0_nwProject.nwx"), [2])
|
||||
assert cmpFiles(projFile, path.join(nwRef,"gui","0_nwProject.nwx"), [2, 6, 7, 8])
|
||||
qtbot.wait(stepDelay)
|
||||
|
||||
# qtbot.stopForInteraction()
|
||||
@@ -246,7 +246,7 @@ def testMainWindows(qtbot, nwTempGUI, nwRef, nwTemp):
|
||||
|
||||
# Check the files
|
||||
refFile = path.join(nwTempGUI, "nwProject.nwx")
|
||||
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_nwProject.nwx"), [2])
|
||||
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_nwProject.nwx"), [2, 6, 7, 8])
|
||||
refFile = path.join(nwTempGUI, "content", "0e17daca5f3e1.nwd")
|
||||
assert cmpFiles(refFile, path.join(nwRef, "gui", "1_0e17daca5f3e1.nwd"))
|
||||
refFile = path.join(nwTempGUI, "content", "98010bd9270f9.nwd")
|
||||
@@ -342,7 +342,7 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef, nwTemp):
|
||||
|
||||
# Check the files
|
||||
projFile = path.join(nwTempGUI,"nwProject.nwx")
|
||||
assert cmpFiles(projFile, path.join(nwRef, "gui", "2_nwProject.nwx"), [2])
|
||||
assert cmpFiles(projFile, path.join(nwRef, "gui", "2_nwProject.nwx"), [2, 8, 9, 10])
|
||||
|
||||
nwGUI.closeMain()
|
||||
# qtbot.stopForInteraction()
|
||||
@@ -391,7 +391,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef, nwTemp):
|
||||
|
||||
# Check the files
|
||||
projFile = path.join(nwTempGUI,"nwProject.nwx")
|
||||
assert cmpFiles(projFile, path.join(nwRef, "gui", "3_nwProject.nwx"), [2])
|
||||
assert cmpFiles(projFile, path.join(nwRef, "gui", "3_nwProject.nwx"), [2, 6, 7, 8])
|
||||
|
||||
nwGUI.closeMain()
|
||||
# qtbot.stopForInteraction()
|
||||
|
||||
@@ -30,7 +30,7 @@ def testProjectNew(nwTempProj,nwRef,nwTemp):
|
||||
assert theProject.setProjectPath(nwTempProj)
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
assert cmpFiles(projFile, refFile, [2])
|
||||
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectOpen(nwTempProj):
|
||||
@@ -43,7 +43,7 @@ def testProjectSave(nwTempProj,nwRef):
|
||||
refFile = path.join(nwRef,"proj","1_nwProject.nwx")
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
assert cmpFiles(projFile, refFile, [2])
|
||||
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
|
||||
assert not theProject.projChanged
|
||||
|
||||
@pytest.mark.project
|
||||
@@ -55,7 +55,7 @@ def testProjectOpenTwice(nwTempProj,nwRef):
|
||||
assert theProject.openProject(projFile, overrideLock=True)
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
assert cmpFiles(projFile, refFile, [2])
|
||||
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
|
||||
|
||||
@pytest.mark.project
|
||||
def testProjectNewRoot(nwTempProj,nwRef):
|
||||
@@ -73,7 +73,7 @@ def testProjectNewRoot(nwTempProj,nwRef):
|
||||
assert theProject.projChanged
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
assert cmpFiles(projFile, refFile, [2])
|
||||
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
|
||||
assert not theProject.projChanged
|
||||
|
||||
@pytest.mark.project
|
||||
@@ -86,7 +86,7 @@ def testProjectNewFile(nwTempProj,nwRef):
|
||||
assert theProject.projChanged
|
||||
assert theProject.saveProject()
|
||||
assert theProject.closeProject()
|
||||
assert cmpFiles(projFile, refFile, [2])
|
||||
assert cmpFiles(projFile, refFile, [2, 6, 7, 8])
|
||||
assert not theProject.projChanged
|
||||
|
||||
@pytest.mark.project
|
||||
|
||||
Reference in New Issue
Block a user