@@ -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.
|
||||
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
|
||||
|
||||

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