Merge pull request #118 from vkbo/tweaks

Code Tweaks
This commit is contained in:
Veronica K. Berglyd Olsen
2019-11-03 18:15:08 +01:00
committed by GitHub
43 changed files with 968 additions and 688 deletions
+5
View File
@@ -133,6 +133,11 @@ The different notes can be assigned tags, which the novel files can refer back t
Currently, this information can be used to display a Timeline View of the story, showing where each scene connects to the plot, and which characters, etc. occur in them. Currently, this information can be used to display a Timeline View of the story, showing where each scene connects to the plot, and which characters, etc. occur in them.
Further features using this meta data will be added in the future. Further features using this meta data will be added in the future.
## Contribution
If you ant to contribute to novelWriter, please follow the coding convention laid out in the [Style Guide](docs/markdown/style.md).
They broadly follow Python PEP8, but there are a few modifications.
## Screenshot ## Screenshot
![Screenshot 1](docs/source/images/screenshot.png) ![Screenshot 1](docs/source/images/screenshot.png)
+24
View File
@@ -0,0 +1,24 @@
# Code Style Guide
The source code of novelWriter broadly follows the style guide [PEP8](https://www.python.org/dev/peps/pep-0008/), but with a few modifications and exceptions.
### Source Code Exceptions
* Methods are camelCase, not underscore based.
The reason is partially because of the maintainers personal preference, and partially because that is what Qt5 and PyQt5 uses.
The maintainer generally, across multiple programming languages, uses underscores for defining namespaces.
* The maximum length of a code line is 99 characters, not 79.
The reason for this is that novelWriter is almost entirely made up of classes, meaning nearly all lines of code already have 8 leading spaces.
A 79 character limitation is too strict, and causes too many wrapped lines.
99 characters is suitable for GitHub diff readability, and therefore the preferred limit.
It is also permitted under PEP8 as the maximum.
Comments and docstrings should comply with the 72 character limit.
* Aligning code with additional spaces is acceptable in those cases where it improves readability.
Otherwise, the PEP8 standard should be applied.
### Documentation
The documentation does not adhere to the 80 character limit either.
The standard used in documentation is one line break after each sentence.
This is an alternative style that greatly improves readability of diffs as re-wrapping text is not needed when inserting new text in paragraphs.
Instead, the diff will show changes to each sentence.
+1 -10
View File
@@ -80,7 +80,6 @@ def main(sysArgs=None):
"help", "help",
"debug", "debug",
"verbose", "verbose",
"debuggui",
"quiet", "quiet",
"time", "time",
"logfile=", "logfile=",
@@ -100,9 +99,8 @@ def main(sysArgs=None):
" -v, --version Print program version and exit.\n" " -v, --version Print program version and exit.\n"
" -d, --debug Print debug output.\n" " -d, --debug Print debug output.\n"
" --verbose Increase verbosity of debug output.\n" " --verbose Increase verbosity of debug output.\n"
" -D, --debuggui Shows additional debug GUI elements. Includes -d.\n"
" -q, --quiet Disable output to command line. Does not affect log file.\n" " -q, --quiet Disable output to command line. Does not affect log file.\n"
" -t, --time Shows time stamp in logging output. Adds milliseconds when --verbose.\n" " -t, --time Shows time stamp in logging output.\n"
" -l, --logfile= Specify log file.\n" " -l, --logfile= Specify log file.\n"
" --style= Set Qt5 style flag. Defaults to Fusion.\n" " --style= Set Qt5 style flag. Defaults to Fusion.\n"
" --config= Alternative config file.\n" " --config= Alternative config file.\n"
@@ -124,7 +122,6 @@ def main(sysArgs=None):
showTime = False showTime = False
confPath = None confPath = None
testMode = False testMode = False
debugGUI = False
spellTool = None spellTool = None
qtStyle = "Fusion" qtStyle = "Fusion"
@@ -152,7 +149,6 @@ def main(sysArgs=None):
toStd = False toStd = False
elif inOpt in ("--verbose"): elif inOpt in ("--verbose"):
debugLevel = VERBOSE debugLevel = VERBOSE
timeStr = "[{asctime:}.{msecs:03.0f}] "
elif inOpt in ("-t","--time"): elif inOpt in ("-t","--time"):
showTime = True showTime = True
elif inOpt in ("--style"): elif inOpt in ("--style"):
@@ -163,14 +159,9 @@ def main(sysArgs=None):
testMode = True testMode = True
elif inOpt in ("--spell"): elif inOpt in ("--spell"):
spellTool = inArg spellTool = inArg
elif inOpt in ("-D","--debuggui"):
debugLevel = logging.DEBUG
debugStr = "{name:>20}:{lineno:<4d} {levelname:8} {message:}"
debugGUI = True
# Set Config Options # Set Config Options
CONFIG.showGUI = not testMode CONFIG.showGUI = not testMode
CONFIG.debugGUI = debugGUI
CONFIG.debugInfo = debugLevel < logging.INFO CONFIG.debugInfo = debugLevel < logging.INFO
CONFIG.spellTool = spellTool CONFIG.spellTool = spellTool
+16 -10
View File
@@ -17,16 +17,20 @@ logger = logging.getLogger(__name__)
def checkString(checkValue, defaultValue, allowNone=False): def checkString(checkValue, defaultValue, allowNone=False):
if allowNone: if allowNone:
if checkValue == None: return None if checkValue == None:
if checkValue == "None": return None return None
if checkValue == "None":
return None
if isinstance(checkValue,str): if isinstance(checkValue,str):
return str(checkValue) return str(checkValue)
return defaultValue return defaultValue
def checkInt(checkValue, defaultValue, allowNone=False): def checkInt(checkValue, defaultValue, allowNone=False):
if allowNone: if allowNone:
if checkValue == None: return None if checkValue == None:
if checkValue == "None": return None return None
if checkValue == "None":
return None
try: try:
return int(checkValue) return int(checkValue)
except: except:
@@ -34,8 +38,10 @@ def checkInt(checkValue, defaultValue, allowNone=False):
def checkBool(checkValue, defaultValue, allowNone=False): def checkBool(checkValue, defaultValue, allowNone=False):
if allowNone: if allowNone:
if checkValue == None: return None if checkValue == None:
if checkValue == "None": return None return None
if checkValue == "None":
return None
if isinstance(checkValue, str): if isinstance(checkValue, str):
if checkValue == "True": if checkValue == "True":
return True return True
@@ -83,8 +89,8 @@ def colRange(rgbStart, rgbEnd, nStep):
return retCol return retCol
def splitVersionNumber(vString): def splitVersionNumber(vString):
""" Splits a version string on the form aa.bb.cc into major, minor and patch, and computes an """ Splits a version string on the form aa.bb.cc into major, minor
integer value aabbcc. and patch, and computes an integer value aabbcc.
""" """
vMajor = 0 vMajor = 0
@@ -92,8 +98,8 @@ def splitVersionNumber(vString):
vPatch = 0 vPatch = 0
vInt = 0 vInt = 0
vBits = vString.split(".") vBits = vString.split(".")
nBits = len(vBits) nBits = len(vBits)
if nBits > 0: if nBits > 0:
vMajor = checkInt(vBits[0],0) vMajor = checkInt(vBits[0],0)
+111 -44
View File
@@ -15,15 +15,15 @@ import configparser
import sys import sys
import nw import nw
from os import path, mkdir, makedirs, getcwd from os import path, mkdir, makedirs, getcwd
from appdirs import user_config_dir from appdirs import user_config_dir
from datetime import datetime from datetime import datetime
from PyQt5.Qt import PYQT_VERSION_STR
from PyQt5.QtCore import QT_VERSION_STR
from nw.constants import nwFiles, nwUnicode from nw.constants import nwFiles, nwUnicode
from nw.common import splitVersionNumber from nw.common import splitVersionNumber
from PyQt5.Qt import PYQT_VERSION_STR
from PyQt5.QtCore import QT_VERSION_STR
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -40,7 +40,6 @@ class Config:
self.appName = nw.__package__ self.appName = nw.__package__
self.appHandle = nw.__package__.lower() self.appHandle = nw.__package__.lower()
self.showGUI = True self.showGUI = True
self.debugGUI = False
self.debugInfo = False self.debugInfo = False
self.spellTool = None self.spellTool = None
@@ -60,8 +59,8 @@ class Config:
self.confChanged = False self.confChanged = False
## General ## General
self.guiTheme = "default" self.guiTheme = "default"
self.guiSyntax = "default_light" self.guiSyntax = "default_light"
## Sizes ## Sizes
self.winGeometry = [1100, 650] self.winGeometry = [1100, 650]
@@ -201,65 +200,133 @@ class Config:
logger.debug("Loading config file") logger.debug("Loading config file")
cnfParse = configparser.ConfigParser() cnfParse = configparser.ConfigParser()
try: try:
cnfParse.read_file(open(path.join(self.confPath,self.confFile),mode="r",encoding="utf8")) cnfParse.read_file(
open(path.join(self.confPath,self.confFile),mode="r",encoding="utf8")
)
except Exception as e: except Exception as e:
logger.error("Could not load config file") logger.error("Could not load config file")
return False return False
## Main ## Main
cnfSec = "Main" cnfSec = "Main"
self.guiTheme = self._parseLine(cnfParse, cnfSec, "theme", self.CNF_STR, self.guiTheme) self.guiTheme = self._parseLine(
self.guiSyntax = self._parseLine(cnfParse, cnfSec, "syntax", self.CNF_STR, self.guiSyntax) cnfParse, cnfSec, "theme", self.CNF_STR, self.guiTheme
)
self.guiSyntax = self._parseLine(
cnfParse, cnfSec, "syntax", self.CNF_STR, self.guiSyntax
)
## Sizes ## Sizes
cnfSec = "Sizes" cnfSec = "Sizes"
self.winGeometry = self._parseLine(cnfParse, cnfSec, "geometry", self.CNF_LIST, self.winGeometry) self.winGeometry = self._parseLine(
self.treeColWidth = self._parseLine(cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth) cnfParse, cnfSec, "geometry", self.CNF_LIST, self.winGeometry
self.mainPanePos = self._parseLine(cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos) )
self.docPanePos = self._parseLine(cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos) self.treeColWidth = self._parseLine(
cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth
)
self.mainPanePos = self._parseLine(
cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos
)
self.docPanePos = self._parseLine(
cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos
)
## Project ## Project
cnfSec = "Project" cnfSec = "Project"
self.autoSaveProj = self._parseLine(cnfParse, cnfSec, "autosaveproject", self.CNF_INT, self.autoSaveProj) self.autoSaveProj = self._parseLine(
self.autoSaveDoc = self._parseLine(cnfParse, cnfSec, "autosavedoc", self.CNF_INT, self.autoSaveDoc) cnfParse, cnfSec, "autosaveproject", self.CNF_INT, self.autoSaveProj
)
self.autoSaveDoc = self._parseLine(
cnfParse, cnfSec, "autosavedoc", self.CNF_INT, self.autoSaveDoc
)
## Editor ## Editor
cnfSec = "Editor" cnfSec = "Editor"
self.textFont = self._parseLine(cnfParse, cnfSec, "textfont", self.CNF_STR, self.textFont) self.textFont = self._parseLine(
self.textSize = self._parseLine(cnfParse, cnfSec, "textsize", self.CNF_INT, self.textSize) cnfParse, cnfSec, "textfont", self.CNF_STR, self.textFont
self.textFixedW = self._parseLine(cnfParse, cnfSec, "fixedwidth", self.CNF_BOOL, self.textFixedW) )
self.textWidth = self._parseLine(cnfParse, cnfSec, "width", self.CNF_INT, self.textWidth) self.textSize = self._parseLine(
self.textMargin = self._parseLine(cnfParse, cnfSec, "margin", self.CNF_INT, self.textMargin) cnfParse, cnfSec, "textsize", self.CNF_INT, self.textSize
self.tabWidth = self._parseLine(cnfParse, cnfSec, "tabwidth", self.CNF_INT, self.tabWidth) )
self.doJustify = self._parseLine(cnfParse, cnfSec, "justify", self.CNF_BOOL, self.doJustify) self.textFixedW = self._parseLine(
self.autoSelect = self._parseLine(cnfParse, cnfSec, "autoselect", self.CNF_BOOL, self.autoSelect) cnfParse, cnfSec, "fixedwidth", self.CNF_BOOL, self.textFixedW
self.doReplace = self._parseLine(cnfParse, cnfSec, "autoreplace", self.CNF_BOOL, self.doReplace) )
self.doReplaceSQuote = self._parseLine(cnfParse, cnfSec, "repsquotes", self.CNF_BOOL, self.doReplaceSQuote) self.textWidth = self._parseLine(
self.doReplaceDQuote = self._parseLine(cnfParse, cnfSec, "repdquotes", self.CNF_BOOL, self.doReplaceDQuote) cnfParse, cnfSec, "width", self.CNF_INT, self.textWidth
self.doReplaceDash = self._parseLine(cnfParse, cnfSec, "repdash", self.CNF_BOOL, self.doReplaceDash) )
self.doReplaceDots = self._parseLine(cnfParse, cnfSec, "repdots", self.CNF_BOOL, self.doReplaceDots) self.textMargin = self._parseLine(
self.fmtSingleQuotes = self._parseLine(cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes) cnfParse, cnfSec, "margin", self.CNF_INT, self.textMargin
self.fmtDoubleQuotes = self._parseLine(cnfParse, cnfSec, "fmtdoublequote", self.CNF_LIST, self.fmtDoubleQuotes) )
self.spellLanguage = self._parseLine(cnfParse, cnfSec, "spellcheck", self.CNF_STR, self.spellLanguage) self.tabWidth = self._parseLine(
self.showTabsNSpaces = self._parseLine(cnfParse, cnfSec, "showtabsnspaces", self.CNF_BOOL, self.showTabsNSpaces) cnfParse, cnfSec, "tabwidth", self.CNF_INT, self.tabWidth
self.showLineEndings = self._parseLine(cnfParse, cnfSec, "showlineendings", self.CNF_BOOL, self.showLineEndings) )
self.doJustify = self._parseLine(
cnfParse, cnfSec, "justify", self.CNF_BOOL, self.doJustify
)
self.autoSelect = self._parseLine(
cnfParse, cnfSec, "autoselect", self.CNF_BOOL, self.autoSelect
)
self.doReplace = self._parseLine(
cnfParse, cnfSec, "autoreplace", self.CNF_BOOL, self.doReplace
)
self.doReplaceSQuote = self._parseLine(
cnfParse, cnfSec, "repsquotes", self.CNF_BOOL, self.doReplaceSQuote
)
self.doReplaceDQuote = self._parseLine(
cnfParse, cnfSec, "repdquotes", self.CNF_BOOL, self.doReplaceDQuote
)
self.doReplaceDash = self._parseLine(
cnfParse, cnfSec, "repdash", self.CNF_BOOL, self.doReplaceDash
)
self.doReplaceDots = self._parseLine(
cnfParse, cnfSec, "repdots", self.CNF_BOOL, self.doReplaceDots
)
self.fmtSingleQuotes = self._parseLine(
cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes
)
self.fmtDoubleQuotes = self._parseLine(
cnfParse, cnfSec, "fmtdoublequote", self.CNF_LIST, self.fmtDoubleQuotes
)
self.spellLanguage = self._parseLine(
cnfParse, cnfSec, "spellcheck", self.CNF_STR, self.spellLanguage
)
self.showTabsNSpaces = self._parseLine(
cnfParse, cnfSec, "showtabsnspaces", self.CNF_BOOL, self.showTabsNSpaces
)
self.showLineEndings = self._parseLine(
cnfParse, cnfSec, "showlineendings", self.CNF_BOOL, self.showLineEndings
)
## Backup ## Backup
cnfSec = "Backup" cnfSec = "Backup"
self.backupPath = self._parseLine(cnfParse, cnfSec, "backuppath", self.CNF_STR, self.backupPath) self.backupPath = self._parseLine(
self.backupOnClose = self._parseLine(cnfParse, cnfSec, "backuponclose", self.CNF_BOOL, self.backupOnClose) cnfParse, cnfSec, "backuppath", self.CNF_STR, self.backupPath
self.askBeforeBackup = self._parseLine(cnfParse, cnfSec, "askbeforebackup", self.CNF_BOOL, self.askBeforeBackup) )
self.backupOnClose = self._parseLine(
cnfParse, cnfSec, "backuponclose", self.CNF_BOOL, self.backupOnClose
)
self.askBeforeBackup = self._parseLine(
cnfParse, cnfSec, "askbeforebackup", self.CNF_BOOL, self.askBeforeBackup
)
## State ## State
cnfSec = "State" cnfSec = "State"
self.showRefPanel = self._parseLine(cnfParse, cnfSec, "showrefpanel", self.CNF_BOOL, self.showRefPanel) self.showRefPanel = self._parseLine(
self.viewComments = self._parseLine(cnfParse, cnfSec, "viewcomments", self.CNF_BOOL, self.viewComments) cnfParse, cnfSec, "showrefpanel", self.CNF_BOOL, self.showRefPanel
)
self.viewComments = self._parseLine(
cnfParse, cnfSec, "viewcomments", self.CNF_BOOL, self.viewComments
)
## Path ## Path
cnfSec = "Path" cnfSec = "Path"
self.lastPath = self._parseLine(cnfParse, cnfSec, "lastpath", self.CNF_STR, self.lastPath) self.lastPath = self._parseLine(
cnfParse, cnfSec, "lastpath", self.CNF_STR, self.lastPath
)
for i in range(10): for i in range(10):
self.recentList[i] = self._parseLine(cnfParse, cnfSec, "recent%d" % i,self.CNF_STR, self.recentList[i]) self.recentList[i] = self._parseLine(
cnfParse, cnfSec, "recent%d" % i,self.CNF_STR, self.recentList[i]
)
# Check Certain Values for None # Check Certain Values for None
self.spellLanguage = self._checkNone(self.spellLanguage) self.spellLanguage = self._checkNone(self.spellLanguage)
+3 -4
View File
@@ -13,12 +13,13 @@
import logging import logging
import nw import nw
from os import path from os import path
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from nw.convert.file.text import TextFile from nw.convert.file.text import TextFile
from nw.convert.tokenizer import Tokenizer from nw.convert.tokenizer import Tokenizer
from nw.enum import nwAlert, nwItemLayout from nw.enum import nwAlert, nwItemLayout
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -26,9 +27,7 @@ class ConcatFile(TextFile):
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
TextFile.__init__(self, theProject, theParent) TextFile.__init__(self, theProject, theParent)
self.theConv = Tokenizer(self.theProject, self.theParent) self.theConv = Tokenizer(self.theProject, self.theParent)
return return
def addText(self, tHandle): def addText(self, tHandle):
+2 -4
View File
@@ -13,9 +13,9 @@
import logging import logging
import nw import nw
from nw.convert.file.text import TextFile from nw.convert.file.text import TextFile
from nw.convert.text.tohtml import ToHtml from nw.convert.text.tohtml import ToHtml
from nw.enum import nwAlert from nw.enum import nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -23,9 +23,7 @@ class HtmlFile(TextFile):
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
TextFile.__init__(self, theProject, theParent) TextFile.__init__(self, theProject, theParent)
self.theConv = ToHtml(self.theProject, self.theParent) self.theConv = ToHtml(self.theProject, self.theParent)
return return
## ##
+2 -7
View File
@@ -13,9 +13,9 @@
import logging import logging
import nw import nw
from nw.convert.file.text import TextFile from nw.convert.file.text import TextFile
from nw.convert.text.tolatex import ToLaTeX from nw.convert.text.tolatex import ToLaTeX
from nw.enum import nwAlert from nw.enum import nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -32,7 +32,6 @@ class LaTeXFile(TextFile):
## ##
def _doOpenFile(self, filePath): def _doOpenFile(self, filePath):
try: try:
self.outFile = open(filePath,mode="wt+",encoding="utf8") self.outFile = open(filePath,mode="wt+",encoding="utf8")
self.outFile.write("\\documentclass[12pt]{report}\n") self.outFile.write("\\documentclass[12pt]{report}\n")
@@ -43,17 +42,13 @@ class LaTeXFile(TextFile):
except Exception as e: except Exception as e:
self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR) self.makeAlert(["Failed to open file.",str(e)], nwAlert.ERROR)
return False return False
return True return True
def _doCloseFile(self): def _doCloseFile(self):
if self.outFile is not None: if self.outFile is not None:
self.outFile.write("\\end{document}\n") self.outFile.write("\\end{document}\n")
self.outFile.close() self.outFile.close()
self.texCodecFail = self.theConv.texCodecFail self.texCodecFail = self.theConv.texCodecFail
return True return True
# END Class LaTeXFile # END Class LaTeXFile
+2 -4
View File
@@ -13,9 +13,9 @@
import logging import logging
import nw import nw
from nw.convert.file.text import TextFile from nw.convert.file.text import TextFile
from nw.convert.text.tomarkdown import ToMarkdown from nw.convert.text.tomarkdown import ToMarkdown
from nw.enum import nwAlert from nw.enum import nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -23,9 +23,7 @@ class MarkdownFile(TextFile):
def __init__(self, theProject, theParent): def __init__(self, theProject, theParent):
TextFile.__init__(self, theProject, theParent) TextFile.__init__(self, theProject, theParent)
self.theConv = ToMarkdown(self.theProject, self.theParent) self.theConv = ToMarkdown(self.theProject, self.theParent)
return return
## ##
+25 -20
View File
@@ -13,11 +13,12 @@
import logging import logging
import nw import nw
from os import path from os import path
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from nw.convert.text.totext import ToText from nw.convert.text.totext import ToText
from nw.enum import nwAlert, nwItemType, nwItemLayout, nwItemClass from nw.enum import nwAlert, nwItemType, nwItemLayout, nwItemClass
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -29,14 +30,14 @@ class TextFile():
self.theProject = theProject self.theProject = theProject
self.theParent = theParent self.theParent = theParent
self.outFile = None self.outFile = None
self.fileName = "" self.fileName = ""
self.theText = "" self.theText = ""
self.expNovel = True self.expNovel = True
self.expNotes = False self.expNotes = False
self.theConv = ToText(self.theProject, self.theParent) self.theConv = ToText(self.theProject, self.theParent)
self.makeAlert = self.theParent.makeAlert self.makeAlert = self.theParent.makeAlert
self.setComments(False) self.setComments(False)
self.setKeywords(False) self.setKeywords(False)
@@ -100,9 +101,9 @@ class TextFile():
self.fileName = path.basename(filePath) self.fileName = path.basename(filePath)
if path.isfile(filePath) and self.mainConf.showGUI: if path.isfile(filePath) and self.mainConf.showGUI:
msgBox = QMessageBox() msgBox = QMessageBox()
msgRes = msgBox.question( msgRes = msgBox.question(self.theParent, "Overwrite", (
self.theParent, "Overwrite", ("File '%s' already exists.<br>Do you want to overwrite it?" % self.fileName) "File '%s' already exists.<br>Do you want to overwrite it?" % self.fileName
) ))
if msgRes != QMessageBox.Yes: if msgRes != QMessageBox.Yes:
return False return False
@@ -137,11 +138,14 @@ class TextFile():
return True return True
def checkInclude(self, tHandle): def checkInclude(self, tHandle):
"""This function checks whether a file should be included in the export or not. For standard """This function checks whether a file should be included in the
note and novel files, this is controlled by the options selected by the user. For other export or not. For standard note and novel files, this is
files classified as non-exportable, a few checks must be made, and the following are not: controlled by the options selected by the user. For other files
classified as non-exportable, a few checks must be made, and the
following are not:
* Items that are not actual files. * Items that are not actual files.
* Items that have been orphaned which are tagged as NO_LAYOUT and NO_CLASS. * Items that have been orphaned which are tagged as NO_LAYOUT
and NO_CLASS.
* Items that appear in the TRASH folder * Items that appear in the TRASH folder
""" """
@@ -168,8 +172,9 @@ class TextFile():
## ##
def _doOpenFile(self, filePath): def _doOpenFile(self, filePath):
"""This function does the actual opening of the file, and can be overloaded by a subclass """This function does the actual opening of the file, and can be
that uses a different file format that requires a different approach. overloaded by a subclass that uses a different file format that
requires a different approach.
""" """
try: try:
self.outFile = open(filePath,mode="wt+",encoding="utf8") self.outFile = open(filePath,mode="wt+",encoding="utf8")
@@ -180,8 +185,8 @@ class TextFile():
return True return True
def _doCloseFile(self): def _doCloseFile(self):
"""This function closes the file, and is meant to be overloaded by the subclass for other """This function closes the file, and is meant to be overloaded
file formats. by the subclass for other file formats.
""" """
if self.outFile is not None: if self.outFile is not None:
self.outFile.close() self.outFile.close()
+4 -5
View File
@@ -15,7 +15,7 @@ import re
import nw import nw
from nw.convert.tokenizer import Tokenizer from nw.convert.tokenizer import Tokenizer
from nw.constants import nwUnicode, nwLabels from nw.constants import nwUnicode, nwLabels
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -27,15 +27,14 @@ class ToHtml(Tokenizer):
return return
def setPreview(self, forPreview, doComments): def setPreview(self, forPreview, doComments):
"""If we're using this class to generate markdown preview, we need to make a few changes to """If we're using this class to generate markdown preview, we
formatting, which is selected by this flag. need to make a few changes to formatting, which is selected by
this flag.
""" """
self.forPreview = forPreview self.forPreview = forPreview
if forPreview: if forPreview:
self.doKeywords = True self.doKeywords = True
self.doComments = doComments self.doComments = doComments
return return
def doAutoReplace(self): def doAutoReplace(self):
+9 -6
View File
@@ -16,7 +16,7 @@ import re
import nw import nw
from nw.convert.tokenizer import Tokenizer from nw.convert.tokenizer import Tokenizer
from nw.constants import nwUnicode from nw.constants import nwUnicode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,7 +28,8 @@ class ToLaTeX(Tokenizer):
return return
def doPostProcessing(self): def doPostProcessing(self):
"""The latexcodec misses dashes and non-breaking spaces, so we do those here. """The latexcodec misses dashes and non-breaking spaces, so we
do those here.
""" """
repDict = { repDict = {
@@ -62,8 +63,9 @@ class ToLaTeX(Tokenizer):
begText = "\\begin{center}\n" begText = "\\begin{center}\n"
endText = "\\end{center}\n\n" endText = "\\end{center}\n\n"
# First check if we have a comment or plain text, as they need some # First check if we have a comment or plain text, as they
# extra replacing before we proceed to wrapping and final formatting. # need some extra replacing before we proceed to wrapping
# and final formatting.
if tType == self.T_COMMENT: if tType == self.T_COMMENT:
tText = "%% %s" % tText tText = "%% %s" % tText
@@ -75,8 +77,9 @@ class ToLaTeX(Tokenizer):
tLen = len(tText) tLen = len(tText)
# Then the text can receive final formatting before we append it to the results. # Then the text can receive final formatting before we
# We also store text lines in a buffer and merge them only when we find an empty line # append it to the results. We also store text lines in a
# buffer and merge them only when we find an empty line
# indicating a new paragraph. # indicating a new paragraph.
if tType == self.T_EMPTY: if tType == self.T_EMPTY:
if len(thisPar) > 0: if len(thisPar) > 0:
+11 -6
View File
@@ -55,8 +55,9 @@ class ToMarkdown(Tokenizer):
thisPar = [] thisPar = []
for tType, tText, tFormat, tAlign in self.theTokens: for tType, tText, tFormat, tAlign in self.theTokens:
# First check if we have a comment or plain text, as they need some # First check if we have a comment or plain text, as they
# extra replacing before we proceed to wrapping and final formatting. # need some extra replacing before we proceed to wrapping
# and final formatting.
if tType == self.T_COMMENT: if tType == self.T_COMMENT:
tText = " %s" % tText tText = " %s" % tText
@@ -68,15 +69,19 @@ class ToMarkdown(Tokenizer):
tLen = len(tText) tLen = len(tText)
# The text can now be word wrapped, if we have requested this and it's needed. # The text can now be word wrapped, if we have requested
# this and it's needed.
if self.wordWrap > 0 and tLen > self.wordWrap: if self.wordWrap > 0 and tLen > self.wordWrap:
if tType == self.T_COMMENT: if tType == self.T_COMMENT:
tText = textwrap.fill(tText.strip(),initial_indent=" ",subsequent_indent=" ") tText = textwrap.fill(
tText.strip(),initial_indent=" ",subsequent_indent=" "
)
else: else:
tText = tWrap.fill(tText) tText = tWrap.fill(tText)
# Then the text can receive final formatting before we append it to the results. # Then the text can receive final formatting before we
# We also store text lines in a buffer and merge them only when we find an empty line, # append it to the results. We also store text lines in a
# buffer and merge them only when we find an empty line,
# indicating a new paragraph. # indicating a new paragraph.
if tType == self.T_EMPTY: if tType == self.T_EMPTY:
if len(thisPar) > 0: if len(thisPar) > 0:
+9 -6
View File
@@ -16,7 +16,7 @@ import re
import nw import nw
from nw.convert.tokenizer import Tokenizer from nw.convert.tokenizer import Tokenizer
from nw.constants import nwUnicode from nw.constants import nwUnicode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -61,8 +61,9 @@ class ToText(Tokenizer):
thisPar = [] thisPar = []
for tType, tText, tFormat, tAlign in self.theTokens: for tType, tText, tFormat, tAlign in self.theTokens:
# First check if we have a comment or plain text, as they need some # First check if we have a comment or plain text, as they
# extra replacing before we proceed to wrapping and final formatting. # need some extra replacing before we proceed to wrapping
# and final formatting.
if tType == self.T_COMMENT: if tType == self.T_COMMENT:
tText = "[%s]" % tText tText = "[%s]" % tText
@@ -74,7 +75,8 @@ class ToText(Tokenizer):
tLen = len(tText) tLen = len(tText)
# The text can now be word wrapped, if we have requested this and it's needed. # The text can now be word wrapped, if we have requested
# this and it's needed.
if tAlign == self.A_CENTRE: if tAlign == self.A_CENTRE:
if self.wordWrap > 0: if self.wordWrap > 0:
if tLen > self.wordWrap: if tLen > self.wordWrap:
@@ -88,8 +90,9 @@ class ToText(Tokenizer):
if self.wordWrap > 0 and tLen > self.wordWrap: if self.wordWrap > 0 and tLen > self.wordWrap:
tText = tWrap.fill(tText) tText = tWrap.fill(tText)
# Then the text can receive final formatting before we append it to the results. # Then the text can receive final formatting before we
# We also store text lines in a buffer and merge them only when we find an empty line, # append it to the results. We also store text lines in a
# buffer and merge them only when we find an empty line,
# indicating a new paragraph. # indicating a new paragraph.
if tType == self.T_EMPTY: if tType == self.T_EMPTY:
if len(thisPar) > 0: if len(thisPar) > 0:
+13 -10
View File
@@ -15,12 +15,12 @@ import logging
import re import re
import nw import nw
from operator import itemgetter from operator import itemgetter
from PyQt5.QtCore import QRegularExpression from PyQt5.QtCore import QRegularExpression
from nw.project.document import NWDoc from nw.project.document import NWDoc
from nw.tools.translate import numberToWord from nw.tools.translate import numberToWord
from nw.enum import nwItemLayout from nw.enum import nwItemLayout
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -153,10 +153,11 @@ class Tokenizer():
return return
def tokenizeText(self): def tokenizeText(self):
"""Scan the text for either lines starting with specific characters that indicate headers, """Scan the text for either lines starting with specific
comments, commands etc, or just contains plain text. in the case of plain text, apply the characters that indicate headers, comments, commands etc, or
same RegExes that the syntax highlighter uses and save the locations of these formatting just contains plain text. in the case of plain text, apply the
tags into the token array. same RegExes that the syntax highlighter uses and save the
locations of these formatting tags into the token array.
""" """
# RegExes for adding formatting tags within text lines # RegExes for adding formatting tags within text lines
@@ -203,7 +204,8 @@ class Tokenizer():
xLen = rxMatch.capturedLength(n) xLen = rxMatch.capturedLength(n)
fmtPos.append([xPos,xLen,theKeys[n]]) fmtPos.append([xPos,xLen,theKeys[n]])
# Save the line as is, but append the array of formatting locations sorted by position # Save the line as is, but append the array of formatting locations
# sorted by position
fmtPos = sorted(fmtPos,key=itemgetter(0)) fmtPos = sorted(fmtPos,key=itemgetter(0))
self.theTokens.append((self.T_TEXT,aLine,fmtPos,self.A_LEFT)) self.theTokens.append((self.T_TEXT,aLine,fmtPos,self.A_LEFT))
@@ -228,7 +230,8 @@ class Tokenizer():
if isNone: return if isNone: return
if isNote: return if isNote: return
# For novel files, we need to handle chapter numbering and scene breaks # For novel files, we need to handle chapter numbering and scene
# breaks
if isBook or isUnNum or isChap or isScene: if isBook or isUnNum or isChap or isScene:
for n in range(len(self.theTokens)): for n in range(len(self.theTokens)):
+21 -11
View File
@@ -15,15 +15,15 @@ import nw
from os import path from os import path
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush, QStandardItemModel, QFont from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush, QStandardItemModel, QFont
from PyQt5.QtSvg import QSvgWidget from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QFormLayout, QLineEdit, QPlainTextEdit, QLabel, QDialog, QHBoxLayout, QVBoxLayout, QFormLayout, QLineEdit, QPlainTextEdit, QLabel,
QWidget, QTabWidget, QDialogButtonBox, QSpinBox, QGroupBox, QComboBox, QMessageBox, QWidget, QTabWidget, QDialogButtonBox, QSpinBox, QGroupBox, QComboBox, QMessageBox,
QCheckBox, QGridLayout, QFontComboBox, QPushButton, QFileDialog QCheckBox, QGridLayout, QFontComboBox, QPushButton, QFileDialog
) )
from nw.enum import nwAlert from nw.enum import nwAlert
from nw.constants import nwQuotes from nw.constants import nwQuotes
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -80,8 +80,8 @@ class GuiConfigEditor(QDialog):
logger.verbose("ConfigEditor save button clicked") logger.verbose("ConfigEditor save button clicked")
validEntries = True validEntries = True
needsRestart = False needsRestart = False
retA, retB = self.tabMain.saveValues() retA, retB = self.tabMain.saveValues()
validEntries &= retA validEntries &= retA
@@ -407,7 +407,9 @@ class GuiConfigEditEditor(QWidget):
self.autoReplaceDQ.setCheckState(Qt.Unchecked) self.autoReplaceDQ.setCheckState(Qt.Unchecked)
self.autoReplaceDash = QCheckBox(self) self.autoReplaceDash = QCheckBox(self)
self.autoReplaceDash.setToolTip("Auto-replace double and triple hyphens with short and long dash.") self.autoReplaceDash.setToolTip(
"Auto-replace double and triple hyphens with short and long dash."
)
if self.mainConf.doReplaceDash: if self.mainConf.doReplaceDash:
self.autoReplaceDash.setCheckState(Qt.Checked) self.autoReplaceDash.setCheckState(Qt.Checked)
else: else:
@@ -555,25 +557,33 @@ class GuiConfigEditEditor(QWidget):
if self._checkQuoteSymbol(fmtSingleQuotesO): if self._checkQuoteSymbol(fmtSingleQuotesO):
self.mainConf.fmtSingleQuotes[0] = fmtSingleQuotesO self.mainConf.fmtSingleQuotes[0] = fmtSingleQuotesO
else: else:
self.theParent.makeAlert("Invalid quote symbol: %s" % fmtSingleQuotesO, nwAlert.ERROR) self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtSingleQuotesO, nwAlert.ERROR
)
validEntries = False validEntries = False
if self._checkQuoteSymbol(fmtSingleQuotesC): if self._checkQuoteSymbol(fmtSingleQuotesC):
self.mainConf.fmtSingleQuotes[1] = fmtSingleQuotesC self.mainConf.fmtSingleQuotes[1] = fmtSingleQuotesC
else: else:
self.theParent.makeAlert("Invalid quote symbol: %s" % fmtSingleQuotesC, nwAlert.ERROR) self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtSingleQuotesC, nwAlert.ERROR
)
validEntries = False validEntries = False
if self._checkQuoteSymbol(fmtDoubleQuotesO): if self._checkQuoteSymbol(fmtDoubleQuotesO):
self.mainConf.fmtDoubleQuotes[0] = fmtDoubleQuotesO self.mainConf.fmtDoubleQuotes[0] = fmtDoubleQuotesO
else: else:
self.theParent.makeAlert("Invalid quote symbol: %s" % fmtDoubleQuotesO, nwAlert.ERROR) self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtDoubleQuotesO, nwAlert.ERROR
)
validEntries = False validEntries = False
if self._checkQuoteSymbol(fmtDoubleQuotesC): if self._checkQuoteSymbol(fmtDoubleQuotesC):
self.mainConf.fmtDoubleQuotes[1] = fmtDoubleQuotesC self.mainConf.fmtDoubleQuotes[1] = fmtDoubleQuotesC
else: else:
self.theParent.makeAlert("Invalid quote symbol: %s" % fmtDoubleQuotesC, nwAlert.ERROR) self.theParent.makeAlert(
"Invalid quote symbol: %s" % fmtDoubleQuotesC, nwAlert.ERROR
)
validEntries = False validEntries = False
showTabsNSpaces = self.showTabsNSpaces.isChecked() showTabsNSpaces = self.showTabsNSpaces.isChecked()
+41 -35
View File
@@ -16,24 +16,25 @@ import nw
from os import path from os import path
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtSvg import QSvgWidget from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QGridLayout, QGroupBox, QCheckBox, QDialog, QHBoxLayout, QVBoxLayout, QWidget, QTabWidget, QGridLayout,
QLabel, QComboBox, QLineEdit, QPushButton, QFileDialog, QProgressBar, QSpinBox, QMessageBox QGroupBox, QCheckBox, QLabel, QComboBox, QLineEdit, QPushButton,
QFileDialog, QProgressBar, QSpinBox, QMessageBox
) )
from nw.project.document import NWDoc from nw.project.document import NWDoc
from nw.tools.translate import numberToWord from nw.tools.translate import numberToWord
from nw.tools.optlaststate import OptLastState from nw.tools.optlaststate import OptLastState
from nw.convert.file.text import TextFile from nw.convert.file.text import TextFile
from nw.convert.file.html import HtmlFile from nw.convert.file.html import HtmlFile
from nw.convert.file.markdown import MarkdownFile from nw.convert.file.markdown import MarkdownFile
from nw.convert.file.latex import LaTeXFile from nw.convert.file.latex import LaTeXFile
from nw.convert.file.concat import ConcatFile from nw.convert.file.concat import ConcatFile
from nw.common import packageRefURL from nw.common import packageRefURL
from nw.constants import nwFiles from nw.constants import nwFiles
from nw.enum import nwItemType, nwAlert from nw.enum import nwItemType, nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -201,9 +202,10 @@ class GuiExport(QDialog):
# Check that encoding was successful # Check that encoding was successful
if outFile.texCodecFail: if outFile.texCodecFail:
self.theParent.makeAlert(( self.theParent.makeAlert((
"Failed to escape unicode characters while writing LaTeX file. The generated " "Failed to escape unicode characters while writing LaTeX "
".tex file may not build properly. Make sure the python package '{package:s}' " "file. The generated .tex file may not build properly. "
"is installed and working." "Make sure the python package '{package:s}' is installed "
"and working."
).format( ).format(
package = packageRefURL("latexcodec") package = packageRefURL("latexcodec")
), nwAlert.WARN) ), nwAlert.WARN)
@@ -343,27 +345,31 @@ class GuiExportMain(QWidget):
} }
FMT_HELP = { FMT_HELP = {
FMT_NWD : ( FMT_NWD : (
"Exports a document using the novelWriter markdown format. The files selected by the " "Exports a document using the novelWriter markdown format. "
"filters are appended as-is, including comments and other settings." "The files selected by the filters are appended as-is, "
"including comments and other settings."
), ),
FMT_TXT : ( FMT_TXT : (
"Exports a plain text file. All formatting is stripped and comments are in square " "Exports a plain text file. All formatting is stripped and "
"brackets." "comments are in square brackets."
), ),
FMT_MD : ( FMT_MD : (
"Exports a standard markdown file. Comments are converted to preformatted text blocks." "Exports a standard markdown file. Comments are converted "
"to preformatted text blocks."
), ),
FMT_HTML : ( FMT_HTML : (
"Exports a plain html5 file. Comments are wrapped in blocks with a yellow background " "Exports a plain html5 file. Comments are wrapped in "
"colour." "blocks with a yellow background colour."
), ),
FMT_TEX : ( FMT_TEX : (
"Exports a LaTeX file that can be compiled to PDF using for instance PDFLaTeX. " "Exports a LaTeX file that can be compiled to PDF using "
"Comments are exported as LaTeX comments." "for instance PDFLaTeX. Comments are exported as LaTeX "
"comments."
), ),
FMT_PDOC : ( FMT_PDOC : (
"Exports first to markdown or html5. The file is then passed on to Pandoc for a second " "Exports first to markdown or html5. The file is then "
"stage. Use the Pandoc tab for settings up the conversion." "passed on to Pandoc for a second stage. Use the Pandoc "
"tab for settings up the conversion."
), ),
} }
@@ -510,7 +516,9 @@ class GuiExportMain(QWidget):
self.fixedWidth.setMaximum(999) self.fixedWidth.setMaximum(999)
self.fixedWidth.setSingleStep(1) self.fixedWidth.setSingleStep(1)
self.fixedWidth.setValue(self.optState.getSetting("fixWidth")) self.fixedWidth.setValue(self.optState.getSetting("fixWidth"))
self.fixedWidth.setToolTip("Applies to .txt and .md files. A value of '0' disables the feature.") self.fixedWidth.setToolTip(
"Applies to .txt and .md files. A value of '0' disables the feature."
)
self.addSettingsForm.addWidget(QLabel("Fixed width"), 0, 0) self.addSettingsForm.addWidget(QLabel("Fixed width"), 0, 0)
self.addSettingsForm.addWidget(self.fixedWidth, 0, 1) self.addSettingsForm.addWidget(self.fixedWidth, 0, 1)
@@ -535,7 +543,8 @@ class GuiExportMain(QWidget):
## ##
def _updateFormat(self, currIdx): def _updateFormat(self, currIdx):
"""Update help text under output format selection and file extension in file box """Update help text under output format selection and file
extension in file box
""" """
if currIdx == -1: if currIdx == -1:
self.outputHelp.setText("") self.outputHelp.setText("")
@@ -563,7 +572,8 @@ class GuiExportMain(QWidget):
dlgOpt = QFileDialog.Options() dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog dlgOpt |= QFileDialog.DontUseNativeDialog
saveTo = QFileDialog.getSaveFileName( saveTo = QFileDialog.getSaveFileName(
self,"Export File",self.exportPath.text(),options=dlgOpt,filter=";;".join(extFilter) self, "Export File", self.exportPath.text(),
options=dlgOpt, filter=";;".join(extFilter)
) )
if saveTo: if saveTo:
self.exportPath.setText(saveTo[0]) self.exportPath.setText(saveTo[0])
@@ -655,7 +665,6 @@ class GuiExportPandoc(QWidget):
self.outputFormat.addItem("ePUB eBook v2 (.epub2)", self.FMT_EPUB2) self.outputFormat.addItem("ePUB eBook v2 (.epub2)", self.FMT_EPUB2)
self.outputFormat.addItem("ePUB eBook v3 (.epub3)", self.FMT_EPUB3) self.outputFormat.addItem("ePUB eBook v3 (.epub3)", self.FMT_EPUB3)
self.outputFormat.addItem("Zim Wiki (.txt)", self.FMT_ZIM) self.outputFormat.addItem("Zim Wiki (.txt)", self.FMT_ZIM)
# self.outputFormat.currentIndexChanged.connect(self._updateFormat)
optIdx = self.outputFormat.findData(self.optState.getSetting("pFormat")) optIdx = self.outputFormat.findData(self.optState.getSetting("pFormat"))
if optIdx == -1: if optIdx == -1:
@@ -671,9 +680,6 @@ class GuiExportPandoc(QWidget):
self.outerBox.addWidget(self.guiInfo, 0, 0) self.outerBox.addWidget(self.guiInfo, 0, 0)
self.outerBox.addWidget(self.guiOutput, 1, 0) self.outerBox.addWidget(self.guiOutput, 1, 0)
self.outerBox.setRowStretch(2, 1) self.outerBox.setRowStretch(2, 1)
# self.outerBox.setColumnStretch(0, 1)
# self.outerBox.setColumnStretch(1, 1)
# self.outerBox.setColumnStretch(2, 1)
self.setLayout(self.outerBox) self.setLayout(self.outerBox)
return return
+11 -8
View File
@@ -13,14 +13,17 @@
import logging import logging
import nw import nw
from os import path from os import path
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QGroupBox, QFormLayout, QLineEdit, QPushButton, QComboBox from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtSvg import QSvgWidget from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QGroupBox, QFormLayout,
QLineEdit, QPushButton, QComboBox
)
from nw.enum import nwItemLayout, nwItemClass, nwItemType from nw.enum import nwItemLayout, nwItemClass, nwItemType
from nw.constants import nwLabels from nw.constants import nwLabels
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -36,8 +39,8 @@ class GuiItemEditor(QDialog):
self.theParent = theParent self.theParent = theParent
self.theItem = self.theProject.getItem(tHandle) self.theItem = self.theProject.getItem(tHandle)
self.outerBox = QHBoxLayout() self.outerBox = QHBoxLayout()
self.innerBox = QVBoxLayout() self.innerBox = QVBoxLayout()
self.setWindowTitle("Item Settings") self.setWindowTitle("Item Settings")
+11 -8
View File
@@ -15,13 +15,14 @@ import nw
from os import path from os import path
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush, QStandardItemModel from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush, QStandardItemModel
from PyQt5.QtSvg import QSvgWidget from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QHBoxLayout, QVBoxLayout, QFormLayout, QLineEdit, QPlainTextEdit, QLabel, QDialog, QHBoxLayout, QVBoxLayout, QFormLayout, QLineEdit, QPlainTextEdit,
QWidget, QTabWidget, QDialogButtonBox, QListWidget, QListWidgetItem, QPushButton, QLabel, QWidget, QTabWidget, QDialogButtonBox, QListWidget,
QColorDialog, QAbstractItemView, QTreeWidget, QTreeWidgetItem, QCheckBox QListWidgetItem, QPushButton, QColorDialog, QAbstractItemView, QTreeWidget,
QTreeWidgetItem, QCheckBox
) )
from nw.enum import nwAlert from nw.enum import nwAlert
@@ -173,7 +174,7 @@ class GuiProjectEditStatus(QWidget):
for iName, iCol, nUse in self.theStatus: for iName, iCol, nUse in self.theStatus:
self._addItem(iName, iCol, iName, nUse) self._addItem(iName, iCol, iName, nUse)
self.editName = QLineEdit() self.editName = QLineEdit()
self.editName.setEnabled(False) self.editName.setEnabled(False)
self.newButton = QPushButton("New") self.newButton = QPushButton("New")
self.delButton = QPushButton("Delete") self.delButton = QPushButton("Delete")
@@ -221,7 +222,9 @@ class GuiProjectEditStatus(QWidget):
def _selectColour(self): def _selectColour(self):
logger.verbose("Item colour button clicked") logger.verbose("Item colour button clicked")
if self.selColour is not None: if self.selColour is not None:
newCol = QColorDialog.getColor(self.selColour, self, "Select Colour", QColorDialog.DontUseNativeDialog) newCol = QColorDialog.getColor(
self.selColour, self, "Select Colour", QColorDialog.DontUseNativeDialog
)
if newCol: if newCol:
self.selColour = newCol self.selColour = newCol
colPixmap = QPixmap(16,16) colPixmap = QPixmap(16,16)
+33 -21
View File
@@ -13,18 +13,20 @@
import logging import logging
import nw import nw
from os import path from os import path
from datetime import datetime from datetime import datetime
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QColor, QPixmap, QFont from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QColor, QPixmap, QFont
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QHeaderView, QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem,
QGridLayout, QLabel, QGroupBox, QCheckBox QDialogButtonBox, QHeaderView, QGridLayout, QLabel, QGroupBox,
QCheckBox
) )
from nw.tools.optlaststate import OptLastState from nw.tools.optlaststate import OptLastState
from nw.constants import nwConst, nwFiles from nw.constants import nwConst, nwFiles
from nw.enum import nwAlert from nw.enum import nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -44,16 +46,22 @@ class GuiSessionLogView(QDialog):
self.timeFilter = 0.0 self.timeFilter = 0.0
self.timeTotal = 0.0 self.timeTotal = 0.0
self.outerBox = QGridLayout() self.outerBox = QGridLayout()
self.bottomBox = QHBoxLayout() self.bottomBox = QHBoxLayout()
self.setWindowTitle("Session Log") self.setWindowTitle("Session Log")
self.setMinimumWidth(420) self.setMinimumWidth(420)
self.setMinimumHeight(400) self.setMinimumHeight(400)
widthCol0 = self.optState.validIntRange(self.optState.getSetting("widthCol0"), 30, 999, 180) widthCol0 = self.optState.validIntRange(
widthCol1 = self.optState.validIntRange(self.optState.getSetting("widthCol1"), 30, 999, 80) self.optState.getSetting("widthCol0"), 30, 999, 180
widthCol2 = self.optState.validIntRange(self.optState.getSetting("widthCol2"), 30, 999, 80) )
widthCol1 = self.optState.validIntRange(
self.optState.getSetting("widthCol1"), 30, 999, 80
)
widthCol2 = self.optState.validIntRange(
self.optState.getSetting("widthCol2"), 30, 999, 80
)
self.listBox = QTreeWidget() self.listBox = QTreeWidget()
self.listBox.setHeaderLabels(["Session Start","Length","Words",""]) self.listBox.setHeaderLabels(["Session Start","Length","Words",""])
@@ -70,8 +78,12 @@ class GuiSessionLogView(QDialog):
self.monoFont = QFont("Monospace",10) self.monoFont = QFont("Monospace",10)
sortValid = (Qt.AscendingOrder, Qt.DescendingOrder) sortValid = (Qt.AscendingOrder, Qt.DescendingOrder)
sortCol = self.optState.validIntRange(self.optState.getSetting("sortCol"), 0, 2, 0) sortCol = self.optState.validIntRange(
sortOrder = self.optState.validIntTuple(self.optState.getSetting("sortOrder"), sortValid, Qt.DescendingOrder) self.optState.getSetting("sortCol"), 0, 2, 0
)
sortOrder = self.optState.validIntTuple(
self.optState.getSetting("sortOrder"), sortValid, Qt.DescendingOrder
)
self.listBox.sortByColumn(sortCol, sortOrder) self.listBox.sortByColumn(sortCol, sortOrder)
self.listBox.setSortingEnabled(True) self.listBox.setSortingEnabled(True)
@@ -81,7 +93,7 @@ class GuiSessionLogView(QDialog):
self.infoBoxForm = QGridLayout(self) self.infoBoxForm = QGridLayout(self)
self.infoBox.setLayout(self.infoBoxForm) self.infoBox.setLayout(self.infoBoxForm)
self.labelTotal = QLabel(self._formatTime(0)) self.labelTotal = QLabel(self._formatTime(0))
self.labelTotal.setFont(self.monoFont) self.labelTotal.setFont(self.monoFont)
self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
@@ -152,11 +164,11 @@ class GuiSessionLogView(QDialog):
inData = inLine.split() inData = inLine.split()
if len(inData) != 8: if len(inData) != 8:
continue continue
dStart = datetime.strptime("%s %s" % (inData[1],inData[2]),nwConst.tStampFmt) dStart = datetime.strptime("%s %s" % (inData[1],inData[2]),nwConst.tStampFmt)
dEnd = datetime.strptime("%s %s" % (inData[4],inData[5]),nwConst.tStampFmt) dEnd = datetime.strptime("%s %s" % (inData[4],inData[5]),nwConst.tStampFmt)
nWords = int(inData[7]) nWords = int(inData[7])
tDiff = dEnd - dStart tDiff = dEnd - dStart
sDiff = tDiff.total_seconds() sDiff = tDiff.total_seconds()
self.timeTotal += sDiff self.timeTotal += sDiff
if abs(nWords) > 0: if abs(nWords) > 0:
+22 -16
View File
@@ -13,17 +13,19 @@
import logging import logging
import nw import nw
from os import path from os import path
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QColor, QPixmap from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QColor, QPixmap
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QDialogButtonBox, QLabel, QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
QPushButton, QHeaderView, QGridLayout, QGroupBox, QCheckBox QDialogButtonBox, QLabel, QPushButton, QHeaderView, QGridLayout,
QGroupBox, QCheckBox
) )
from nw.tools.optlaststate import OptLastState from nw.tools.optlaststate import OptLastState
from nw.constants import nwFiles from nw.constants import nwFiles
from nw.enum import nwItemClass from nw.enum import nwItemClass
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -41,21 +43,25 @@ class GuiTimeLineView(QDialog):
self.optState = TimeLineLastState(self.theProject,nwFiles.TLINE_OPT) self.optState = TimeLineLastState(self.theProject,nwFiles.TLINE_OPT)
self.optState.loadSettings() self.optState.loadSettings()
self.theMatrix = {} self.theMatrix = {}
self.numRows = 0 self.numRows = 0
self.numCols = 0 self.numCols = 0
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
self.filterBox = QVBoxLayout() self.filterBox = QVBoxLayout()
self.centreBox = QHBoxLayout() self.centreBox = QHBoxLayout()
self.bottomBox = QHBoxLayout() self.bottomBox = QHBoxLayout()
self.setWindowTitle("Timeline View") self.setWindowTitle("Timeline View")
self.setMinimumWidth(700) self.setMinimumWidth(700)
self.setMinimumHeight(400) self.setMinimumHeight(400)
winWidth = self.optState.validIntRange(self.optState.getSetting("winWidth"), 700, 10000, 700) winWidth = self.optState.validIntRange(
winHeight = self.optState.validIntRange(self.optState.getSetting("winHeight"), 400, 10000, 400) self.optState.getSetting("winWidth"), 700, 10000, 700
)
winHeight = self.optState.validIntRange(
self.optState.getSetting("winHeight"), 400, 10000, 400
)
self.resize(winWidth,winHeight) self.resize(winWidth,winHeight)
# TimeLine Table # TimeLine Table
+2 -3
View File
@@ -13,10 +13,10 @@
import logging import logging
import nw import nw
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel
from nw.constants import nwLabels from nw.constants import nwLabels
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -32,7 +32,6 @@ class GuiDocDetails(QFrame):
logger.debug("Initialising DocDetails ...") logger.debug("Initialising DocDetails ...")
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.debugGUI = self.mainConf.debugGUI
self.theParent = theParent self.theParent = theParent
self.theProject = theProject self.theProject = theProject
+120 -69
View File
@@ -15,18 +15,21 @@ import nw
from time import time from time import time
from PyQt5.QtCore import Qt, QTimer, QSizeF from PyQt5.QtCore import Qt, QTimer, QSizeF
from PyQt5.QtWidgets import qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox from PyQt5.QtWidgets import (
from PyQt5.QtGui import ( qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox
QTextCursor, QTextOption, QIcon, QKeySequence, QFont, QColor, QPalette, QTextDocument, )
from PyQt5.QtGui import (
QTextCursor, QTextOption, QIcon, QKeySequence, QFont, QColor,
QPalette, QTextDocument,
) )
from nw.project.document import NWDoc from nw.project.document import NWDoc
from nw.gui.tools.dochighlight import GuiDocHighlighter from nw.gui.tools.dochighlight import GuiDocHighlighter
from nw.gui.tools.wordcounter import WordCounter from nw.gui.tools.wordcounter import WordCounter
from nw.tools.spellcheck import NWSpellCheck from nw.tools.spellcheck import NWSpellCheck
from nw.constants import nwFiles, nwUnicode from nw.constants import nwFiles, nwUnicode
from nw.enum import nwDocAction, nwAlert from nw.enum import nwDocAction, nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -82,9 +85,24 @@ class GuiDocEditor(QTextEdit):
self.setAcceptRichText(False) self.setAcceptRichText(False)
# Custom Shortcuts # Custom Shortcuts
QShortcut(QKeySequence("Ctrl+."), self, context=Qt.WidgetShortcut, activated=self._openSpellContext) QShortcut(
QShortcut(Qt.Key_Return | Qt.ControlModifier, self, context=Qt.WidgetShortcut, activated=self._followTag) QKeySequence("Ctrl+."),
QShortcut(Qt.Key_Enter | Qt.ControlModifier, self, context=Qt.WidgetShortcut, activated=self._followTag) self,
context=Qt.WidgetShortcut,
activated=self._openSpellContext
)
QShortcut(
Qt.Key_Return | Qt.ControlModifier,
self,
context=Qt.WidgetShortcut,
activated=self._followTag
)
QShortcut(
Qt.Key_Enter | Qt.ControlModifier,
self,
context=Qt.WidgetShortcut,
activated=self._followTag
)
# Set Up Word Count Thread and Timer # Set Up Word Count Thread and Timer
self.wcInterval = self.mainConf.wordCountTimer self.wcInterval = self.mainConf.wordCountTimer
@@ -121,9 +139,9 @@ class GuiDocEditor(QTextEdit):
return True return True
def initEditor(self): def initEditor(self):
"""Initialise or re-initialise the editor with the user's settings. """Initialise or re-initialise the editor with the user's
This function is both called when the editor is created, and when the user changes the settings. This function is both called when the editor is
main editor preferences. created, and when the user changes the main editor preferences.
""" """
# Reload dictionaries # Reload dictionaries
@@ -164,10 +182,12 @@ class GuiDocEditor(QTextEdit):
# Initialise the syntax highlighter # Initialise the syntax highlighter
self.hLight.initHighlighter() self.hLight.initHighlighter()
# If we have a document open, we should reload it in case the font changed, otherwise # If we have a document open, we should reload it in case the
# we just clear the editor entirely, which makes it read only. # font changed, otherwise we just clear the editor entirely,
# which makes it read only.
if self.theHandle is not None: if self.theHandle is not None:
# We must save the current handle as clearEditor() sets it to None # We must save the current handle as clearEditor() sets it
# to None
tHandle = self.theHandle tHandle = self.theHandle
self.clearEditor() self.clearEditor()
self.loadText(tHandle) self.loadText(tHandle)
@@ -178,11 +198,13 @@ class GuiDocEditor(QTextEdit):
return True return True
def loadText(self, tHandle): def loadText(self, tHandle):
"""Load text from a document into the editor. If we have an io error, we must handle this """Load text from a document into the editor. If we have an io
and clear the editor so that we don't risk overwriting the file if it exists. This can for error, we must handle this and clear the editor so that we don't
instance happen of the file contains binary elements or an encoding that novelWriter does risk overwriting the file if it exists. This can for instance
not support. If load is successful, ot the document is new (empty string) we set up the happen of the file contains binary elements or an encoding that
editor for editing the file. novelWriter does not support. If load is successful, or the
document is new (empty string) we set up the editor for editing
the file.
""" """
theDoc = self.nwDocument.openDocument(tHandle) theDoc = self.nwDocument.openDocument(tHandle)
@@ -236,8 +258,9 @@ class GuiDocEditor(QTextEdit):
return self.docChanged return self.docChanged
def getText(self): def getText(self):
"""Get the text content of the current document. This method uses QTextEdit->toPlainText for """Get the text content of the current document. This method
Qt versions lower than 5.9, and the QDocument->toRawText for higher version. The latter uses QTextEdit->toPlainText for Qt versions lower than 5.9, and
the QDocument->toRawText for higher version. The latter
preserves non-breaking spaces, which the former does not. preserves non-breaking spaces, which the former does not.
""" """
if self.mainConf.verQtValue >= 50900: if self.mainConf.verQtValue >= 50900:
@@ -281,8 +304,8 @@ class GuiDocEditor(QTextEdit):
## ##
def changeWidth(self): def changeWidth(self):
"""Automatically adjust the margins so the text is centred, but only if Config.textFixedW is """Automatically adjust the margins so the text is centred, but
set to True. only if Config.textFixedW is set to True.
""" """
if self.mainConf.textFixedW: if self.mainConf.textFixedW:
vBar = self.verticalScrollBar() vBar = self.verticalScrollBar()
@@ -307,23 +330,40 @@ class GuiDocEditor(QTextEdit):
if not self.theParent.hasProject: if not self.theParent.hasProject:
logger.error("No project open") logger.error("No project open")
return False return False
if theAction == nwDocAction.UNDO: self.undo() if theAction == nwDocAction.UNDO:
elif theAction == nwDocAction.REDO: self.redo() self.undo()
elif theAction == nwDocAction.CUT: self.cut() elif theAction == nwDocAction.REDO:
elif theAction == nwDocAction.COPY: self.copy() self.redo()
elif theAction == nwDocAction.PASTE: self.paste() elif theAction == nwDocAction.CUT:
elif theAction == nwDocAction.BOLD: self._wrapSelection("**","**") self.cut()
elif theAction == nwDocAction.ITALIC: self._wrapSelection("_","_") elif theAction == nwDocAction.COPY:
elif theAction == nwDocAction.U_LINE: self._wrapSelection("__","__") self.copy()
elif theAction == nwDocAction.S_QUOTE: self._wrapSelection(self.typSQOpen,self.typSQClose) elif theAction == nwDocAction.PASTE:
elif theAction == nwDocAction.D_QUOTE: self._wrapSelection(self.typDQOpen,self.typDQClose) self.paste()
elif theAction == nwDocAction.SEL_ALL: self._makeSelection(QTextCursor.Document) elif theAction == nwDocAction.BOLD:
elif theAction == nwDocAction.SEL_PARA: self._makeSelection(QTextCursor.BlockUnderCursor) self._wrapSelection("**","**")
elif theAction == nwDocAction.FIND: self._beginSearch() elif theAction == nwDocAction.ITALIC:
elif theAction == nwDocAction.REPLACE: self._beginReplace() self._wrapSelection("_","_")
elif theAction == nwDocAction.GO_NEXT: self._findNext() elif theAction == nwDocAction.U_LINE:
elif theAction == nwDocAction.GO_PREV: self._findPrev() self._wrapSelection("__","__")
elif theAction == nwDocAction.REPL_NEXT: self._replaceNext() elif theAction == nwDocAction.S_QUOTE:
self._wrapSelection(self.typSQOpen,self.typSQClose)
elif theAction == nwDocAction.D_QUOTE:
self._wrapSelection(self.typDQOpen,self.typDQClose)
elif theAction == nwDocAction.SEL_ALL:
self._makeSelection(QTextCursor.Document)
elif theAction == nwDocAction.SEL_PARA:
self._makeSelection(QTextCursor.BlockUnderCursor)
elif theAction == nwDocAction.FIND:
self._beginSearch()
elif theAction == nwDocAction.REPLACE:
self._beginReplace()
elif theAction == nwDocAction.GO_NEXT:
self._findNext()
elif theAction == nwDocAction.GO_PREV:
self._findPrev()
elif theAction == nwDocAction.REPL_NEXT:
self._replaceNext()
else: else:
logger.error("Unknown or unsupported document action %s" % str(theAction)) logger.error("Unknown or unsupported document action %s" % str(theAction))
return False return False
@@ -351,13 +391,15 @@ class GuiDocEditor(QTextEdit):
def keyPressEvent(self, keyEvent): def keyPressEvent(self, keyEvent):
"""Intercept key press events. """Intercept key press events.
We need to intercept key presses briefly to record the state of selection. This is in order We need to intercept key presses briefly to record the state of
to know whether we had a selection prior to triggering the _docChange slot, as we do not selection. This is in order to know whether we had a selection
want to trigger autoreplace on selections. Autoreplace on selections messes with undo/redo prior to triggering the _docChange slot, as we do not want to
history. trigger autoreplace on selections. Autoreplace on selections
We also need to intercept the Shift key modifier for certain key combinations that modifies messes with undo/redo history.
standard keys like enter and space. However, we don't want to spend a lot of time in this We also need to intercept the Shift key modifier for certain key
function as it is triggered on every keypress when typing. combinations that modifies standard keys like enter and space.
However, we don't want to spend a lot of time in this function
as it is triggered on every keypress when typing.
""" """
self.hasSelection = self.textCursor().hasSelection() self.hasSelection = self.textCursor().hasSelection()
@@ -378,8 +420,9 @@ class GuiDocEditor(QTextEdit):
return return
def mouseReleaseEvent(self, mEvent): def mouseReleaseEvent(self, mEvent):
"""If the mouse button is released and the control key is pressed, check if we're clicking """If the mouse button is released and the control key is
on a tag, and trigger the follow tag function. pressed, check if we're clicking on a tag, and trigger the
follow tag function.
""" """
if qApp.keyboardModifiers() == Qt.ControlModifier: if qApp.keyboardModifiers() == Qt.ControlModifier:
theCursor = self.cursorForPosition(mEvent.pos()) theCursor = self.cursorForPosition(mEvent.pos())
@@ -392,9 +435,11 @@ class GuiDocEditor(QTextEdit):
## ##
def _followTag(self, theCursor=None): def _followTag(self, theCursor=None):
"""Activated by Ctrl+Enter. Checks that we're in a block starting with '@'. We then find the """Activated by Ctrl+Enter. Checks that we're in a block
word under the cursor and check that it is after the ':'. If all this is fine, we have a tag starting with '@'. We then find the word under the cursor and
and can tell the document viewer to try and find and load the file where the tag is defined. check that it is after the ':'. If all this is fine, we have a
tag and can tell the document viewer to try and find and load
the file where the tag is defined.
""" """
if theCursor is None: if theCursor is None:
@@ -459,7 +504,9 @@ class GuiDocEditor(QTextEdit):
if len(theSuggest) > 0: if len(theSuggest) > 0:
for aWord in theSuggest: for aWord in theSuggest:
mnuWord = QAction(aWord, mnuSuggest) mnuWord = QAction(aWord, mnuSuggest)
mnuWord.triggered.connect(lambda thePos, aWord=aWord : self._correctWord(theCursor, aWord)) mnuWord.triggered.connect(
lambda thePos, aWord=aWord : self._correctWord(theCursor, aWord)
)
mnuSuggest.addAction(mnuWord) mnuSuggest.addAction(mnuWord)
mnuSuggest.addSeparator() mnuSuggest.addSeparator()
mnuAdd = QAction("Add Word to Dictionary", mnuSuggest) mnuAdd = QAction("Add Word to Dictionary", mnuSuggest)
@@ -557,7 +604,8 @@ class GuiDocEditor(QTextEdit):
return return
def _runCounter(self): def _runCounter(self):
"""Decide whether to run the word counter, or stop the timer due to inactivity. """Decide whether to run the word counter, or stop the timer due
to inactivity.
""" """
sinceActive = time()-self.lastEdit sinceActive = time()-self.lastEdit
if sinceActive > 5*self.wcInterval: if sinceActive > 5*self.wcInterval:
@@ -586,9 +634,10 @@ class GuiDocEditor(QTextEdit):
return return
def _wrapSelection(self, tBefore, tAfter): def _wrapSelection(self, tBefore, tAfter):
"""Wraps the selected text in whatever is in tBefore and tAfter. If there is no selection, """Wraps the selected text in whatever is in tBefore and tAfter.
the autoSelect setting decides the action. AutoSelect will select the word under the cursor If there is no selection, the autoSelect setting decides the
before wrapping it. If this feature is disabled, nothing is done. action. AutoSelect will select the word under the cursor before
wrapping it. If this feature is disabled, nothing is done.
""" """
theCursor = self.textCursor() theCursor = self.textCursor()
if self.mainConf.autoSelect and not theCursor.hasSelection(): if self.mainConf.autoSelect and not theCursor.hasSelection():
@@ -626,18 +675,19 @@ class GuiDocEditor(QTextEdit):
return return
def _beginReplace(self): def _beginReplace(self):
"""Opens the replace line of the search bar and sets the replace text. """Opens the replace line of the search bar and sets the replace
text.
""" """
self._beginSearch() self._beginSearch()
self.theParent.searchBar.setReplaceText("") self.theParent.searchBar.setReplaceText("")
return return
def _findNext(self): def _findNext(self):
"""Searches for the next occurrence of the search bar text in the document. """Searches for the next occurrence of the search bar text in
Wraps back to the top if not found. the document. Wraps back to the top if not found.
""" """
searchFor = self.theParent.searchBar.getSearchText() searchFor = self.theParent.searchBar.getSearchText()
wasFound = self.find(searchFor) wasFound = self.find(searchFor)
if not wasFound: if not wasFound:
theCursor = self.textCursor() theCursor = self.textCursor()
theCursor.movePosition(QTextCursor.Start) theCursor.movePosition(QTextCursor.Start)
@@ -645,8 +695,8 @@ class GuiDocEditor(QTextEdit):
return return
def _findPrev(self): def _findPrev(self):
"""Searches for the previous occurrence of the search bar text in the document. """Searches for the previous occurrence of the search bar text
Wraps back to the end if not found. in the document. Wraps back to the end if not found.
""" """
searchFor = self.theParent.searchBar.getSearchText() searchFor = self.theParent.searchBar.getSearchText()
wasFound = self.find(searchFor, QTextDocument.FindBackward) wasFound = self.find(searchFor, QTextDocument.FindBackward)
@@ -657,8 +707,9 @@ class GuiDocEditor(QTextEdit):
return return
def _replaceNext(self): def _replaceNext(self):
"""Searches for the next occurrence of the search bar text in the document and replaces it """Searches for the next occurrence of the search bar text in
with the replace text. Wraps back to the top if not found. the document and replaces it with the replace text. Wraps back
to the top if not found.
""" """
theCursor = self.textCursor() theCursor = self.textCursor()
searchFor = self.theParent.searchBar.getSearchText() searchFor = self.theParent.searchBar.getSearchText()
+34 -26
View File
@@ -13,13 +13,15 @@
import logging import logging
import nw import nw
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QIcon, QFont, QColor from PyQt5.QtGui import QIcon, QFont, QColor
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, QApplication from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QApplication
)
from nw.project.item import NWItem from nw.project.item import NWItem
from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert from nw.constants import nwLabels
from nw.constants import nwLabels from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -35,7 +37,6 @@ class GuiDocTree(QTreeWidget):
logger.debug("Initialising DocTree ...") logger.debug("Initialising DocTree ...")
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.debugGUI = self.mainConf.debugGUI
self.theParent = theParent self.theParent = theParent
self.theTheme = theParent.theTheme self.theTheme = theParent.theTheme
self.theProject = theProject self.theProject = theProject
@@ -52,8 +53,7 @@ class GuiDocTree(QTreeWidget):
self.setIndentation(13) self.setIndentation(13)
self.setColumnCount(4) self.setColumnCount(4)
self.setHeaderLabels(["Label","Words","Flags","Handle"]) self.setHeaderLabels(["Label","Words","Flags","Handle"])
if not self.debugGUI: self.hideColumn(self.C_HANDLE)
self.hideColumn(self.C_HANDLE)
treeHead = self.headerItem() treeHead = self.headerItem()
treeHead.setTextAlignment(self.C_COUNT,Qt.AlignRight) treeHead.setTextAlignment(self.C_COUNT,Qt.AlignRight)
@@ -131,7 +131,8 @@ class GuiDocTree(QTreeWidget):
tHandle = self.theProject.newRoot(nwLabels.CLASS_NAME[itemClass], itemClass) tHandle = self.theProject.newRoot(nwLabels.CLASS_NAME[itemClass], itemClass)
else: else:
# If no parent has been selected, make the new file under the root NOVEL item. # If no parent has been selected, make the new file under
# the root NOVEL item.
if pHandle is None: if pHandle is None:
pHandle = self.theProject.findRootItem(nwItemClass.NOVEL) pHandle = self.theProject.findRootItem(nwItemClass.NOVEL)
@@ -140,18 +141,23 @@ class GuiDocTree(QTreeWidget):
logger.error("Did not find anywhere to add the item!") logger.error("Did not find anywhere to add the item!")
return False return False
# Now check if the selected item is a file, in which case the new file will be a sibling # Now check if the selected item is a file, in which case
# the new file will be a sibling
pItem = self.theProject.getItem(pHandle) pItem = self.theProject.getItem(pHandle)
if pItem.itemType == nwItemType.FILE: if pItem.itemType == nwItemType.FILE:
pHandle = pItem.parHandle pHandle = pItem.parHandle
# If we again has no home, give up # If we again has no home, give up
if pHandle is None: if pHandle is None:
self.makeAlert("Did not find anywhere to add the file or folder!", nwAlert.ERROR) self.makeAlert(
"Did not find anywhere to add the file or folder!", nwAlert.ERROR
)
return False return False
if pHandle == self.theProject.trashRoot: if pHandle == self.theProject.trashRoot:
self.makeAlert("Cannot add new files or folders to the trash folder.", nwAlert.ERROR) self.makeAlert(
"Cannot add new files or folders to the trash folder.", nwAlert.ERROR
)
return False return False
# If we're still here, add the file or folder # If we're still here, add the file or folder
@@ -175,8 +181,8 @@ class GuiDocTree(QTreeWidget):
return True return True
def moveTreeItem(self, nStep): def moveTreeItem(self, nStep):
"""Move an item up or down in the tree, but only if the treeView has focus. This also """Move an item up or down in the tree, but only if the treeView
applies when the menu is used. has focus. This also applies when the menu is used.
""" """
if QApplication.focusWidget() == self and self.theParent.hasProject: if QApplication.focusWidget() == self and self.theParent.hasProject:
tHandle = self.getSelectedHandle() tHandle = self.getSelectedHandle()
@@ -223,10 +229,11 @@ class GuiDocTree(QTreeWidget):
return retVals return retVals
def deleteItem(self, tHandle=None): def deleteItem(self, tHandle=None):
"""Delete items from the tree. Note that this does not delete the item from the item tree in """Delete items from the tree. Note that this does not delete
the project object. However, since this is only meta data, there isn't really a need to do the item from the item tree in the project object. However,
that to save memory. Items not in the tree are not saved to the project file, so a loaded since this is only meta data, there isn't really a need to do
project will be clean anyway. that to save memory. Items not in the tree are not saved to the
project file, so a loaded project will be clean anyway.
""" """
if tHandle is None: if tHandle is None:
@@ -297,10 +304,10 @@ class GuiDocTree(QTreeWidget):
tStatus += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout] tStatus += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout]
iStatus = nwItem.itemStatus iStatus = nwItem.itemStatus
if tClass == nwItemClass.NOVEL: if tClass == nwItemClass.NOVEL:
iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's a valid index iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid
flagIcon = self.theParent.statusIcons[iStatus] flagIcon = self.theParent.statusIcons[iStatus]
else: else:
iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's a valid index iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid
flagIcon = self.theParent.importIcons[iStatus] flagIcon = self.theParent.importIcons[iStatus]
trItem.setText(self.C_NAME, tName) trItem.setText(self.C_NAME, tName)
@@ -451,8 +458,9 @@ class GuiDocTree(QTreeWidget):
return return
def _updateItemParent(self, tHandle): def _updateItemParent(self, tHandle):
"""Update the parent handle of an item so that the information in the project is consistent """Update the parent handle of an item so that the information
with the treeView. Also move the word count over to the new parent tree. in the project is consistent with the treeView. Also move the
word count over to the new parent tree.
""" """
trItemS = self._getTreeItem(tHandle) trItemS = self._getTreeItem(tHandle)
@@ -494,8 +502,8 @@ class GuiDocTree(QTreeWidget):
## ##
def mousePressEvent(self, theEvent): def mousePressEvent(self, theEvent):
"""Overload mousePressEvent to clear selection if clicking the mouse in a blank """Overload mousePressEvent to clear selection if clicking the
area of the tree view. mouse in a blank area of the tree view.
""" """
QTreeWidget.mousePressEvent(self, theEvent) QTreeWidget.mousePressEvent(self, theEvent)
selItem = self.indexAt(theEvent.pos()) selItem = self.indexAt(theEvent.pos())
@@ -504,8 +512,8 @@ class GuiDocTree(QTreeWidget):
return return
def dropEvent(self, theEvent): def dropEvent(self, theEvent):
"""Overload the drop of dragged item event to check whether the drop is allowed """Overload the drop of dragged item event to check whether the
or not. Disallowed drops are cancelled. drop is allowed or not. Disallowed drops are cancelled.
""" """
sHandle = self.getSelectedHandle() sHandle = self.getSelectedHandle()
if sHandle is None: if sHandle is None:
+22 -22
View File
@@ -13,13 +13,13 @@
import logging import logging
import nw import nw
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QTextBrowser from PyQt5.QtWidgets import QTextBrowser
from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor
from nw.convert.tokenizer import Tokenizer from nw.convert.tokenizer import Tokenizer
from nw.convert.text.tohtml import ToHtml from nw.convert.text.tohtml import ToHtml
from nw.enum import nwAlert, nwItemType from nw.enum import nwAlert, nwItemType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -205,24 +205,24 @@ class GuiDocViewer(QTextBrowser):
).format( ).format(
textSize = self.mainConf.textSize, textSize = self.mainConf.textSize,
preSize = self.mainConf.textSize*0.9, preSize = self.mainConf.textSize*0.9,
tColR = self.theTheme.colText[0], tColR = self.theTheme.colText[0],
tColG = self.theTheme.colText[1], tColG = self.theTheme.colText[1],
tColB = self.theTheme.colText[2], tColB = self.theTheme.colText[2],
hColR = self.theTheme.colHead[0], hColR = self.theTheme.colHead[0],
hColG = self.theTheme.colHead[1], hColG = self.theTheme.colHead[1],
hColB = self.theTheme.colHead[2], hColB = self.theTheme.colHead[2],
cColR = self.theTheme.colComm[0], cColR = self.theTheme.colComm[0],
cColG = self.theTheme.colComm[1], cColG = self.theTheme.colComm[1],
cColB = self.theTheme.colComm[2], cColB = self.theTheme.colComm[2],
eColR = self.theTheme.colEmph[0], eColR = self.theTheme.colEmph[0],
eColG = self.theTheme.colEmph[1], eColG = self.theTheme.colEmph[1],
eColB = self.theTheme.colEmph[2], eColB = self.theTheme.colEmph[2],
aColR = self.theTheme.colVal[0], aColR = self.theTheme.colVal[0],
aColG = self.theTheme.colVal[1], aColG = self.theTheme.colVal[1],
aColB = self.theTheme.colVal[2], aColB = self.theTheme.colVal[2],
kColR = self.theTheme.colKey[0], kColR = self.theTheme.colKey[0],
kColG = self.theTheme.colKey[1], kColG = self.theTheme.colKey[1],
kColB = self.theTheme.colKey[2], kColB = self.theTheme.colKey[2],
) )
self.qDocument.setDefaultStyleSheet(styleSheet) self.qDocument.setDefaultStyleSheet(styleSheet)
+4 -3
View File
@@ -13,8 +13,8 @@
import logging import logging
import nw import nw
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPalette, QColor from PyQt5.QtGui import QPalette, QColor
from PyQt5.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton from PyQt5.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -36,7 +36,8 @@ class GuiNoticeBar(QFrame):
self.mainBox = QHBoxLayout(self) self.mainBox = QHBoxLayout(self)
self.mainBox.setContentsMargins(8,2,2,2) self.mainBox.setContentsMargins(8,2,2,2)
self.noteLabel = QLabel("Hi there!") self.noteLabel = QLabel("Hi there!")
self.closeButton = QPushButton(self.theTheme.getIcon("close"),"") self.closeButton = QPushButton(self.theTheme.getIcon("close"),"")
self.closeButton.clicked.connect(self.hideNote) self.closeButton.clicked.connect(self.hideNote)
+6 -4
View File
@@ -13,11 +13,13 @@
import logging import logging
import nw import nw
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel, QLineEdit, QPushButton, QApplication from PyQt5.QtWidgets import (
QFrame, QGridLayout, QLabel, QLineEdit, QPushButton, QApplication
)
from nw.enum import nwDocAction from nw.enum import nwDocAction
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+4 -5
View File
@@ -13,11 +13,11 @@
import logging import logging
import nw import nw
from PyQt5.QtCore import Qt, QSize from PyQt5.QtCore import Qt, QSize
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QWidget, QHBoxLayout, QVBoxLayout, QLabel, QGroupBox, QScrollArea, QFrame, QToolButton, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QGroupBox, QScrollArea, QFrame,
QSizePolicy, QCheckBox, QGridLayout QToolButton, QSizePolicy, QCheckBox, QGridLayout
) )
from nw.constants import nwLabels from nw.constants import nwLabels
@@ -31,7 +31,6 @@ class GuiDocViewDetails(QWidget):
logger.debug("Initialising DocViewDetails ...") logger.debug("Initialising DocViewDetails ...")
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.debugGUI = self.mainConf.debugGUI
self.theParent = theParent self.theParent = theParent
self.theProject = theProject self.theProject = theProject
self.currHandle = None self.currHandle = None
+12 -7
View File
@@ -13,11 +13,11 @@
import logging import logging
import nw import nw
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QIcon, QDesktopServices from PyQt5.QtGui import QIcon, QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox
from nw.enum import nwItemType, nwItemClass, nwDocAction from nw.enum import nwItemType, nwItemClass, nwDocAction
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -78,7 +78,9 @@ class GuiMainMenu(QMenuBar):
recentProject = self.mainConf.recentList[n] recentProject = self.mainConf.recentList[n]
if recentProject == "": continue if recentProject == "": continue
menuItem = QAction("%s" % recentProject, self.projMenu) menuItem = QAction("%s" % recentProject, self.projMenu)
menuItem.triggered.connect(lambda menuItem, n=n : self.openRecentProject(menuItem, n)) menuItem.triggered.connect(
lambda menuItem, n=n : self.openRecentProject(menuItem, n)
)
self.recentMenu.addAction(menuItem) self.recentMenu.addAction(menuItem)
self.recentMenu.addSeparator() self.recentMenu.addSeparator()
@@ -122,8 +124,9 @@ class GuiMainMenu(QMenuBar):
aboutMsg = ( aboutMsg = (
"<h3>About {name:s}</h3>" "<h3>About {name:s}</h3>"
"<p>Version: {version:s}<br>Release Date: {date:s}</p>" "<p>Version: {version:s}<br>Release Date: {date:s}</p>"
"<p>{name:s} is a markdown-like text editor designed for organising and writing novels. " "<p>{name:s} is a markdown-like text editor designed for organising "
"It is written in Python 3 with a Qt5 GUI, using PyQt5</p>" "and writing novels. It is written in Python 3 with a Qt5 GUI, "
"using PyQt5</p>"
"<p>{name:s} is licensed under GPL v3.0</p>" "<p>{name:s} is licensed under GPL v3.0</p>"
"<p>{copyright:s}</p>" "<p>{copyright:s}</p>"
"<p>Website: <a href='{website:s}'>{website:s}</a></p>" "<p>Website: <a href='{website:s}'>{website:s}</a></p>"
@@ -353,7 +356,9 @@ class GuiMainMenu(QMenuBar):
# Document > Show File Details # Document > Show File Details
menuItem = QAction("Show File Details", self) menuItem = QAction("Show File Details", self)
menuItem.setStatusTip("Shows a message box with the document location in the project folder") menuItem.setStatusTip(
"Shows a message box with the document location in the project folder"
)
menuItem.triggered.connect(self._showDocumentLocation) menuItem.triggered.connect(self._showDocumentLocation)
self.docuMenu.addAction(menuItem) self.docuMenu.addAction(menuItem)
+4 -16
View File
@@ -13,9 +13,10 @@
import logging import logging
import nw import nw
from time import time from time import time
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QColor, QPixmap from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QColor, QPixmap
from PyQt5.QtWidgets import QStatusBar, QLabel, QFrame from PyQt5.QtWidgets import QStatusBar, QLabel, QFrame
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -60,9 +61,6 @@ class GuiMainStatus(QStatusBar):
self.docChanged.setFixedWidth(16) self.docChanged.setFixedWidth(16)
self.docChanged.setToolTip("Document Changes Saved") self.docChanged.setToolTip("Document Changes Saved")
self.boxDocHandle = QLabel()
self.boxDocHandle.setFrameStyle(QFrame.Panel | QFrame.Sunken);
# Add Them # Add Them
self.addPermanentWidget(self.docChanged) self.addPermanentWidget(self.docChanged)
self.addPermanentWidget(self.boxCounts) self.addPermanentWidget(self.boxCounts)
@@ -70,8 +68,6 @@ class GuiMainStatus(QStatusBar):
self.addPermanentWidget(self.projChanged) self.addPermanentWidget(self.projChanged)
self.addPermanentWidget(self.boxStats) self.addPermanentWidget(self.boxStats)
self.addPermanentWidget(self.boxTime) self.addPermanentWidget(self.boxTime)
if self.mainConf.debugGUI:
self.addPermanentWidget(self.boxDocHandle)
self.setSizeGripEnabled(True) self.setSizeGripEnabled(True)
@@ -90,7 +86,6 @@ class GuiMainStatus(QStatusBar):
self.setRefTime(None) self.setRefTime(None)
self.setStats(0,0) self.setStats(0,0)
self.setCounts(0,0,0) self.setCounts(0,0,0)
self.setDocHandle(None)
self.setProjectStatus(None) self.setProjectStatus(None)
self.setDocumentStatus(None) self.setDocumentStatus(None)
self._updateTime() self._updateTime()
@@ -134,13 +129,6 @@ class GuiMainStatus(QStatusBar):
self.boxCounts.setText("<b>Document:</b> {:d} : {:d} : {:d}".format(cC,wC,pC)) self.boxCounts.setText("<b>Document:</b> {:d} : {:d} : {:d}".format(cC,wC,pC))
return return
def setDocHandle(self, theHandle):
if theHandle is None:
self.boxDocHandle.setText("0000000000000")
else:
self.boxDocHandle.setText("%13s" % theHandle)
return
## ##
# Internal Functions # Internal Functions
## ##
+16 -14
View File
@@ -14,7 +14,9 @@ import logging
import nw import nw
from PyQt5.QtCore import Qt, QRegularExpression from PyQt5.QtCore import Qt, QRegularExpression
from PyQt5.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush from PyQt5.QtGui import (
QColor, QTextCharFormat, QFont, QSyntaxHighlighter, QBrush
)
from nw.constants import nwUnicode from nw.constants import nwUnicode
@@ -38,18 +40,18 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.hRules = [] self.hRules = []
self.hStyles = {} self.hStyles = {}
self.colHead = QColor(0,0,0) self.colHead = QColor(0,0,0)
self.colHeadH = QColor(0,0,0) self.colHeadH = QColor(0,0,0)
self.colEmph = QColor(0,0,0) self.colEmph = QColor(0,0,0)
self.colDialN = QColor(0,0,0) self.colDialN = QColor(0,0,0)
self.colDialD = QColor(0,0,0) self.colDialD = QColor(0,0,0)
self.colDialS = QColor(0,0,0) self.colDialS = QColor(0,0,0)
self.colComm = QColor(0,0,0) self.colComm = QColor(0,0,0)
self.colKey = QColor(0,0,0) self.colKey = QColor(0,0,0)
self.colVal = QColor(0,0,0) self.colVal = QColor(0,0,0)
self.colSpell = QColor(0,0,0) self.colSpell = QColor(0,0,0)
self.colTagErr = QColor(0,0,0) self.colTagErr = QColor(0,0,0)
self.colRepTag = QColor(0,0,0) self.colRepTag = QColor(0,0,0)
self.initHighlighter() self.initHighlighter()
@@ -196,7 +198,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
)) ))
self.hRules.append(( self.hRules.append((
"<(\S+?)>", { r"<(\S+?)>", {
0 : self.hStyles["replace"], 0 : self.hStyles["replace"],
} }
)) ))
+83 -51
View File
@@ -16,37 +16,37 @@ import nw
from os import path from os import path
from PyQt5.QtCore import Qt, QTimer from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QPixmap, QColor from PyQt5.QtGui import QIcon, QPixmap, QColor
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, qApp, QWidget, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog,
QShortcut, QMessageBox, QProgressDialog, QDialog QShortcut, QMessageBox, QProgressDialog, QDialog
) )
from nw.gui.mainmenu import GuiMainMenu from nw.gui.mainmenu import GuiMainMenu
from nw.gui.statusbar import GuiMainStatus from nw.gui.statusbar import GuiMainStatus
from nw.gui.elements.doctree import GuiDocTree from nw.gui.elements.doctree import GuiDocTree
from nw.gui.elements.doceditor import GuiDocEditor from nw.gui.elements.doceditor import GuiDocEditor
from nw.gui.elements.docviewer import GuiDocViewer from nw.gui.elements.docviewer import GuiDocViewer
from nw.gui.elements.docdetails import GuiDocDetails from nw.gui.elements.docdetails import GuiDocDetails
from nw.gui.elements.searchbar import GuiSearchBar from nw.gui.elements.searchbar import GuiSearchBar
from nw.gui.elements.noticebar import GuiNoticeBar from nw.gui.elements.noticebar import GuiNoticeBar
from nw.gui.elements.viewdetails import GuiDocViewDetails from nw.gui.elements.viewdetails import GuiDocViewDetails
from nw.gui.dialogs.configeditor import GuiConfigEditor from nw.gui.dialogs.configeditor import GuiConfigEditor
from nw.gui.dialogs.projecteditor import GuiProjectEditor from nw.gui.dialogs.projecteditor import GuiProjectEditor
from nw.gui.dialogs.export import GuiExport from nw.gui.dialogs.export import GuiExport
from nw.gui.dialogs.itemeditor import GuiItemEditor from nw.gui.dialogs.itemeditor import GuiItemEditor
from nw.gui.dialogs.timelineview import GuiTimeLineView from nw.gui.dialogs.timelineview import GuiTimeLineView
from nw.gui.dialogs.sessionlog import GuiSessionLogView from nw.gui.dialogs.sessionlog import GuiSessionLogView
from nw.project.project import NWProject from nw.project.project import NWProject
from nw.project.document import NWDoc from nw.project.document import NWDoc
from nw.project.item import NWItem from nw.project.item import NWItem
from nw.project.index import NWIndex from nw.project.index import NWIndex
from nw.project.backup import NWBackup from nw.project.backup import NWBackup
from nw.tools.wordcount import countWords from nw.tools.wordcount import countWords
from nw.theme import Theme from nw.theme import Theme
from nw.enum import nwItemType, nwAlert from nw.enum import nwItemType, nwAlert
from nw.constants import nwFiles from nw.constants import nwFiles
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -63,10 +63,18 @@ class GuiMain(QMainWindow):
self.theIndex = NWIndex(self.theProject, self) self.theIndex = NWIndex(self.theProject, self)
self.hasProject = False self.hasProject = False
logger.info("OS: %s" % (self.mainConf.osType)) logger.info("OS: %s" % (
logger.info("Qt5 Version: %s (%d)" % (self.mainConf.verQtString, self.mainConf.verQtValue)) self.mainConf.osType)
logger.info("PyQt5 Version: %s (%d)" % (self.mainConf.verPyQtString, self.mainConf.verPyQtValue)) )
logger.info("Python Version: %s (0x%x)" % (self.mainConf.verPyString, self.mainConf.verPyHexVal)) logger.info("Qt5 Version: %s (%d)" % (
self.mainConf.verQtString, self.mainConf.verQtValue)
)
logger.info("PyQt5 Version: %s (%d)" % (
self.mainConf.verPyQtString, self.mainConf.verPyQtValue)
)
logger.info("Python Version: %s (0x%x)" % (
self.mainConf.verPyString, self.mainConf.verPyHexVal)
)
self.resize(*self.mainConf.winGeometry) self.resize(*self.mainConf.winGeometry)
self._setWindowTitle() self._setWindowTitle()
@@ -157,11 +165,20 @@ class GuiMain(QMainWindow):
self.asDocTimer.timeout.connect(self._autoSaveDocument) self.asDocTimer.timeout.connect(self._autoSaveDocument)
# Keyboard Shortcuts # Keyboard Shortcuts
QShortcut(Qt.Key_Return, self.treeView, context=Qt.WidgetShortcut, activated=self._treeKeyPressReturn) QShortcut(
QShortcut(Qt.Key_Escape, self, activated=self._keyPressEscape) Qt.Key_Return,
self.treeView,
context=Qt.WidgetShortcut,
activated=self._treeKeyPressReturn
)
QShortcut(
Qt.Key_Escape,
self,
activated=self._keyPressEscape
)
# Forward Functions # Forward Functions
self.setStatus = self.statusBar.setStatus self.setStatus = self.statusBar.setStatus
self.setProjectStatus = self.statusBar.setProjectStatus self.setProjectStatus = self.statusBar.setProjectStatus
if self.mainConf.showGUI: if self.mainConf.showGUI:
@@ -227,7 +244,8 @@ class GuiMain(QMainWindow):
def closeProject(self, isYes=False): def closeProject(self, isYes=False):
"""Closes the project if one is open. """Closes the project if one is open.
isYes is passed on from the close application event so the user doesn't get prompted twice. isYes is passed on from the close application event so the user
doesn't get prompted twice.
""" """
if not self.hasProject: if not self.hasProject:
# There is no project loaded, everything OK # There is no project loaded, everything OK
@@ -271,15 +289,17 @@ class GuiMain(QMainWindow):
return saveOK return saveOK
def openProject(self, projFile=None): def openProject(self, projFile=None):
"""Open a project. """Open a project. The parameter projFile is passed from the
projFile is passed from the open recent projects menu, so can be set. If not, we pop the dialog. open recent projects menu, so can be set. If not, we pop the
dialog.
""" """
if projFile is None: if projFile is None:
projFile = self.openProjectDialog() projFile = self.openProjectDialog()
if projFile is None: if projFile is None:
return False return False
# Make sure any open project is cleared out first before we load another one # Make sure any open project is cleared out first before we load
# another one
if not self.closeProject(): if not self.closeProject():
return False return False
@@ -415,20 +435,26 @@ class GuiMain(QMainWindow):
with open(loadFile,mode="rt",encoding="utf8") as inFile: with open(loadFile,mode="rt",encoding="utf8") as inFile:
theText = inFile.read() theText = inFile.read()
except Exception as e: except Exception as e:
self.makeAlert(["Could not read file. The file cannot be a binary file.",str(e)], nwAlert.ERROR) self.makeAlert(
["Could not read file. The file cannot be a binary file.",str(e)],
nwAlert.ERROR
)
return False return False
if self.docEditor.theHandle is None: if self.docEditor.theHandle is None:
self.makeAlert(["Please open a document to import the text file into."], nwAlert.ERROR) self.makeAlert(
["Please open a document to import the text file into."],
nwAlert.ERROR
)
return False return False
if not self.docEditor.isEmpty(): if not self.docEditor.isEmpty():
if self.mainConf.showGUI: if self.mainConf.showGUI:
msgBox = QMessageBox() msgBox = QMessageBox()
msgRes = msgBox.question( msgRes = msgBox.question(self, "Import Document",(
self, "Import Document", "Importing the file will overwrite the current content of the document. "
"Importing the file will overwrite the current content of the document. Do you want to proceed?" "Do you want to proceed?"
) ))
if msgRes != QMessageBox.Yes: if msgRes != QMessageBox.Yes:
return False return False
else: else:
@@ -552,7 +578,7 @@ class GuiMain(QMainWindow):
dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.ShowDirsOnly
dlgOpt |= QFileDialog.DontUseNativeDialog dlgOpt |= QFileDialog.DontUseNativeDialog
projPath = QFileDialog.getExistingDirectory( projPath = QFileDialog.getExistingDirectory(
self,"Save novelWriter Project","",options=dlgOpt self, "Save novelWriter Project", "", options=dlgOpt
) )
if projPath: if projPath:
return projPath return projPath
@@ -563,7 +589,7 @@ class GuiMain(QMainWindow):
dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.ShowDirsOnly
dlgOpt |= QFileDialog.DontUseNativeDialog dlgOpt |= QFileDialog.DontUseNativeDialog
projPath = QFileDialog.getExistingDirectory( projPath = QFileDialog.getExistingDirectory(
self,"Select Location for New novelWriter Project","",options=dlgOpt self, "Select Location for New novelWriter Project", "", options=dlgOpt
) )
if projPath: if projPath:
return projPath return projPath
@@ -606,8 +632,9 @@ class GuiMain(QMainWindow):
return True return True
def makeAlert(self, theMessage, theLevel=nwAlert.INFO): def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
"""Alert both the user and the logger at the same time. Message can be either a string or an """Alert both the user and the logger at the same time. Message
array of strings. Severity level is 0 = info, 1 = warning, and 2 = error. can be either a string or an array of strings. Severity level is
0 = info, 1 = warning, and 2 = error.
""" """
if isinstance(theMessage, list): if isinstance(theMessage, list):
@@ -701,7 +728,8 @@ class GuiMain(QMainWindow):
return True return True
def _autoSaveProject(self): def _autoSaveProject(self):
if self.hasProject and self.theProject.projChanged and self.theProject.projPath is not None: if (self.hasProject and self.theProject.projChanged and
self.theProject.projPath is not None):
logger.debug("Autosaving project") logger.debug("Autosaving project")
self.saveProject(isAuto=True) self.saveProject(isAuto=True)
return return
@@ -733,7 +761,8 @@ class GuiMain(QMainWindow):
## ##
def resizeEvent(self, theEvent): def resizeEvent(self, theEvent):
"""Extend QMainWindow.resizeEvent to signal dependent GUI elements that its pane may have changed size. """Extend QMainWindow.resizeEvent to signal dependent GUI
elements that its pane may have changed size.
""" """
QMainWindow.resizeEvent(self,theEvent) QMainWindow.resizeEvent(self,theEvent)
self.docEditor.changeWidth() self.docEditor.changeWidth()
@@ -779,7 +808,8 @@ class GuiMain(QMainWindow):
return return
def _keyPressEscape(self): def _keyPressEscape(self):
"""When the escape key is pressed somewhere in the main window, do the following, in order """When the escape key is pressed somewhere in the main window,
do the following, in order.
""" """
if self.searchBar.isVisible(): if self.searchBar.isVisible():
self.searchBar.setVisible(False) self.searchBar.setVisible(False)
@@ -787,13 +817,15 @@ class GuiMain(QMainWindow):
return return
def _splitMainMove(self, pWidth, pHeight): def _splitMainMove(self, pWidth, pHeight):
"""Alert dependent GUI elements that the main pane splitter has been moved. """Alert dependent GUI elements that the main pane splitter has
been moved.
""" """
self.docEditor.changeWidth() self.docEditor.changeWidth()
return return
def _splitViewMove(self, pWidth, pHeight): def _splitViewMove(self, pWidth, pHeight):
"""Alert dependent GUI elements that the main pane splitter has been moved. """Alert dependent GUI elements that the main pane splitter has
been moved.
""" """
self.docEditor.changeWidth() self.docEditor.changeWidth()
return return
+12 -5
View File
@@ -13,8 +13,8 @@
import logging import logging
import nw import nw
from os import path, mkdir, listdir from os import path, mkdir, listdir
from shutil import make_archive from shutil import make_archive
from datetime import datetime from datetime import datetime
from nw.enum import nwAlert from nw.enum import nwAlert
@@ -32,11 +32,15 @@ class NWBackup():
def zipIt(self): def zipIt(self):
if self.mainConf.backupPath is None: if self.mainConf.backupPath is None:
self.theParent.makeAlert("Cannot backup project because no backup path is set.",nwAlert.WARN) self.theParent.makeAlert(
"Cannot backup project because no backup path is set.",nwAlert.WARN
)
return False return False
if self.theProject.projName is None: if self.theProject.projName is None:
self.theParent.makeAlert("Cannot backup project because no project name is set.",nwAlert.WARN) self.theParent.makeAlert(
"Cannot backup project because no project name is set.",nwAlert.WARN
)
return False return False
logger.info("Backing up project") logger.info("Backing up project")
@@ -55,7 +59,10 @@ class NWBackup():
try: try:
make_archive(baseName, "zip", self.theProject.projPath, ".") make_archive(baseName, "zip", self.theProject.projPath, ".")
except Exception as e: except Exception as e:
self.theParent.makeAlert(["Could not write backup archive.",str(e)],nwAlert.ERROR) self.theParent.makeAlert(
["Could not write backup archive.",str(e)],
nwAlert.ERROR
)
return False return False
self.theParent.statusBar.setStatus("Project backup complete") self.theParent.statusBar.setStatus("Project backup complete")
+13 -9
View File
@@ -53,7 +53,8 @@ class NWDoc():
self.clearDocument() self.clearDocument()
return None return None
# By default, the document is editable. Except for files in the trash folder. # By default, the document is editable.
# Except for files in the trash folder.
self.docEditable = True self.docEditable = True
if self.theItem.parHandle == self.theProject.trashRoot: if self.theItem.parHandle == self.theProject.trashRoot:
self.docEditable = False self.docEditable = False
@@ -70,19 +71,19 @@ class NWDoc():
theDoc = inFile.read() theDoc = inFile.read()
except Exception as e: except Exception as e:
self.makeAlert(["Failed to open document file.",str(e)], nwAlert.ERROR) self.makeAlert(["Failed to open document file.",str(e)], nwAlert.ERROR)
# Note: Document must be cleared in case of an io error, or else the auto-save or # Note: Document must be cleared in case of an io error,
# save will try to overwrite it with an empty file. Return None to alert the caller. # or else the auto-save or save will try to overwrite it
# with an empty file. Return None to alert the caller.
self.clearDocument() self.clearDocument()
return None return None
else: else:
# The document file does not exist, so we assume it's a new document and initialise an # The document file does not exist, so we assume it's a new
# empty text string. # document and initialise an empty text string.
logger.debug("The requested document does not exist.") logger.debug("The requested document does not exist.")
return "" return ""
if showStatus: if showStatus:
self.theParent.statusBar.setStatus("Opened Document: %s" % self.theItem.itemName) self.theParent.statusBar.setStatus("Opened Document: %s" % self.theItem.itemName)
self.theParent.statusBar.setDocHandle(tHandle)
return theDoc return theDoc
@@ -102,9 +103,12 @@ class NWDoc():
docTemp = path.join(dataPath,docFile[:-3]+"tmp") docTemp = path.join(dataPath,docFile[:-3]+"tmp")
docBack = path.join(dataPath,docFile[:-3]+"bak") docBack = path.join(dataPath,docFile[:-3]+"bak")
if path.isfile(docTemp): unlink(docTemp) if path.isfile(docTemp):
if path.isfile(docBack): rename(docBack,docTemp) unlink(docTemp)
if path.isfile(docPath): rename(docPath,docBack) if path.isfile(docBack):
rename(docBack,docTemp)
if path.isfile(docPath):
rename(docPath,docBack)
try: try:
with open(docPath,mode="w",encoding="utf8") as outFile: with open(docPath,mode="w",encoding="utf8") as outFile:
+30 -23
View File
@@ -17,9 +17,9 @@ import nw
from os import path from os import path
from nw.project.document import NWDoc from nw.project.document import NWDoc
from nw.enum import nwItemType, nwItemClass, nwItemLayout from nw.enum import nwItemType, nwItemClass, nwItemLayout
from nw.constants import nwFiles, nwKeyWords from nw.constants import nwFiles, nwKeyWords
from nw.enum import nwAlert from nw.enum import nwAlert
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -62,7 +62,7 @@ class NWIndex():
self.noteIndex = {} self.noteIndex = {}
# Lists # Lists
self.novelList = [] self.novelList = []
return return
@@ -101,7 +101,7 @@ class NWIndex():
"""Load index from last session from the project meta folder. """Load index from last session from the project meta folder.
""" """
theData = {} theData = {}
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE) indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
if path.isfile(indexFile): if path.isfile(indexFile):
logger.debug("Loading index file") logger.debug("Loading index file")
@@ -130,7 +130,8 @@ class NWIndex():
return False return False
def saveIndex(self): def saveIndex(self):
"""Save the current index as a json file in the project meta folder. """Save the current index as a json file in the project meta
folder.
""" """
indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE) indexFile = path.join(self.theProject.projMeta, nwFiles.INDEX_FILE)
@@ -155,7 +156,8 @@ class NWIndex():
return True return True
def checkIndex(self): def checkIndex(self):
"""Check that the entries in the index are valid and contain the elements it should. """Check that the entries in the index are valid and contain the
elements it should.
""" """
self.indexBroken = False self.indexBroken = False
@@ -193,8 +195,9 @@ class NWIndex():
## ##
def scanText(self, tHandle, theText): def scanText(self, tHandle, theText):
"""Scan a piece of text associated with a handle. This will update the indices accordingly. """Scan a piece of text associated with a handle. This will
This function takes the handle and text as separate inputs as we want to primarily scan the update the indices accordingly. This function takes the handle
and text as separate inputs as we want to primarily scan the
files before we save them, unless we're rebuilding the index. files before we save them, unless we're rebuilding the index.
""" """
@@ -210,11 +213,11 @@ class NWIndex():
# Check file type, and reset its old index # Check file type, and reset its old index
if itemClass == nwItemClass.NOVEL: if itemClass == nwItemClass.NOVEL:
self.novelIndex[tHandle] = [] self.novelIndex[tHandle] = []
self.refIndex[tHandle] = [] self.refIndex[tHandle] = []
isNovel = True isNovel = True
else: else:
self.noteIndex[tHandle] = [] self.noteIndex[tHandle] = []
self.refIndex[tHandle] = [] self.refIndex[tHandle] = []
isNovel = False isNovel = False
# Also clear references to file in tag index # Also clear references to file in tag index
@@ -243,7 +246,8 @@ class NWIndex():
return True return True
def indexTitle(self, tHandle, isNovel, aLine, nLine, itemLayout): def indexTitle(self, tHandle, isNovel, aLine, nLine, itemLayout):
"""Save information about the title and its location in the file. """Save information about the title and its location in the
file.
""" """
if aLine.startswith("# "): if aLine.startswith("# "):
@@ -272,7 +276,8 @@ class NWIndex():
return True return True
def indexNoteRef(self, tHandle, aLine, nLine, nTitle): def indexNoteRef(self, tHandle, aLine, nLine, nTitle):
"""Validate and save the information about a reference to a tag in another file. """Validate and save the information about a reference to a tag
in another file.
""" """
isValid, theBits, thePos = self.scanThis(aLine) isValid, theBits, thePos = self.scanThis(aLine)
@@ -303,8 +308,9 @@ class NWIndex():
## ##
def scanThis(self, aLine): def scanThis(self, aLine):
"""Scan a line starting with @ to check that it's valid and to split up its elements into """Scan a line starting with @ to check that it's valid and to
an array and an array of positions. The latter is needed for the syntax highlighter. split up its elements into an array and an array of positions.
The latter is needed for the syntax highlighter.
""" """
theBits = [] theBits = []
@@ -343,8 +349,8 @@ class NWIndex():
return True, theBits, thePos return True, theBits, thePos
def checkThese(self, theBits, tItem): def checkThese(self, theBits, tItem):
"""Check the tags against the index to see if they are valid tags. This is needed for syntax """Check the tags against the index to see if they are valid
highlighting. tags. This is needed for syntax highlighting.
""" """
nBits = len(theBits) nBits = len(theBits)
@@ -357,7 +363,8 @@ class NWIndex():
if not isGood[0] or nBits == 1: if not isGood[0] or nBits == 1:
return isGood return isGood
# If we have a tag, only the first value is accepted, the rest is ignored # If we have a tag, only the first value is accepted, the rest
# is ignored
if theBits[0] == nwKeyWords.TAG_KEY and nBits > 1: if theBits[0] == nwKeyWords.TAG_KEY and nBits > 1:
isGood[0] = True isGood[0] = True
if theBits[1] in self.tagIndex.keys(): if theBits[1] in self.tagIndex.keys():
@@ -383,7 +390,6 @@ class NWIndex():
def buildNovelList(self): def buildNovelList(self):
"""Build a list of the content of the novel. """Build a list of the content of the novel.
""" """
self.novelList = [] self.novelList = []
self.novelOrder = [] self.novelOrder = []
for tHandle in self.theProject.treeOrder: for tHandle in self.theProject.treeOrder:
@@ -392,11 +398,11 @@ class NWIndex():
for tEntry in self.novelIndex[tHandle]: for tEntry in self.novelIndex[tHandle]:
self.novelList.append(tEntry) self.novelList.append(tEntry)
self.novelOrder.append("%s:%d" % (tHandle,tEntry[0])) self.novelOrder.append("%s:%d" % (tHandle,tEntry[0]))
return True return True
def buildReferenceList(self, tHandle): def buildReferenceList(self, tHandle):
"""Build a list of files referring back to our file, specified by tHandle. """Build a list of files referring back to our file, specified
by tHandle.
""" """
theRefs = {} theRefs = {}
@@ -429,8 +435,9 @@ class NWIndex():
return None, 0 return None, 0
def buildTagNovelMap(self, theTags, theFilters=None): def buildTagNovelMap(self, theTags, theFilters=None):
"""Build a two-dimensional map of all titles of the novel and which tags they link to from """Build a two-dimensional map of all titles of the novel and
the various meta tags. This map is used to display the timeline view. which tags they link to from the various meta tags. This map is
used to display the timeline view.
""" """
tagMap = {} tagMap = {}
+40 -29
View File
@@ -13,11 +13,11 @@
import logging import logging
import nw import nw
from os import path, mkdir from os import path, mkdir
from lxml import etree from lxml import etree
from datetime import datetime from datetime import datetime
from nw.enum import nwItemType, nwItemClass, nwItemLayout from nw.enum import nwItemType, nwItemClass, nwItemLayout
from nw.common import checkInt from nw.common import checkInt
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,23 +28,23 @@ class NWItem():
def __init__(self, theProject): def __init__(self, theProject):
self.theProject = theProject self.theProject = theProject
self.itemName = "" self.itemName = ""
self.itemHandle = None self.itemHandle = None
self.parHandle = None self.parHandle = None
self.itemOrder = None self.itemOrder = None
self.itemType = nwItemType.NO_TYPE self.itemType = nwItemType.NO_TYPE
self.itemClass = nwItemClass.NO_CLASS self.itemClass = nwItemClass.NO_CLASS
self.itemLayout = nwItemLayout.NO_LAYOUT self.itemLayout = nwItemLayout.NO_LAYOUT
self.itemStatus = None self.itemStatus = None
self.isExpanded = False self.isExpanded = False
# Document Meta Data # Document Meta Data
self.charCount = 0 self.charCount = 0
self.wordCount = 0 self.wordCount = 0
self.paraCount = 0 self.paraCount = 0
self.cursorPos = 0 self.cursorPos = 0
return return
@@ -85,17 +85,28 @@ class NWItem():
def setFromTag(self, tagName, tagValue): def setFromTag(self, tagName, tagValue):
logger.verbose("Setting tag '%s' to value '%s'" % (tagName, str(tagValue))) logger.verbose("Setting tag '%s' to value '%s'" % (tagName, str(tagValue)))
if tagName == "name": self.setName(tagValue) if tagName == "name":
elif tagName == "order": self.setOrder(tagValue) self.setName(tagValue)
elif tagName == "type": self.setType(tagValue) elif tagName == "order":
elif tagName == "class": self.setClass(tagValue) self.setOrder(tagValue)
elif tagName == "layout": self.setLayout(tagValue) elif tagName == "type":
elif tagName == "status": self.setStatus(tagValue) self.setType(tagValue)
elif tagName == "expanded": self.setExpanded(tagValue) elif tagName == "class":
elif tagName == "charCount": self.setCharCount(tagValue) self.setClass(tagValue)
elif tagName == "wordCount": self.setWordCount(tagValue) elif tagName == "layout":
elif tagName == "paraCount": self.setParaCount(tagValue) self.setLayout(tagValue)
elif tagName == "cursorPos": self.setCursorPos(tagValue) elif tagName == "status":
self.setStatus(tagValue)
elif tagName == "expanded":
self.setExpanded(tagValue)
elif tagName == "charCount":
self.setCharCount(tagValue)
elif tagName == "wordCount":
self.setWordCount(tagValue)
elif tagName == "paraCount":
self.setParaCount(tagValue)
elif tagName == "cursorPos":
self.setCursorPos(tagValue)
else: else:
logger.error("Unknown tag '%s'" % tagName) logger.error("Unknown tag '%s'" % tagName)
return return
+82 -63
View File
@@ -13,18 +13,18 @@
import logging import logging
import nw import nw
from os import path, mkdir, listdir from os import path, mkdir, listdir
from shutil import copyfile from shutil import copyfile
from lxml import etree from lxml import etree
from hashlib import sha256 from hashlib import sha256
from datetime import datetime from datetime import datetime
from time import time from time import time
from nw.project.item import NWItem from nw.project.item import NWItem
from nw.project.status import NWStatus from nw.project.status import NWStatus
from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from nw.common import checkString, checkBool, checkInt from nw.common import checkString, checkBool, checkInt
from nw.constants import nwFiles, nwConst from nw.constants import nwFiles, nwConst
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -37,21 +37,21 @@ class NWProject():
self.mainConf = self.theParent.mainConf self.mainConf = self.theParent.mainConf
self.projOpened = None # The time stamp of when the project file was opened self.projOpened = None # The time stamp of when the project file was opened
self.projChanged = None # The project has unsaved changes self.projChanged = None # The project has unsaved changes
self.projAltered = None # The project has been altered this session (used to trigger backup) self.projAltered = None # The project has been altered this session
# Debug # Debug
self.handleSeed = None self.handleSeed = None
# Class Settings # Class Settings
self.projTree = None # Holds all the items of the project self.projTree = None # Holds all the items of the project
self.treeOrder = None # The order of the tree items on the tree view self.treeOrder = None # The order of the tree items on the tree view
self.treeRoots = None # The root items of the tree self.treeRoots = None # The root items of the tree
self.trashRoot = None # The handle of the trash root folder self.trashRoot = None # The handle of the trash root folder
self.projPath = None # The full path to where the currently open project is saved self.projPath = None # The full path to where the currently open project is saved
self.projMeta = None # The full path to the project's meta data folder self.projMeta = None # The full path to the project's meta data folder
self.projCache = None # The full path to the project's cache folder self.projCache = None # The full path to the project's cache folder
self.projDict = None # The spell check dictionary self.projDict = None # The spell check dictionary
self.projFile = None # The file name of the project main xml file self.projFile = None # The file name of the project main xml file
# Project Meta # Project Meta
self.projName = None self.projName = None
@@ -194,8 +194,10 @@ class NWProject():
self.projCache = path.join(self.projPath,"cache") self.projCache = path.join(self.projPath,"cache")
self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT) self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT)
if not self._checkFolder(self.projMeta): return if not self._checkFolder(self.projMeta):
if not self._checkFolder(self.projCache): return return
if not self._checkFolder(self.projCache):
return
try: try:
nwXML = etree.parse(fileName) nwXML = etree.parse(fileName)
@@ -214,14 +216,18 @@ class NWProject():
logger.verbose("File version is %s" % fileVersion) logger.verbose("File version is %s" % fileVersion)
if not nwxRoot == "novelWriterXML" or not fileVersion == "1.0": if not nwxRoot == "novelWriterXML" or not fileVersion == "1.0":
self.makeAlert("Project file does not appear to be a novelWriterXML file version 1.0", nwAlert.ERROR) self.makeAlert(
"Project file does not appear to be a novelWriterXML file version 1.0",
nwAlert.ERROR
)
return False return False
for xChild in xRoot: for xChild in xRoot:
if xChild.tag == "project": if xChild.tag == "project":
logger.debug("Found project meta") logger.debug("Found project meta")
for xItem in xChild: for xItem in xChild:
if xItem.text is None: continue if xItem.text is None:
continue
if xItem.tag == "name": if xItem.tag == "name":
logger.verbose("Working Title: '%s'" % xItem.text) logger.verbose("Working Title: '%s'" % xItem.text)
self.projName = xItem.text self.projName = xItem.text
@@ -236,7 +242,8 @@ class NWProject():
elif xChild.tag == "settings": elif xChild.tag == "settings":
logger.debug("Found project settings") logger.debug("Found project settings")
for xItem in xChild: for xItem in xChild:
if xItem.text is None: continue if xItem.text is None:
continue
if xItem.tag == "spellCheck": if xItem.tag == "spellCheck":
self.spellCheck = checkBool(xItem.text,False) self.spellCheck = checkBool(xItem.text,False)
elif xItem.tag == "lastEdited": elif xItem.tag == "lastEdited":
@@ -308,19 +315,19 @@ class NWProject():
}) })
# Save Project Meta # Save Project Meta
xProject = etree.SubElement(nwXML,"project") xProject = etree.SubElement(nwXML, "project")
self._saveProjectValue(xProject,"name", self.projName, True) self._saveProjectValue(xProject, "name", self.projName, True)
self._saveProjectValue(xProject,"title", self.bookTitle, True) self._saveProjectValue(xProject, "title", self.bookTitle, True)
self._saveProjectValue(xProject,"author",self.bookAuthors) self._saveProjectValue(xProject, "author", self.bookAuthors)
self._saveProjectValue(xProject,"backup",self.doBackup) self._saveProjectValue(xProject, "backup", self.doBackup)
# Save Project Settings # Save Project Settings
xSettings = etree.SubElement(nwXML,"settings") xSettings = etree.SubElement(nwXML, "settings")
self._saveProjectValue(xSettings,"spellCheck", self.spellCheck) self._saveProjectValue(xSettings, "spellCheck", self.spellCheck)
self._saveProjectValue(xSettings,"lastEdited", self.lastEdited) self._saveProjectValue(xSettings, "lastEdited", self.lastEdited)
self._saveProjectValue(xSettings,"lastViewed", self.lastViewed) self._saveProjectValue(xSettings, "lastViewed", self.lastViewed)
self._saveProjectValue(xSettings,"lastWordCount",self.currWCount) self._saveProjectValue(xSettings, "lastWordCount", self.currWCount)
xAutoRep = etree.SubElement(xSettings,"autoReplace") xAutoRep = etree.SubElement(xSettings, "autoReplace")
for aKey, aValue in self.autoReplace.items(): for aKey, aValue in self.autoReplace.items():
if len(aKey) > 0: if len(aKey) > 0:
self._saveProjectValue(xAutoRep,aKey,aValue) self._saveProjectValue(xAutoRep,aKey,aValue)
@@ -332,7 +339,7 @@ class NWProject():
# Save Tree Content # Save Tree Content
logger.debug("Writing project content") logger.debug("Writing project content")
xContent = etree.SubElement(nwXML,"content",attrib={"count":str(len(self.treeOrder))}) xContent = etree.SubElement(nwXML, "content", attrib={"count":str(len(self.treeOrder))})
for tHandle in self.treeOrder: for tHandle in self.treeOrder:
self.projTree[tHandle].packXML(xContent) self.projTree[tHandle].packXML(xContent)
@@ -399,16 +406,16 @@ class NWProject():
self.doBackup = False self.doBackup = False
if doBackup: if doBackup:
if not path.isdir(self.mainConf.backupPath): if not path.isdir(self.mainConf.backupPath):
self.theParent.makeAlert( self.theParent.makeAlert((
"You must set a valid backup path in preferences<br>to use the automatic project backup feature.", "You must set a valid backup path in preferences to use "
nwAlert.ERROR "the automatic project backup feature."
) ), nwAlert.ERROR)
return False return False
if self.projName == "": if self.projName == "":
self.theParent.makeAlert( self.theParent.makeAlert((
"You must set a valid project name in project settings<br>to use the automatic project backup feature.", "You must set a valid project name in project settings to "
nwAlert.ERROR "use the automatic project backup feature."
) ), nwAlert.ERROR)
return False return False
self.doBackup = True self.doBackup = True
return True return True
@@ -490,8 +497,9 @@ class NWProject():
return None return None
def getRootItem(self, tHandle): def getRootItem(self, tHandle):
"""Iterate upwards in the tree until we find the item with parent None, the root item. """Iterate upwards in the tree until we find the item with
We do this with a for loop with a maximum depth of 200 to make infinite loops impossible. 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) tItem = self.getItem(tHandle)
if tItem is not None: if tItem is not None:
@@ -503,9 +511,10 @@ class NWProject():
return None return None
def getProjectItems(self): def getProjectItems(self):
"""This function is called from the tree view when building the tree. Each item in the """This function is called from the tree view when building the
project is returned in the order saved in the project file, but first it checks that it has tree. Each item in the project is returned in the order saved in
a parent item already sent to the tree. the project file, but first it checks that it has a parent item
already sent to the tree.
""" """
sentItems = [] sentItems = []
iterItems = self.treeOrder.copy() iterItems = self.treeOrder.copy()
@@ -518,10 +527,12 @@ class NWProject():
if n > 10000: if n > 10000:
return # Just in case return # Just in case
if tItem is None: if tItem is None:
# Technically a bug since treeOrder is built from the same data as projTree # Technically a bug since treeOrder is built from the
# same data as projTree
continue continue
elif tItem.parHandle is None: elif tItem.parHandle is None:
# Item is a root, or already been identified as an orphaned item # Item is a root, or already been identified as an
# orphaned item
sentItems.append(tHandle) sentItems.append(tHandle)
yield tItem yield tItem
elif tItem.parHandle in sentItems: elif tItem.parHandle in sentItems:
@@ -529,7 +540,8 @@ class NWProject():
sentItems.append(tHandle) sentItems.append(tHandle)
yield tItem yield tItem
elif tItem.parHandle in iterItems: elif tItem.parHandle in iterItems:
# Item's parent exists, but hasn't been sent yet, so add it again to the end # Item's parent exists, but hasn't been sent yet, so add
# it again to the end
logger.warning("Item %s found before its parent" % tHandle) logger.warning("Item %s found before its parent" % tHandle)
iterItems.append(tHandle) iterItems.append(tHandle)
nMax = len(iterItems) nMax = len(iterItems)
@@ -544,7 +556,8 @@ class NWProject():
## ##
def deleteItem(self, tHandle): def deleteItem(self, tHandle):
"""This only removes the item from the order list, but not from the project tree. """This only removes the item from the order list, but not from
the project tree.
""" """
self.treeOrder.remove(tHandle) self.treeOrder.remove(tHandle)
self.setProjectChanged(True) self.setProjectChanged(True)
@@ -557,8 +570,8 @@ class NWProject():
return None return None
def checkRootUnique(self, theClass): def checkRootUnique(self, theClass):
"""Checks if there already is a root entry of class 'theClass' in the """Checks if there already is a root entry of class 'theClass'
root of the project tree. in the root of the project tree.
""" """
if theClass == nwItemClass.CUSTOM: if theClass == nwItemClass.CUSTOM:
return True return True
@@ -634,7 +647,10 @@ class NWProject():
# Report status # Report status
if len(orphanFiles) > 0: if len(orphanFiles) > 0:
self.makeAlert("Found %d orphaned file(s) in project folder!" % len(orphanFiles), nwAlert.WARN) self.makeAlert(
"Found %d orphaned file(s) in project folder!" % len(orphanFiles),
nwAlert.WARN
)
else: else:
logger.debug("File check OK") logger.debug("File check OK")
return return
@@ -683,7 +699,9 @@ class NWProject():
if self.projMeta is None: if self.projMeta is None:
return False return False
with open(path.join(self.projMeta, nwFiles.SESS_INFO),mode="a+",encoding="utf8") as outFile: sessionFile = path.join(self.projMeta, nwFiles.SESS_INFO)
with open(sessionFile, mode="a+", encoding="utf8") as outFile:
print(( print((
"Start: {opened:s} " "Start: {opened:s} "
"End: {closed:s} " "End: {closed:s} "
@@ -711,9 +729,10 @@ class NWProject():
return itemHandle return itemHandle
def _maintainPrevious(self): def _maintainPrevious(self):
"""This function will take the current project file and copy it into the project cache """This function will take the current project file and copy it
folder with an incremental file extension added. These serve as a backup in case the xml into the project cache folder with an incremental file extension
file gets corrupted. added. These serve as a backup in case the xml file gets
corrupted.
""" """
countFile = path.join(self.projCache, nwFiles.PROJ_COUNT) countFile = path.join(self.projCache, nwFiles.PROJ_COUNT)
@@ -733,8 +752,8 @@ class NWProject():
try: try:
copyfile( copyfile(
path.join(self.projPath,self.projFile), path.join(self.projPath, self.projFile),
path.join(self.projCache,projBackup) path.join(self.projCache, projBackup)
) )
except: except:
logger.error("Failed to write to file %s" % projBackup) logger.error("Failed to write to file %s" % projBackup)
+2 -2
View File
@@ -13,9 +13,9 @@
import logging import logging
import nw import nw
from lxml import etree from lxml import etree
from nw.enum import nwItemClass from nw.enum import nwItemClass
from nw.common import checkInt from nw.common import checkInt
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
+86 -86
View File
@@ -17,7 +17,7 @@ import nw
from os import path, listdir from os import path, listdir
from PyQt5.QtWidgets import qApp from PyQt5.QtWidgets import qApp
from PyQt5.QtGui import QPalette, QColor, QIcon from PyQt5.QtGui import QPalette, QColor, QIcon
from nw.enum import nwAlert from nw.enum import nwAlert
@@ -41,30 +41,30 @@ class Theme:
def __init__(self, theParent): def __init__(self, theParent):
self.mainConf = nw.CONFIG self.mainConf = nw.CONFIG
self.theParent = theParent self.theParent = theParent
self.guiPalette = QPalette() self.guiPalette = QPalette()
self.guiPath = "gui" self.guiPath = "gui"
self.iconPath = "icons" self.iconPath = "icons"
self.syntaxPath = "syntax" self.syntaxPath = "syntax"
self.cssName = "style.qss" self.cssName = "style.qss"
self.confName = "theme.conf" self.confName = "theme.conf"
self.themeList = [] self.themeList = []
self.syntaxList = [] self.syntaxList = []
# Loaded Theme Settings # Loaded Theme Settings
## Theme ## Theme
self.themeName = "" self.themeName = ""
self.themeAuthor = "" self.themeAuthor = ""
self.themeCredit = "" self.themeCredit = ""
self.themeUrl = "" self.themeUrl = ""
## GUI ## GUI
self.treeWCount = [ 0, 0, 0] self.treeWCount = [ 0, 0, 0]
self.statNone = [120,120,120] self.statNone = [120,120,120]
self.statUnsaved = [120,120, 40] self.statUnsaved = [120,120, 40]
self.statSaved = [ 40,120, 0] self.statSaved = [ 40,120, 0]
# Loaded Syntax Settings # Loaded Syntax Settings
@@ -75,33 +75,33 @@ class Theme:
self.syntaxUrl = "" self.syntaxUrl = ""
## Colours ## Colours
self.colBack = [255,255,255] self.colBack = [255,255,255]
self.colText = [ 0, 0, 0] self.colText = [ 0, 0, 0]
self.colLink = [ 0, 0, 0] self.colLink = [ 0, 0, 0]
self.colHead = [ 0, 0, 0] self.colHead = [ 0, 0, 0]
self.colHeadH = [ 0, 0, 0] self.colHeadH = [ 0, 0, 0]
self.colEmph = [ 0, 0, 0] self.colEmph = [ 0, 0, 0]
self.colDialN = [ 0, 0, 0] self.colDialN = [ 0, 0, 0]
self.colDialD = [ 0, 0, 0] self.colDialD = [ 0, 0, 0]
self.colDialS = [ 0, 0, 0] self.colDialS = [ 0, 0, 0]
self.colComm = [ 0, 0, 0] self.colComm = [ 0, 0, 0]
self.colKey = [ 0, 0, 0] self.colKey = [ 0, 0, 0]
self.colVal = [ 0, 0, 0] self.colVal = [ 0, 0, 0]
self.colSpell = [ 0, 0, 0] self.colSpell = [ 0, 0, 0]
self.colTagErr = [ 0, 0, 0] self.colTagErr = [ 0, 0, 0]
self.colRepTag = [ 0, 0, 0] self.colRepTag = [ 0, 0, 0]
## Icons ## Icons
self.themeIcons = {} self.themeIcons = {}
# Changeable Settings # Changeable Settings
self.guiTheme = None self.guiTheme = None
self.guiSyntax = None self.guiSyntax = None
self.themeRoot = None self.themeRoot = None
self.themePath = None self.themePath = None
self.syntaxFile = None self.syntaxFile = None
self.confFile = None self.confFile = None
self.cssFile = None self.cssFile = None
self.updateTheme() self.updateTheme()
@@ -162,36 +162,36 @@ class Theme:
## Main ## Main
cnfSec = "Main" cnfSec = "Main"
if confParser.has_section(cnfSec): if confParser.has_section(cnfSec):
self.themeName = self._parseLine(confParser,cnfSec,"name", "") self.themeName = self._parseLine( confParser, cnfSec, "name", "")
self.themeAuthor = self._parseLine(confParser,cnfSec,"author","") self.themeAuthor = self._parseLine( confParser, cnfSec, "author", "")
self.themeCredit = self._parseLine(confParser,cnfSec,"credit","") self.themeCredit = self._parseLine( confParser, cnfSec, "credit", "")
self.themeUrl = self._parseLine(confParser,cnfSec,"url", "") self.themeUrl = self._parseLine( confParser, cnfSec, "url", "")
## Palette ## Palette
cnfSec = "Palette" cnfSec = "Palette"
if confParser.has_section(cnfSec): if confParser.has_section(cnfSec):
self._setPalette(confParser,cnfSec,"window", QPalette.Window) self._setPalette(confParser, cnfSec, "window", QPalette.Window)
self._setPalette(confParser,cnfSec,"windowtext", QPalette.WindowText) self._setPalette(confParser, cnfSec, "windowtext", QPalette.WindowText)
self._setPalette(confParser,cnfSec,"base", QPalette.Base) self._setPalette(confParser, cnfSec, "base", QPalette.Base)
self._setPalette(confParser,cnfSec,"alternatebase", QPalette.AlternateBase) self._setPalette(confParser, cnfSec, "alternatebase", QPalette.AlternateBase)
self._setPalette(confParser,cnfSec,"text", QPalette.Text) self._setPalette(confParser, cnfSec, "text", QPalette.Text)
self._setPalette(confParser,cnfSec,"tooltipbase", QPalette.ToolTipBase) self._setPalette(confParser, cnfSec, "tooltipbase", QPalette.ToolTipBase)
self._setPalette(confParser,cnfSec,"tooltiptext", QPalette.ToolTipText) self._setPalette(confParser, cnfSec, "tooltiptext", QPalette.ToolTipText)
self._setPalette(confParser,cnfSec,"button", QPalette.Button) self._setPalette(confParser, cnfSec, "button", QPalette.Button)
self._setPalette(confParser,cnfSec,"buttontext", QPalette.ButtonText) self._setPalette(confParser, cnfSec, "buttontext", QPalette.ButtonText)
self._setPalette(confParser,cnfSec,"brighttext", QPalette.BrightText) self._setPalette(confParser, cnfSec, "brighttext", QPalette.BrightText)
self._setPalette(confParser,cnfSec,"highlight", QPalette.Highlight) self._setPalette(confParser, cnfSec, "highlight", QPalette.Highlight)
self._setPalette(confParser,cnfSec,"highlightedtext",QPalette.HighlightedText) self._setPalette(confParser, cnfSec, "highlightedtext", QPalette.HighlightedText)
self._setPalette(confParser,cnfSec,"link", QPalette.Link) self._setPalette(confParser, cnfSec, "link", QPalette.Link)
self._setPalette(confParser,cnfSec,"linkvisited", QPalette.LinkVisited) self._setPalette(confParser, cnfSec, "linkvisited", QPalette.LinkVisited)
## GUI ## GUI
cnfSec = "GUI" cnfSec = "GUI"
if confParser.has_section(cnfSec): if confParser.has_section(cnfSec):
self.treeWCount = self._loadColour(confParser,cnfSec,"treewordcount") self.treeWCount = self._loadColour(confParser, cnfSec, "treewordcount")
self.statNone = self._loadColour(confParser,cnfSec,"statusnone") self.statNone = self._loadColour(confParser, cnfSec, "statusnone")
self.statUnsaved = self._loadColour(confParser,cnfSec,"statusunsaved") self.statUnsaved = self._loadColour(confParser, cnfSec, "statusunsaved")
self.statSaved = self._loadColour(confParser,cnfSec,"statussaved") self.statSaved = self._loadColour(confParser, cnfSec, "statussaved")
# Apply Styles # Apply Styles
qApp.setStyleSheet(cssData) qApp.setStyleSheet(cssData)
@@ -205,7 +205,7 @@ class Theme:
confParser = configparser.ConfigParser() confParser = configparser.ConfigParser()
try: try:
confParser.read_file(open(self.syntaxFile,mode="r",encoding="utf8")) confParser.read_file(open(self.syntaxFile, mode="r", encoding="utf8"))
except Exception as e: except Exception as e:
logger.error("Could not load syntax colours from: %s" % self.syntaxFile) logger.error("Could not load syntax colours from: %s" % self.syntaxFile)
return False return False
@@ -213,29 +213,29 @@ class Theme:
## Main ## Main
cnfSec = "Main" cnfSec = "Main"
if confParser.has_section(cnfSec): if confParser.has_section(cnfSec):
self.syntaxName = self._parseLine(confParser,cnfSec,"name","") self.syntaxName = self._parseLine(confParser, cnfSec, "name", "")
self.syntaxAuthor = self._parseLine(confParser,cnfSec,"author","") self.syntaxAuthor = self._parseLine(confParser, cnfSec, "author", "")
self.syntaxCredit = self._parseLine(confParser,cnfSec,"credit","") self.syntaxCredit = self._parseLine(confParser, cnfSec, "credit", "")
self.syntaxUrl = self._parseLine(confParser,cnfSec,"url", "") self.syntaxUrl = self._parseLine(confParser, cnfSec, "url", "")
## Syntax ## Syntax
cnfSec = "Syntax" cnfSec = "Syntax"
if confParser.has_section(cnfSec): if confParser.has_section(cnfSec):
self.colBack = self._loadColour(confParser,cnfSec,"background") self.colBack = self._loadColour(confParser, cnfSec, "background")
self.colText = self._loadColour(confParser,cnfSec,"text") self.colText = self._loadColour(confParser, cnfSec, "text")
self.colLink = self._loadColour(confParser,cnfSec,"link") self.colLink = self._loadColour(confParser, cnfSec, "link")
self.colHead = self._loadColour(confParser,cnfSec,"headertext") self.colHead = self._loadColour(confParser, cnfSec, "headertext")
self.colHeadH = self._loadColour(confParser,cnfSec,"headertag") self.colHeadH = self._loadColour(confParser, cnfSec, "headertag")
self.colEmph = self._loadColour(confParser,cnfSec,"emphasis") self.colEmph = self._loadColour(confParser, cnfSec, "emphasis")
self.colDialN = self._loadColour(confParser,cnfSec,"straightquotes") self.colDialN = self._loadColour(confParser, cnfSec, "straightquotes")
self.colDialD = self._loadColour(confParser,cnfSec,"doublequotes") self.colDialD = self._loadColour(confParser, cnfSec, "doublequotes")
self.colDialS = self._loadColour(confParser,cnfSec,"singlequotes") self.colDialS = self._loadColour(confParser, cnfSec, "singlequotes")
self.colComm = self._loadColour(confParser,cnfSec,"hidden") self.colComm = self._loadColour(confParser, cnfSec, "hidden")
self.colKey = self._loadColour(confParser,cnfSec,"keyword") self.colKey = self._loadColour(confParser, cnfSec, "keyword")
self.colVal = self._loadColour(confParser,cnfSec,"value") self.colVal = self._loadColour(confParser, cnfSec, "value")
self.colSpell = self._loadColour(confParser,cnfSec,"spellcheckline") self.colSpell = self._loadColour(confParser, cnfSec, "spellcheckline")
self.colTagErr = self._loadColour(confParser,cnfSec,"tagerror") self.colTagErr = self._loadColour(confParser, cnfSec, "tagerror")
self.colRepTag = self._loadColour(confParser,cnfSec,"replacetag") self.colRepTag = self._loadColour(confParser, cnfSec, "replacetag")
logger.info("Loaded syntax theme '%s'" % self.guiSyntax) logger.info("Loaded syntax theme '%s'" % self.guiSyntax)
@@ -251,7 +251,7 @@ class Theme:
themeConf = path.join(self.mainConf.themeRoot, self.guiPath, themeDir, self.confName) themeConf = path.join(self.mainConf.themeRoot, self.guiPath, themeDir, self.confName)
logger.verbose("Checking theme config for '%s'" % themeDir) logger.verbose("Checking theme config for '%s'" % themeDir)
try: try:
confParser.read_file(open(themeConf,mode="r",encoding="utf8")) confParser.read_file(open(themeConf, mode="r", encoding="utf8"))
except Exception as e: except Exception as e:
self.theParent.makeAlert(["Could not load theme config file",str(e)],nwAlert.ERROR) self.theParent.makeAlert(["Could not load theme config file",str(e)],nwAlert.ERROR)
continue continue
@@ -279,7 +279,7 @@ class Theme:
continue continue
logger.verbose("Checking theme syntax for '%s'" % syntaxFile) logger.verbose("Checking theme syntax for '%s'" % syntaxFile)
try: try:
confParser.read_file(open(syntaxPath,mode="r",encoding="utf8")) confParser.read_file(open(syntaxPath, mode="r", encoding="utf8"))
except Exception as e: except Exception as e:
self.theParent.makeAlert(["Could not load syntax file",str(e)],nwAlert.ERROR) self.theParent.makeAlert(["Could not load syntax file",str(e)],nwAlert.ERROR)
return [] return []
+14 -11
View File
@@ -58,7 +58,7 @@ class TextAnalysis():
return rScore, gLevel return rScore, gLevel
def getReadabilityText(self, rScore): def getReadabilityText(self, rScore):
if rScore >= 90.0: if rScore >= 90.0:
return "Very Easy" return "Very Easy"
elif rScore >= 80.0: elif rScore >= 80.0:
return "Easy" return "Easy"
@@ -78,13 +78,15 @@ class TextAnalysis():
# #
def _countWords(self): def _countWords(self):
"""Counts the number of words in a text by simply splitting on all white spaces. """Counts the number of words in a text by simply splitting on
all white spaces.
""" """
return len(self.theText.strip().split()) return len(self.theText.strip().split())
def _countSentences(self): def _countSentences(self):
"""Counts the number of non-repeated sentence endings seen in the text. """Counts the number of non-repeated sentence endings seen in
Note: This will count filenames and urls as multiple sentences. the text. Note: This will count filenames and urls as multiple
sentences.
""" """
nSent = 0 nSent = 0
sawEnd = False sawEnd = False
@@ -98,7 +100,8 @@ class TextAnalysis():
return nSent return nSent
def _countParagraphs(self, pThreshold=2): def _countParagraphs(self, pThreshold=2):
"""Counts the number of paragraphs by counting repeated line breaks. """Counts the number of paragraphs by counting repeated line
breaks.
""" """
nPara = 1 nPara = 1
sawEnd = 0 sawEnd = 0
@@ -114,9 +117,10 @@ class TextAnalysis():
return nPara return nPara
def _countSyllablesEN(self): def _countSyllablesEN(self):
"""Attempt to count the syllables in a piece of English language text. """Attempt to count the syllables in a piece of English language
This function tends to slightly over-estimate the number of syllables as it doesn't handle text. This function tends to slightly over-estimate the number
the complexity of silent vowels in endings very well. It will count them all. of syllables as it doesn't handle the complexity of silent
vowels in endings very well. It will count them all.
""" """
cleanText = "" cleanText = ""
@@ -126,8 +130,8 @@ class TextAnalysis():
else: else:
cleanText += " " cleanText += " "
asVow = "aeiouy'" asVow = "aeiouy'"
dExept = ("ei","ie","ua","ia","eo") dExept = ("ei","ie","ua","ia","eo")
theWords = cleanText.lower().split() theWords = cleanText.lower().split()
allSylls = 0 allSylls = 0
for inWord in theWords: for inWord in theWords:
@@ -160,7 +164,6 @@ class TextAnalysis():
nSyll += 1 nSyll += 1
if nSyll < 1: if nSyll < 1:
nSyll = 1 nSyll = 1
# print("%-15s: %d" % (inWord,nSyll))
allSylls += nSyll allSylls += nSyll
return allSylls/len(theWords) return allSylls/len(theWords)
+3 -2
View File
@@ -32,8 +32,9 @@ class NWSpellEnchant(NWSpellCheck):
return return
def setLanguage(self, theLang, projectDict=None): def setLanguage(self, theLang, projectDict=None):
"""Load a dictionary for the language specified in the config. If that fails, we load a """Load a dictionary for the language specified in the config.
dummy dictionary so that lookups don't crash. If that fails, we load a dummy dictionary so that lookups don't
crash.
""" """
try: try:
if projectDict is None: if projectDict is None:
+1 -1
View File
@@ -34,7 +34,7 @@ def countWords(theText):
if aLine[0] == "@" or aLine[0] == "%": if aLine[0] == "@" or aLine[0] == "%":
continue continue
if aLine[0:5] == "#### ": if aLine[0:5] == "#### ":
wordCount -= 1 wordCount -= 1
charCount -= 5 charCount -= 5
countPara = False countPara = False
+2 -2
View File
@@ -15,8 +15,8 @@ setuptools.setup(
license = "GNU General Public License v3", license = "GNU General Public License v3",
url = "https://github.com/vkbo/novelWriter", url = "https://github.com/vkbo/novelWriter",
entry_points = { entry_points = {
"console_scripts" : ["novelWriter=nw:main"], "console_scripts" : ["novelWriter-cli=nw:main"],
"gui_scripts" : ["novelWriter-gui=nw:main"], "gui_scripts" : ["novelWriter=nw:main"],
}, },
packages = setuptools.find_packages(exclude=["docs","tests","sample"]), packages = setuptools.find_packages(exclude=["docs","tests","sample"]),
include_package_data = True, include_package_data = True,