Merge branch 'main' into release_1.0rc2
This commit is contained in:
+20
-18
@@ -58,20 +58,21 @@ from nw.config import Config
|
||||
#
|
||||
|
||||
__package__ = "nw"
|
||||
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
|
||||
__license__ = "GPLv3"
|
||||
__author__ = "Veronica Berglyd Olsen"
|
||||
__maintainer__ = "Veronica Berglyd Olsen"
|
||||
__copyright__ = "Copyright 2018–2020, Veronica Berglyd Olsen"
|
||||
__email__ = "code@vkbo.net"
|
||||
__version__ = "1.0rc2"
|
||||
__hexversion__ = "0x010000c2"
|
||||
__date__ = "2020-11-29"
|
||||
__date__ = "2020-12-13"
|
||||
__status__ = "Beta"
|
||||
__isstable__ = False
|
||||
__domain__ = "novelwriter.io"
|
||||
__url__ = "https://novelwriter.io"
|
||||
__sourceurl__ = "https://github.com/vkbo/novelWriter"
|
||||
__issuesurl__ = "https://github.com/vkbo/novelWriter/issues"
|
||||
__domain__ = "novelwriter.io"
|
||||
__helpurl__ = "https://github.com/vkbo/novelWriter/discussions"
|
||||
__releaseurl__ = "https://github.com/vkbo/novelWriter/releases/latest"
|
||||
__docurl__ = "https://novelwriter.readthedocs.io"
|
||||
__credits__ = [
|
||||
"Veronica Berglyd Olsen (developer)",
|
||||
@@ -246,19 +247,20 @@ def main(sysArgs=None):
|
||||
errorCode |= 32
|
||||
|
||||
if errorData:
|
||||
if not testMode:
|
||||
errApp = QApplication([])
|
||||
errMsg = QErrorMessage()
|
||||
errMsg.resize(500, 300)
|
||||
errMsg.showMessage((
|
||||
"<h3>A critical error has been encountered</h3>"
|
||||
"<p>novelWriter cannot start due to the following issues:<p>"
|
||||
"<p> - %s</p>"
|
||||
"<p>Shutting down ...</p>"
|
||||
) % (
|
||||
"<br> - ".join(errorData)
|
||||
))
|
||||
errApp.exec_()
|
||||
errApp = QApplication([])
|
||||
errMsg = QErrorMessage()
|
||||
errMsg.resize(500, 300)
|
||||
errMsg.showMessage((
|
||||
"<h3>A critical error has been encountered</h3>"
|
||||
"<p>novelWriter cannot start due to the following issues:<p>"
|
||||
"<p> - %s</p>"
|
||||
"<p>Shutting down ...</p>"
|
||||
) % (
|
||||
"<br> - ".join(errorData)
|
||||
))
|
||||
for errMsg in errorData:
|
||||
logger.critical(errMsg)
|
||||
errApp.exec_()
|
||||
sys.exit(errorCode)
|
||||
|
||||
# Finish initialising config
|
||||
@@ -293,4 +295,4 @@ def main(sysArgs=None):
|
||||
nwGUI = GuiMain()
|
||||
sys.exit(nwApp.exec_())
|
||||
|
||||
return
|
||||
# END Function main
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,207 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 270.93333 270.93334"
|
||||
height="1024"
|
||||
width="1024">
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="linearGradient2515">
|
||||
<stop
|
||||
style="stop-color:#f7f7f7;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop2511" />
|
||||
<stop
|
||||
style="stop-color:#e5e5e5;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop2513" />
|
||||
</linearGradient>
|
||||
<rect
|
||||
x="60.484347"
|
||||
y="205.79962"
|
||||
width="130.57787"
|
||||
height="65.785888"
|
||||
id="rect852" />
|
||||
<rect
|
||||
id="rect1474"
|
||||
height="28.631245"
|
||||
width="177.70083"
|
||||
y="18.019813"
|
||||
x="13.463703" />
|
||||
<rect
|
||||
id="rect852-7"
|
||||
height="65.785889"
|
||||
width="130.57787"
|
||||
y="205.79962"
|
||||
x="60.484348" />
|
||||
<rect
|
||||
id="rect1544"
|
||||
height="65.785889"
|
||||
width="130.57787"
|
||||
y="205.79962"
|
||||
x="60.484348" />
|
||||
<rect
|
||||
x="60.484348"
|
||||
y="205.79962"
|
||||
width="130.57787"
|
||||
height="65.785889"
|
||||
id="rect1570" />
|
||||
<rect
|
||||
x="13.463703"
|
||||
y="18.019813"
|
||||
width="177.70083"
|
||||
height="28.631245"
|
||||
id="rect1587" />
|
||||
<filter
|
||||
id="filter1444"
|
||||
style="color-interpolation-filters:sRGB">
|
||||
<feFlood
|
||||
id="feFlood1434"
|
||||
result="flood"
|
||||
flood-color="rgb(0,0,0)"
|
||||
flood-opacity="0.498039" />
|
||||
<feComposite
|
||||
id="feComposite1436"
|
||||
result="composite1"
|
||||
operator="in"
|
||||
in2="SourceGraphic"
|
||||
in="flood" />
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur1438"
|
||||
result="blur"
|
||||
stdDeviation="3"
|
||||
in="composite1" />
|
||||
<feOffset
|
||||
id="feOffset1440"
|
||||
result="offset"
|
||||
dy="6"
|
||||
dx="6" />
|
||||
<feComposite
|
||||
id="feComposite1442"
|
||||
result="composite2"
|
||||
operator="over"
|
||||
in2="offset"
|
||||
in="SourceGraphic" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1456"
|
||||
style="color-interpolation-filters:sRGB">
|
||||
<feFlood
|
||||
id="feFlood1446"
|
||||
result="flood"
|
||||
flood-color="rgb(0,0,0)"
|
||||
flood-opacity="0.498039" />
|
||||
<feComposite
|
||||
id="feComposite1448"
|
||||
result="composite1"
|
||||
operator="in"
|
||||
in2="SourceGraphic"
|
||||
in="flood" />
|
||||
<feGaussianBlur
|
||||
id="feGaussianBlur1450"
|
||||
result="blur"
|
||||
stdDeviation="3"
|
||||
in="composite1" />
|
||||
<feOffset
|
||||
id="feOffset1452"
|
||||
result="offset"
|
||||
dy="6"
|
||||
dx="6" />
|
||||
<feComposite
|
||||
id="feComposite1454"
|
||||
result="composite2"
|
||||
operator="over"
|
||||
in2="offset"
|
||||
in="SourceGraphic" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
xlink:href="#linearGradient2515"
|
||||
id="radialGradient2517"
|
||||
cx="605.42725"
|
||||
cy="623.97186"
|
||||
fx="605.42725"
|
||||
fy="623.97186"
|
||||
r="410"
|
||||
gradientTransform="matrix(2.4110274,1.7617596,-0.719517,0.98463462,-776.23441,-1372.7537)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer3"
|
||||
style="display:inline">
|
||||
<path
|
||||
clip-path="none"
|
||||
transform="scale(0.26458333)"
|
||||
d="m 192,22 c -44.32,0 -80,35.680002 -80,80 v 820 c 0,44.32002 35.68,80 80,80 h 640 c 44.31999,0 80,-35.67998 80,-80 V 213.44727 L 720.55469,22 Z"
|
||||
style="fill:url(#radialGradient2517);fill-opacity:1;stroke:#999999;stroke-width:20;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1481" />
|
||||
<path
|
||||
id="path1490"
|
||||
style="display:inline;fill:#e0e0e0;fill-opacity:1;stroke:#999999;stroke-width:5.29167;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 241.30037,56.474215 190.64713,5.8204593 V 35.307549 c 0,11.726333 9.44034,21.166666 21.16667,21.166666 z" />
|
||||
<g
|
||||
aria-label=".nwx"
|
||||
transform="matrix(1.7893481,0,0,1.782571,-45.41919,-191.90571)"
|
||||
id="text850"
|
||||
style="font-size:5.64444px;line-height:1.25;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans, Normal';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect852);display:inline;fill:#666666;fill-opacity:1">
|
||||
<path
|
||||
d="m 63.088865,241.76928 q 0,-1.36426 0.909505,-2.27376 0.930175,-0.93018 2.480467,-0.93018 1.550291,0 2.459796,0.93018 0.909504,0.9095 0.909504,2.27376 0,1.34359 -0.909504,2.27376 -0.909505,0.9095 -2.459796,0.9095 -1.550292,0 -2.480467,-0.9095 -0.909505,-0.93017 -0.909505,-2.27376 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
|
||||
id="path856" />
|
||||
<path
|
||||
d="m 84.75161,226.78312 q -2.542479,0 -3.782712,2.14974 v 15.79231 h -5.973791 v -22.36554 h 5.601721 l 0.206705,2.56314 q 2.397785,-2.97656 6.428544,-2.97656 3.162595,0 5.105627,1.88103 1.943033,1.88102 1.963703,6.55256 v 14.34537 h -5.994461 v -14.26269 q 0,-2.14973 -0.930175,-2.91454 -0.909505,-0.76482 -2.625161,-0.76482 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
|
||||
id="path858" />
|
||||
<path
|
||||
d="m 126.96089,222.35963 -5.68441,22.36554 h -5.00227 l -4.25814,-14.07665 -4.23746,14.07665 h -4.9816 l -5.705078,-22.36554 h 5.767088 l 2.95589,14.22134 4.05143,-14.22134 h 4.32014 l 4.09277,14.20067 2.91455,-14.20067 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
|
||||
id="path860" />
|
||||
<path
|
||||
d="m 134.77436,222.35963 3.65869,6.65592 3.76204,-6.65592 h 6.3872 l -6.36653,10.93472 6.63525,11.43082 h -6.3872 l -4.01009,-7.04866 -3.94808,7.04866 h -6.42854 l 6.63525,-11.43082 -6.36654,-10.93472 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.3333px;font-family:Roboto;-inkscape-font-specification:'Roboto, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;fill-opacity:1"
|
||||
id="path862" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g2509"
|
||||
transform="matrix(1.2371037,0,0,1.2371037,206.3182,37.341857)">
|
||||
<path
|
||||
transform="matrix(0.16895991,0,0,0.18329422,-134.6942,-19.491243)"
|
||||
id="rect1406-1"
|
||||
style="display:inline;fill:#a11212;fill-opacity:1;stroke:#7e1b1b;stroke-width:9.99999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1444)"
|
||||
d="m 145.18984,56.414257 c -44.32,0 -79.999998,35.68 -79.999998,80.000003 v 199.99805 a 279.99999,279.99999 0 0 0 0,0.002 279.99999,279.99999 0 0 0 0,0.0195 v 199.98242 c 0,44.31999 35.679998,80 79.999998,80 44.32,0 80,-35.68001 80,-80 v -200 a 120,120 0 0 1 120,-120 120,120 0 0 1 120,120 v 282.83592 h 160 V 336.41621 a 279.99999,279.99999 0 0 0 -280,-280.000003 279.99999,279.99999 0 0 0 -0.81055,0 279.99999,279.99999 0 0 0 -133.46484,34.29688 c -14.4245,-20.75262 -38.42829,-34.29883 -65.72461,-34.29883 z M 376.65078,619.64082 c -0.62174,0.0972 -1.2172,0.22317 -1.7793,0.37696 0.5621,-0.15379 1.15756,-0.27973 1.7793,-0.37696 z m -1.7793,0.37696 c -0.5621,0.15378 -1.09071,0.33534 -1.58008,0.53906 0.48937,-0.20372 1.01798,-0.38528 1.58008,-0.53906 z m 338.85742,-0.37696 c 0.62174,0.0972 1.2172,0.22317 1.7793,0.37696 -0.5621,-0.15379 -1.15756,-0.27973 -1.7793,-0.37696 z m 1.7793,0.37696 c 0.5621,0.15378 1.09071,0.33534 1.58008,0.53906 -0.48938,-0.20372 -1.01798,-0.38528 -1.58008,-0.53906 z" />
|
||||
<path
|
||||
transform="matrix(0.16895991,0,0,0.18329422,-134.6942,-19.491243)"
|
||||
id="path897"
|
||||
style="display:inline;fill:#730d0d;fill-opacity:1;stroke:#590d0d;stroke-width:9.99999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1456)"
|
||||
d="m 465.18984,619.25215 v 0.16406 h -85.58398 c -5.49302,0 -9.91602,2.2329 -9.91602,5.00586 0,0.52294 0.15913,1.02696 0.45117,1.5 h -0.44531 c 0.30916,2.03607 1.24102,3.99791 2.81641,5.57227 l 165.59961,165.49414 c 3.92061,3.91808 10.23371,3.91812 14.15429,0 L 717.86757,631.49434 c 1.57569,-1.57463 2.50725,-3.53578 2.81641,-5.57227 h -0.44531 c 0.29216,-0.47304 0.45117,-0.97706 0.45117,-1.5 0,-2.77296 -4.42304,-5.00586 -9.91602,-5.00586 h -85.58398 v -0.16406 z" />
|
||||
<rect
|
||||
style="display:inline;fill:#c32222;fill-opacity:1;stroke:none;stroke-width:1.75982;stroke-linejoin:round;stroke-opacity:1"
|
||||
id="rect914"
|
||||
width="6.7583966"
|
||||
height="82.482399"
|
||||
x="-118.27303"
|
||||
y="1.2969339"
|
||||
rx="3.3791983"
|
||||
ry="3.6658845" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.7 KiB |
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html public "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<body>
|
||||
<h2>Release Notes for 1.0 RC1</h2>
|
||||
<p>This is the first release candidate for the upcoming release of novelWriter 1.0.</p>
|
||||
<p>Since the fifth beta release about four weeks ago, not much has been changed in novelWriter. A
|
||||
few minor tweaks have been made to the GUI.</p>
|
||||
<p>A number of features and tools are now automatically switched off when there is no project or
|
||||
document open for those features to act upon. Previously, this was a bit inconsistent, although no
|
||||
serious bugs have been reported or encountered.</p>
|
||||
<p>Most of the minor changes in this release should not be noticeable to most users. However, there
|
||||
are a couple of noticeable changes.</p>
|
||||
<h3>Typewriter Mode</h3>
|
||||
<p>The "Typewriter Mode" of the editor has been improved. Essentially, this feature is a sort of
|
||||
smart scroll. It tries to keep the cursor stationary in the vertical direction, and will try to
|
||||
scroll the document up when the cursor skips to a new line while typing (or down in case of
|
||||
backspaces). This is similar to the way a typewriter scrolls the paper when hitting the return key.
|
||||
It improves the writing experience as the current active line will stay at the same eye height
|
||||
level on the screen.</p>
|
||||
<p>Previously, the feature would lock the cursor to a given vertical position defined by the user.
|
||||
Now, instead, the cursor will remain stationary in the vertical direction at any position the user
|
||||
sets it to by mouse click or keyboard navigation. The user can define a minimum distance from the
|
||||
top where this feature is activated. These changes makes it more flexible in terms of where the
|
||||
focus is in the editor. The feature can be controlled from the main Preferences.</p>
|
||||
<h3>Switching Syntax Theme</h3>
|
||||
<p>It is now possible to switch syntax highlighting theme without restarting novelWriter.
|
||||
Previously, changing the theme would only half-way update the document, header and footer
|
||||
background and text colours. The new settings would not be fully applied until the application was
|
||||
shut down and started again, thus making it a bit tedious to look through syntax themes to find the
|
||||
one you want.</p>
|
||||
<p>Switching main GUI theme still requires a restart.</p>
|
||||
<p><i>The full changelog is available
|
||||
<a href="https://github.com/vkbo/novelWriter/blob/main/CHANGELOG.md">here</a>.</i></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,7 +10,7 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
||||
background = 255, 255, 255
|
||||
text = 0, 0, 0
|
||||
link = 0, 0, 200
|
||||
headertext = 0, 100, 00
|
||||
headertext = 0, 100, 0
|
||||
headertag = 50, 100, 50
|
||||
emphasis = 150, 110, 30
|
||||
straightquotes = 200, 0, 0
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
[Main]
|
||||
name = Grey Dark
|
||||
author = Veronica Berglyd Olsen
|
||||
credit = Veronica Berglyd Olsen
|
||||
url = https://github.com/vkbo/novelWriter
|
||||
license = CC BY-SA 4.0
|
||||
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
[Syntax]
|
||||
background = 54, 54, 54
|
||||
text = 200, 200, 200
|
||||
link = 200, 200, 200
|
||||
headertext = 225, 225, 225
|
||||
headertag = 225, 225, 225
|
||||
emphasis = 200, 200, 200
|
||||
straightquotes = 200, 200, 200
|
||||
doublequotes = 200, 200, 200
|
||||
singlequotes = 200, 200, 200
|
||||
hidden = 150, 150, 150
|
||||
keyword = 225, 225, 225
|
||||
value = 200, 200, 200
|
||||
spellcheckline = 200, 46, 0
|
||||
tagerror = 46, 200, 0
|
||||
replacetag = 225, 225, 225
|
||||
modifier = 225, 225, 225
|
||||
@@ -0,0 +1,25 @@
|
||||
[Main]
|
||||
name = Grey Light
|
||||
author = Veronica Berglyd Olsen
|
||||
credit = Veronica Berglyd Olsen
|
||||
url = https://github.com/vkbo/novelWriter
|
||||
license = CC BY-SA 4.0
|
||||
licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
||||
|
||||
[Syntax]
|
||||
background = 255, 255, 255
|
||||
text = 20, 20, 20
|
||||
link = 20, 20, 20
|
||||
headertext = 0, 0, 0
|
||||
headertag = 0, 0, 0
|
||||
emphasis = 20, 20, 20
|
||||
straightquotes = 20, 20, 20
|
||||
doublequotes = 20, 20, 20
|
||||
singlequotes = 20, 20, 20
|
||||
hidden = 100, 100, 100
|
||||
keyword = 0, 0, 0
|
||||
value = 20, 20, 20
|
||||
spellcheckline = 200, 0, 0
|
||||
tagerror = 0, 150, 0
|
||||
replacetag = 0, 0, 0
|
||||
modifier = 0, 0, 0
|
||||
+56
-40
@@ -27,12 +27,12 @@
|
||||
|
||||
import logging
|
||||
import configparser
|
||||
import shutil
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
from time import time
|
||||
from shutil import which
|
||||
|
||||
from PyQt5.Qt import PYQT_VERSION_STR
|
||||
from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
|
||||
@@ -44,10 +44,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class Config:
|
||||
|
||||
CNF_STR = 0
|
||||
CNF_INT = 1
|
||||
CNF_BOOL = 2
|
||||
CNF_LIST = 3
|
||||
CNF_STR = 0
|
||||
CNF_INT = 1
|
||||
CNF_BOOL = 2
|
||||
CNF_S_LST = 3
|
||||
CNF_I_LST = 4
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -91,6 +92,7 @@ class Config:
|
||||
self.guiFont = "" # Defaults to system default font
|
||||
self.guiFontSize = 11
|
||||
self.guiScale = 1.0 # Set automatically by Theme class
|
||||
self.lastNotes = "" # The latest release notes that have been shown
|
||||
|
||||
## Sizes
|
||||
self.winGeometry = [1200, 650]
|
||||
@@ -210,12 +212,8 @@ class Config:
|
||||
self.osUnknown = True
|
||||
|
||||
# Other System Info
|
||||
if self.verQtValue >= 50600:
|
||||
self.hostName = QSysInfo.machineHostName()
|
||||
self.kernelVer = QSysInfo.kernelVersion()
|
||||
else:
|
||||
self.hostName = "Unknown"
|
||||
self.kernelVer = "Unknown"
|
||||
self.hostName = "Unknown"
|
||||
self.kernelVer = "Unknown"
|
||||
|
||||
# Packages
|
||||
self.hasEnchant = False # The pyenchant package
|
||||
@@ -320,6 +318,11 @@ class Config:
|
||||
self.errData.append(str(e))
|
||||
self.dataPath = None
|
||||
|
||||
# Host and Kernel
|
||||
if self.verQtValue >= 50600:
|
||||
self.hostName = QSysInfo.machineHostName()
|
||||
self.kernelVer = QSysInfo.kernelVersion()
|
||||
|
||||
# Load recent projects cache
|
||||
self.loadRecentCache()
|
||||
|
||||
@@ -380,29 +383,32 @@ class Config:
|
||||
self.guiFontSize = self._parseLine(
|
||||
cnfParse, cnfSec, "guifontsize", self.CNF_INT, self.guiFontSize
|
||||
)
|
||||
self.lastNotes = self._parseLine(
|
||||
cnfParse, cnfSec, "lastnotes", self.CNF_STR, self.lastNotes
|
||||
)
|
||||
|
||||
## Sizes
|
||||
cnfSec = "Sizes"
|
||||
self.winGeometry = self._parseLine(
|
||||
cnfParse, cnfSec, "geometry", self.CNF_LIST, self.winGeometry
|
||||
cnfParse, cnfSec, "geometry", self.CNF_I_LST, self.winGeometry
|
||||
)
|
||||
self.treeColWidth = self._parseLine(
|
||||
cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth
|
||||
cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth
|
||||
)
|
||||
self.projColWidth = self._parseLine(
|
||||
cnfParse, cnfSec, "projcols", self.CNF_LIST, self.projColWidth
|
||||
cnfParse, cnfSec, "projcols", self.CNF_I_LST, self.projColWidth
|
||||
)
|
||||
self.mainPanePos = self._parseLine(
|
||||
cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos
|
||||
cnfParse, cnfSec, "mainpane", self.CNF_I_LST, self.mainPanePos
|
||||
)
|
||||
self.docPanePos = self._parseLine(
|
||||
cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos
|
||||
cnfParse, cnfSec, "docpane", self.CNF_I_LST, self.docPanePos
|
||||
)
|
||||
self.viewPanePos = self._parseLine(
|
||||
cnfParse, cnfSec, "viewpane", self.CNF_LIST, self.viewPanePos
|
||||
cnfParse, cnfSec, "viewpane", self.CNF_I_LST, self.viewPanePos
|
||||
)
|
||||
self.outlnPanePos = self._parseLine(
|
||||
cnfParse, cnfSec, "outlinepane", self.CNF_LIST, self.outlnPanePos
|
||||
cnfParse, cnfSec, "outlinepane", self.CNF_I_LST, self.outlnPanePos
|
||||
)
|
||||
self.isFullScreen = self._parseLine(
|
||||
cnfParse, cnfSec, "fullscreen", self.CNF_BOOL, self.isFullScreen
|
||||
@@ -480,10 +486,10 @@ class Config:
|
||||
cnfParse, cnfSec, "autoscrollpos", self.CNF_INT, self.autoScrollPos
|
||||
)
|
||||
self.fmtSingleQuotes = self._parseLine(
|
||||
cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes
|
||||
cnfParse, cnfSec, "fmtsinglequote", self.CNF_S_LST, self.fmtSingleQuotes
|
||||
)
|
||||
self.fmtDoubleQuotes = self._parseLine(
|
||||
cnfParse, cnfSec, "fmtdoublequote", self.CNF_LIST, self.fmtDoubleQuotes
|
||||
cnfParse, cnfSec, "fmtdoublequote", self.CNF_S_LST, self.fmtDoubleQuotes
|
||||
)
|
||||
self.spellTool = self._parseLine(
|
||||
cnfParse, cnfSec, "spelltool", self.CNF_STR, self.spellTool
|
||||
@@ -584,6 +590,7 @@ class Config:
|
||||
cnfParse.set(cnfSec, "guidark", str(self.guiDark))
|
||||
cnfParse.set(cnfSec, "guifont", str(self.guiFont))
|
||||
cnfParse.set(cnfSec, "guifontsize", str(self.guiFontSize))
|
||||
cnfParse.set(cnfSec, "lastnotes", str(self.lastNotes))
|
||||
|
||||
## Sizes
|
||||
cnfSec = "Sizes"
|
||||
@@ -896,23 +903,28 @@ class Config:
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _unpackList(self, inStr, listLen, listDefault, castTo=int):
|
||||
"""Unpack a comma separated string of items into a list.
|
||||
"""
|
||||
inData = inStr.split(",")
|
||||
outData = []
|
||||
for i in range(listLen):
|
||||
try:
|
||||
outData.append(castTo(inData[i]))
|
||||
except Exception:
|
||||
outData.append(listDefault[i])
|
||||
return outData
|
||||
|
||||
def _packList(self, inData):
|
||||
"""Pack a list of items into a comma separated string.
|
||||
"""Pack a list of items into a comma-separated string.
|
||||
"""
|
||||
return ", ".join([str(inVal) for inVal in inData])
|
||||
|
||||
def _unpackList(self, inStr, listDefault, cnfType):
|
||||
"""Unpack a comma-separated string of items into a list.
|
||||
"""
|
||||
inData = inStr.split(",")
|
||||
outData = listDefault.copy()
|
||||
for i in range(min(len(inData), len(listDefault))):
|
||||
try:
|
||||
if cnfType == self.CNF_S_LST:
|
||||
outData[i] = inData[i].strip()
|
||||
elif cnfType == self.CNF_I_LST:
|
||||
outData[i] = int(inData[i].strip())
|
||||
else:
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
return outData
|
||||
|
||||
def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault):
|
||||
"""Parse a line and return the correct datatype.
|
||||
"""
|
||||
@@ -925,18 +937,24 @@ class Config:
|
||||
return cnfParse.getint(cnfSec, cnfName)
|
||||
elif cnfType == self.CNF_BOOL:
|
||||
return cnfParse.getboolean(cnfSec, cnfName)
|
||||
elif cnfType == self.CNF_LIST:
|
||||
elif cnfType == self.CNF_I_LST:
|
||||
return self._unpackList(
|
||||
cnfParse.get(cnfSec, cnfName), len(cnfDefault), cnfDefault
|
||||
cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_I_LST
|
||||
)
|
||||
elif cnfType == self.CNF_S_LST:
|
||||
return self._unpackList(
|
||||
cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_S_LST
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.error("Failed to load value from config file.")
|
||||
logger.error(str(e))
|
||||
return cnfDefault
|
||||
|
||||
return cnfDefault
|
||||
|
||||
def _checkNone(self, checkVal):
|
||||
"""Convert a string to a none type.
|
||||
"""Return a NoneType if the value correspomds to None, otherwise
|
||||
return the value unchanged.
|
||||
"""
|
||||
if checkVal is None:
|
||||
return None
|
||||
@@ -956,10 +974,8 @@ class Config:
|
||||
self.hasEnchant = False
|
||||
logger.debug("Checking package 'pyenchant': Missing")
|
||||
|
||||
try:
|
||||
self.hasAssistant = which("assistant")
|
||||
except Exception:
|
||||
self.hasAssistant = False
|
||||
assistPath = shutil.which("assistant")
|
||||
self.hasAssistant = assistPath is not None
|
||||
if self.hasAssistant:
|
||||
logger.debug("Checking executable 'assistant': Ok")
|
||||
else:
|
||||
|
||||
+2
-1
@@ -168,7 +168,8 @@ class NWDoc():
|
||||
os.unlink(docPath)
|
||||
os.rename(docTemp, docPath)
|
||||
|
||||
self.theParent.setStatus("Saved Document: %s" % self._theItem.itemName)
|
||||
if self._theItem is not None:
|
||||
self.theParent.setStatus("Saved Document: %s" % self._theItem.itemName)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
+10
-6
@@ -43,7 +43,7 @@ class NWItem():
|
||||
self.itemName = ""
|
||||
self.itemHandle = None
|
||||
self.itemParent = None
|
||||
self.itemOrder = None
|
||||
self.itemOrder = 0
|
||||
self.itemType = nwItemType.NO_TYPE
|
||||
self.itemClass = nwItemClass.NO_CLASS
|
||||
self.itemLayout = nwItemLayout.NO_LAYOUT
|
||||
@@ -96,20 +96,21 @@ class NWItem():
|
||||
return False
|
||||
|
||||
if "handle" in xItem.attrib:
|
||||
self.itemHandle = xItem.attrib["handle"]
|
||||
self.setHandle(xItem.attrib["handle"])
|
||||
else:
|
||||
logger.error("XML item entry does not have a handle")
|
||||
return False
|
||||
|
||||
if "parent" in xItem.attrib:
|
||||
self.itemParent = xItem.attrib["parent"]
|
||||
self.setParent(xItem.attrib["parent"])
|
||||
|
||||
if "order" in xItem.attrib:
|
||||
self.setOrder(xItem.attrib["order"])
|
||||
|
||||
retStatus = True
|
||||
for xValue in xItem:
|
||||
if xValue.tag == "name":
|
||||
self.setName(xValue.text)
|
||||
elif xValue.tag == "order":
|
||||
self.setOrder(xValue.text)
|
||||
elif xValue.tag == "type":
|
||||
self.setType(xValue.text)
|
||||
elif xValue.tag == "class":
|
||||
@@ -156,7 +157,10 @@ class NWItem():
|
||||
def setName(self, theName):
|
||||
"""Set the item name.
|
||||
"""
|
||||
self.itemName = theName.strip()
|
||||
if isinstance(theName, str):
|
||||
self.itemName = theName.strip()
|
||||
else:
|
||||
self.itemName = ""
|
||||
return
|
||||
|
||||
def setHandle(self, theHandle):
|
||||
|
||||
+3
-15
@@ -176,11 +176,7 @@ class OptionState():
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return self.theState[getGroup][getName]
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return self.theState[getGroup][getName]
|
||||
return defaultValue
|
||||
|
||||
def getString(self, getGroup, getName, defaultValue):
|
||||
@@ -189,11 +185,7 @@ class OptionState():
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return str(self.theState[getGroup][getName])
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return str(self.theState[getGroup][getName])
|
||||
return defaultValue
|
||||
|
||||
def getInt(self, getGroup, getName, defaultValue):
|
||||
@@ -228,11 +220,7 @@ class OptionState():
|
||||
"""
|
||||
if getGroup in self.theState:
|
||||
if getName in self.theState[getGroup]:
|
||||
try:
|
||||
return bool(self.theState[getGroup][getName])
|
||||
except Exception as e:
|
||||
logger.warning(str(e))
|
||||
return defaultValue
|
||||
return bool(self.theState[getGroup][getName])
|
||||
return defaultValue
|
||||
|
||||
##
|
||||
|
||||
+67
-70
@@ -28,12 +28,10 @@
|
||||
import nw
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from lxml import etree
|
||||
from time import time
|
||||
from shutil import make_archive, unpack_archive, copyfile
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from nw.core.tree import NWTree
|
||||
from nw.core.item import NWItem
|
||||
@@ -436,26 +434,15 @@ class NWProject():
|
||||
xRoot = nwXML.getroot()
|
||||
nwxRoot = xRoot.tag
|
||||
|
||||
appVersion = "Unknown"
|
||||
hexVersion = "0x0"
|
||||
fileVersion = "Unknown"
|
||||
self.saveCount = 0
|
||||
self.autoCount = 0
|
||||
|
||||
if "appVersion" in xRoot.attrib:
|
||||
appVersion = xRoot.attrib["appVersion"]
|
||||
if "hexVersion" in xRoot.attrib:
|
||||
hexVersion = xRoot.attrib["hexVersion"]
|
||||
if "fileVersion" in xRoot.attrib:
|
||||
fileVersion = xRoot.attrib["fileVersion"]
|
||||
appVersion = xRoot.attrib.get("appVersion", "Unknown")
|
||||
hexVersion = xRoot.attrib.get("hexVersion", "0x0")
|
||||
fileVersion = xRoot.attrib.get("fileVersion", "Unknown")
|
||||
|
||||
# The following are deprecated and will be removed
|
||||
if "saveCount" in xRoot.attrib:
|
||||
self.saveCount = checkInt(xRoot.attrib["saveCount"], 0, False)
|
||||
if "autoCount" in xRoot.attrib:
|
||||
self.autoCount = checkInt(xRoot.attrib["autoCount"], 0, False)
|
||||
if "editTime" in xRoot.attrib:
|
||||
self.editTime = checkInt(xRoot.attrib["editTime"], 0, False)
|
||||
# The settings have been moved to the <project> tag
|
||||
self.saveCount = checkInt(xRoot.attrib.get("saveCount", 0), 0, False)
|
||||
self.autoCount = checkInt(xRoot.attrib.get("autoCount", 0), 0, False)
|
||||
self.editTime = checkInt(xRoot.attrib.get("editTime", 0), 0, False)
|
||||
|
||||
logger.verbose("XML root is %s" % nwxRoot)
|
||||
logger.verbose("File version is %s" % fileVersion)
|
||||
@@ -483,20 +470,19 @@ class NWProject():
|
||||
# parser will lose the autoReplace settings if allowed to
|
||||
# read the file. Introduced in version 0.10.
|
||||
|
||||
if fileVersion == "1.0" and self.mainConf.showGUI:
|
||||
msgBox = QMessageBox()
|
||||
msgRes = msgBox.question(self.theParent, "Old Project Version", (
|
||||
if fileVersion == "1.0":
|
||||
msgRes = self.theParent.askQuestion("Old Project Version", (
|
||||
"The project file and data is created by a novelWriter version "
|
||||
"lower than 0.7. Do you want to upgrade the project to the "
|
||||
"most recent format?<br><br>Note that after the upgrade, you "
|
||||
"cannot open the project with an older version of novelWriter "
|
||||
"any more, so make sure you have a recent backup."
|
||||
))
|
||||
if msgRes != QMessageBox.Yes:
|
||||
if not msgRes:
|
||||
self.clearProject()
|
||||
return False
|
||||
|
||||
elif fileVersion != "1.1" and fileVersion != "1.2" and self.mainConf.showGUI:
|
||||
elif fileVersion != "1.1" and fileVersion != "1.2":
|
||||
self.makeAlert((
|
||||
"Unknown or unsupported novelWriter project file format. "
|
||||
"The project cannot be opened by this version of novelWriter. "
|
||||
@@ -510,9 +496,8 @@ class NWProject():
|
||||
# Check novelWriter Version
|
||||
# =========================
|
||||
|
||||
if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.showGUI:
|
||||
msgBox = QMessageBox()
|
||||
msgRes = msgBox.question(self.theParent, "Version Conflict", (
|
||||
if int(hexVersion, 16) > int(nw.__hexversion__, 16):
|
||||
msgRes = self.theParent.askQuestion("Version Conflict", (
|
||||
"This project was saved by a newer version of novelWriter, version %s. "
|
||||
"This is version %s. If you continue to open the project, some attributes "
|
||||
"and settings may not be preserved, but the overall project should be fine. "
|
||||
@@ -520,7 +505,7 @@ class NWProject():
|
||||
) % (
|
||||
appVersion, nw.__version__
|
||||
))
|
||||
if msgRes != QMessageBox.Yes:
|
||||
if not msgRes:
|
||||
self.clearProject()
|
||||
return False
|
||||
|
||||
@@ -835,15 +820,15 @@ class NWProject():
|
||||
|
||||
try:
|
||||
self._clearLockFile()
|
||||
make_archive(baseName, "zip", self.projPath, ".")
|
||||
shutil.make_archive(baseName, "zip", self.projPath, ".")
|
||||
self._writeLockFile()
|
||||
logger.info("Backup written to: %s" % archName)
|
||||
if doNotify:
|
||||
self.theParent.makeAlert(
|
||||
"Backup archive file written to: %s.zip" % os.path.join(cleanName, archName),
|
||||
nwAlert.INFO
|
||||
)
|
||||
else:
|
||||
logger.info("Backup written to: %s" % archName)
|
||||
|
||||
except Exception as e:
|
||||
self.theParent.makeAlert(
|
||||
["Could not write backup archive.", str(e)],
|
||||
@@ -874,7 +859,7 @@ class NWProject():
|
||||
|
||||
self.setProjectPath(projPath, newProject=True)
|
||||
try:
|
||||
unpack_archive(pkgSample, projPath)
|
||||
shutil.unpack_archive(pkgSample, projPath)
|
||||
isSuccess = True
|
||||
except Exception as e:
|
||||
self.makeAlert(
|
||||
@@ -887,14 +872,14 @@ class NWProject():
|
||||
try:
|
||||
srcProj = os.path.join(srcSample, nwFiles.PROJ_FILE)
|
||||
dstProj = os.path.join(projPath, nwFiles.PROJ_FILE)
|
||||
copyfile(srcProj, dstProj)
|
||||
shutil.copyfile(srcProj, dstProj)
|
||||
|
||||
srcContent = os.path.join(srcSample, "content")
|
||||
dstContent = os.path.join(projPath, "content")
|
||||
for srcFile in os.listdir(srcContent):
|
||||
srcDoc = os.path.join(srcContent, srcFile)
|
||||
dstDoc = os.path.join(dstContent, srcFile)
|
||||
copyfile(srcDoc, dstDoc)
|
||||
shutil.copyfile(srcDoc, dstDoc)
|
||||
|
||||
isSuccess = True
|
||||
|
||||
@@ -998,11 +983,15 @@ class NWProject():
|
||||
"You must set a valid backup path in preferences to use "
|
||||
"the automatic project backup feature."
|
||||
), nwAlert.WARN)
|
||||
return False
|
||||
|
||||
if self.projName == "":
|
||||
self.theParent.makeAlert((
|
||||
"You must set a valid project name in project settings to "
|
||||
"use the automatic project backup feature."
|
||||
), nwAlert.WARN)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def setSpellCheck(self, theMode):
|
||||
@@ -1011,12 +1000,15 @@ class NWProject():
|
||||
if self.spellCheck != theMode:
|
||||
self.spellCheck = theMode
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
return self.spellCheck
|
||||
|
||||
def setSpellLang(self, theLang):
|
||||
"""Set the project-specific spell check language.
|
||||
"""
|
||||
self.projLang = checkString(theLang, None, True)
|
||||
theLang = checkString(theLang, None, True)
|
||||
if self.projLang != theLang:
|
||||
self.projLang = theLang
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
def setAutoOutline(self, theMode):
|
||||
@@ -1025,7 +1017,7 @@ class NWProject():
|
||||
if self.autoOutline != theMode:
|
||||
self.autoOutline = theMode
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
return self.autoOutline
|
||||
|
||||
def setTreeOrder(self, newOrder):
|
||||
"""A list representing the linear/flattened order of project
|
||||
@@ -1072,7 +1064,7 @@ class NWProject():
|
||||
if nwItem.itemStatus in replaceMap.keys():
|
||||
nwItem.setStatus(replaceMap[nwItem.itemStatus])
|
||||
self.setProjectChanged(True)
|
||||
return
|
||||
return True
|
||||
|
||||
def setImportColours(self, newCols):
|
||||
"""Update the list of note file importance flags. Also iterate
|
||||
@@ -1084,14 +1076,15 @@ class NWProject():
|
||||
if nwItem.itemStatus in replaceMap.keys():
|
||||
nwItem.setStatus(replaceMap[nwItem.itemStatus])
|
||||
self.setProjectChanged(True)
|
||||
return
|
||||
return True
|
||||
|
||||
def setAutoReplace(self, autoReplace):
|
||||
"""Update the auto-replace dictionary. This replaces the entire
|
||||
dictionary, so alterations have to be made in a copy.
|
||||
"""
|
||||
self.autoReplace = autoReplace
|
||||
return
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
def setTitleFormat(self, titleFormat):
|
||||
"""Set the formatting of titles in the project.
|
||||
@@ -1099,7 +1092,7 @@ class NWProject():
|
||||
for valKey, valEntry in titleFormat.items():
|
||||
if valKey in self.titleFormat:
|
||||
self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey], False)
|
||||
return
|
||||
return True
|
||||
|
||||
def setProjectChanged(self, bValue):
|
||||
"""Toggle the project changed flag, and propagate the
|
||||
@@ -1132,13 +1125,11 @@ class NWProject():
|
||||
sentItems = []
|
||||
iterItems = self.projTree.handles()
|
||||
n = 0
|
||||
nMax = len(iterItems)
|
||||
nMax = min(len(iterItems), 10000)
|
||||
while n < nMax:
|
||||
tHandle = iterItems[n]
|
||||
tItem = self.projTree[tHandle]
|
||||
n += 1
|
||||
if n > 10000:
|
||||
return # Just in case
|
||||
if tItem is None:
|
||||
# Technically a bug since treeOrder is built from the
|
||||
# same data as projTree
|
||||
@@ -1154,10 +1145,11 @@ class NWProject():
|
||||
yield tItem
|
||||
elif tItem.itemParent in iterItems:
|
||||
# Item's parent exists, but hasn't been sent yet, so add
|
||||
# it again to the end
|
||||
# it again to the end, but make sure this doesn't get
|
||||
# out hand, so we cap at 10000 items
|
||||
logger.warning("Item %s found before its parent" % tHandle)
|
||||
iterItems.append(tHandle)
|
||||
nMax = len(iterItems)
|
||||
nMax = min(len(iterItems), 10000)
|
||||
else:
|
||||
# Item is orphaned
|
||||
logger.error("Item %s has no parent in current tree" % tHandle)
|
||||
@@ -1196,13 +1188,12 @@ class NWProject():
|
||||
if not os.path.isfile(lockFile):
|
||||
return []
|
||||
|
||||
theLines = []
|
||||
try:
|
||||
with open(lockFile, mode="r", encoding="utf8") as inFile:
|
||||
theData = inFile.read()
|
||||
theLines = theData.splitlines()
|
||||
if len(theLines) == 4:
|
||||
return theLines
|
||||
else:
|
||||
if len(theLines) != 4:
|
||||
return ["ERROR"]
|
||||
|
||||
except Exception as e:
|
||||
@@ -1210,7 +1201,7 @@ class NWProject():
|
||||
logger.error(str(e))
|
||||
return ["ERROR"]
|
||||
|
||||
return ["ERROR"]
|
||||
return theLines
|
||||
|
||||
def _writeLockFile(self):
|
||||
"""Writes a lock file to the project folder.
|
||||
@@ -1243,13 +1234,12 @@ class NWProject():
|
||||
if os.path.isfile(lockFile):
|
||||
try:
|
||||
os.unlink(lockFile)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Failed to remove project lockfile")
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
return None
|
||||
return True
|
||||
|
||||
def _checkFolder(self, thePath):
|
||||
"""Check if a folder exists, and if it doesn't, create it.
|
||||
@@ -1292,7 +1282,7 @@ class NWProject():
|
||||
back into the project tree.
|
||||
"""
|
||||
if self.projPath is None:
|
||||
return
|
||||
return False
|
||||
|
||||
# Then check the files in the data folder
|
||||
logger.debug("Checking files in project content folder")
|
||||
@@ -1352,7 +1342,7 @@ class NWProject():
|
||||
orphItem.setLayout(oLayout)
|
||||
self.projTree.append(oHandle, None, orphItem)
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
def _appendSessionStats(self):
|
||||
"""Append session statistics to the sessions log file.
|
||||
@@ -1363,21 +1353,27 @@ class NWProject():
|
||||
sessionFile = os.path.join(self.projMeta, nwFiles.SESS_STATS)
|
||||
isFile = os.path.isfile(sessionFile)
|
||||
|
||||
with open(sessionFile, mode="a+", encoding="utf8") as outFile:
|
||||
if not isFile:
|
||||
# It's a new file, so add a header
|
||||
if self.lastWCount > 0:
|
||||
outFile.write("# Offset %d\n" % self.lastWCount)
|
||||
outFile.write("# %-17s %-19s %8s %8s\n" % (
|
||||
"Start Time", "End Time", "Novel", "Notes"
|
||||
try:
|
||||
with open(sessionFile, mode="a+", encoding="utf8") as outFile:
|
||||
if not isFile:
|
||||
# It's a new file, so add a header
|
||||
if self.lastWCount > 0:
|
||||
outFile.write("# Offset %d\n" % self.lastWCount)
|
||||
outFile.write("# %-17s %-19s %8s %8s\n" % (
|
||||
"Start Time", "End Time", "Novel", "Notes"
|
||||
))
|
||||
|
||||
outFile.write("%-19s %-19s %8d %8d\n" % (
|
||||
formatTimeStamp(self.projOpened),
|
||||
formatTimeStamp(time()),
|
||||
self.novelWCount,
|
||||
self.notesWCount,
|
||||
))
|
||||
|
||||
outFile.write("%-19s %-19s %8d %8d\n" % (
|
||||
formatTimeStamp(self.projOpened),
|
||||
formatTimeStamp(time()),
|
||||
self.novelWCount,
|
||||
self.notesWCount,
|
||||
))
|
||||
except Exception as e:
|
||||
logger.error("Failed to write session stats file")
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -1490,7 +1486,8 @@ class NWProject():
|
||||
os.unlink(rmFile)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
# END Class NWProject
|
||||
|
||||
+18
-14
@@ -28,8 +28,7 @@
|
||||
import nw
|
||||
import logging
|
||||
import os
|
||||
|
||||
from difflib import get_close_matches
|
||||
import difflib
|
||||
|
||||
from nw.constants import nwConst, isoLanguage
|
||||
|
||||
@@ -70,14 +69,16 @@ class NWSpellCheck():
|
||||
"""
|
||||
if self.projectDict is not None and newWord not in self.projDict:
|
||||
newWord = newWord.strip()
|
||||
self.projDict.append(newWord)
|
||||
try:
|
||||
with open(self.projectDict, mode="a+", encoding="utf-8") as outFile:
|
||||
outFile.write("%s\n" % newWord)
|
||||
self.projDict.append(newWord)
|
||||
except Exception as e:
|
||||
logger.error("Failed to add word to project word list %s" % str(self.projectDict))
|
||||
logger.error(str(e))
|
||||
return
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def listDictionaries(self):
|
||||
"""Dummy function.
|
||||
@@ -109,9 +110,12 @@ class NWSpellCheck():
|
||||
"""
|
||||
self.projDict = []
|
||||
if projectDict is not None:
|
||||
self.projectDict = projectDict
|
||||
if not os.path.isfile(projectDict):
|
||||
return
|
||||
self.projectDict = None
|
||||
return False
|
||||
else:
|
||||
self.projectDict = projectDict
|
||||
|
||||
try:
|
||||
logger.debug("Loading project word list")
|
||||
with open(projectDict, mode="r", encoding="utf-8") as wordsFile:
|
||||
@@ -123,7 +127,9 @@ class NWSpellCheck():
|
||||
except Exception as e:
|
||||
logger.error("Failed to load project word list")
|
||||
logger.error(str(e))
|
||||
return
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# END Class NWSpellCheck
|
||||
|
||||
@@ -287,7 +293,7 @@ class NWSpellSimple(NWSpellCheck):
|
||||
if len(theWord) == 0:
|
||||
return []
|
||||
|
||||
theMatches = get_close_matches(theWord.lower(), self.WORDS, n=10, cutoff=0.75)
|
||||
theMatches = difflib.get_close_matches(theWord.lower(), self.WORDS, n=10, cutoff=0.75)
|
||||
theOptions = []
|
||||
for aWord in theMatches:
|
||||
if len(aWord) == 0:
|
||||
@@ -314,14 +320,12 @@ class NWSpellSimple(NWSpellCheck):
|
||||
retList = []
|
||||
for dictFile in os.listdir(self.mainConf.dictPath):
|
||||
|
||||
theBits = os.path.splitext(dictFile)
|
||||
if len(theBits) != 2:
|
||||
continue
|
||||
if theBits[1] != ".dict":
|
||||
fRoot, fExt = os.path.splitext(dictFile)
|
||||
if fExt != ".dict":
|
||||
continue
|
||||
|
||||
spName = "%s [%s]" % (self.expandLanguage(theBits[0]), nwConst.SP_INTERNAL)
|
||||
retList.append((theBits[0], spName))
|
||||
spName = "%s [%s]" % (self.expandLanguage(fRoot), nwConst.SP_INTERNAL)
|
||||
retList.append((fRoot, spName))
|
||||
|
||||
return retList
|
||||
|
||||
|
||||
+6
-13
@@ -79,6 +79,7 @@ class NWStatus():
|
||||
theStatus = checkInt(theStatus, 0, False)
|
||||
if theStatus >= 0 and theStatus < self._theLength:
|
||||
return self._theLabels[theStatus]
|
||||
return self._theLabels[0]
|
||||
|
||||
def setNewEntries(self, newList):
|
||||
"""Update the list of entries after they have been modified by
|
||||
@@ -108,7 +109,8 @@ class NWStatus():
|
||||
return
|
||||
|
||||
def countEntry(self, theLabel):
|
||||
"""Lookup the usage count of a given entry.
|
||||
"""Increment the counter for a given label. This should be used
|
||||
together with resetCounts in a loop over project items.
|
||||
"""
|
||||
theIndex = self.lookupEntry(theLabel)
|
||||
if theIndex is not None:
|
||||
@@ -136,18 +138,9 @@ class NWStatus():
|
||||
|
||||
for xChild in xParent:
|
||||
theLabels.append(xChild.text)
|
||||
if "red" in xChild.attrib:
|
||||
cR = checkInt(xChild.attrib["red"], 0, False)
|
||||
else:
|
||||
cR = 0
|
||||
if "green" in xChild.attrib:
|
||||
cG = checkInt(xChild.attrib["green"], 0, False)
|
||||
else:
|
||||
cG = 0
|
||||
if "blue" in xChild.attrib:
|
||||
cB = checkInt(xChild.attrib["blue"], 0, False)
|
||||
else:
|
||||
cB = 0
|
||||
cR = checkInt(xChild.attrib.get("red", 0), 0, False)
|
||||
cG = checkInt(xChild.attrib.get("green", 0), 0, False)
|
||||
cB = checkInt(xChild.attrib.get("blue", 0), 0, False)
|
||||
theColours.append((cR, cG, cB))
|
||||
|
||||
if len(theLabels) > 0:
|
||||
|
||||
+11
-9
@@ -68,16 +68,16 @@ class ToHtml(Tokenizer):
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setPreview(self, forPreview, doComments, doSynopsis):
|
||||
def setPreview(self, doComments, doSynopsis):
|
||||
"""If we're using this class to generate markdown preview, we
|
||||
need to make a few changes to formatting, which is managed by
|
||||
these flags.
|
||||
"""
|
||||
if forPreview:
|
||||
self.genMode = self.M_PREVIEW
|
||||
self.doKeywords = True
|
||||
self.doComments = doComments
|
||||
self.doSynopsis = doSynopsis
|
||||
self.genMode = self.M_PREVIEW
|
||||
self.doKeywords = True
|
||||
self.doComments = doComments
|
||||
self.doSynopsis = doSynopsis
|
||||
|
||||
return
|
||||
|
||||
def setStyles(self, cssStyles):
|
||||
@@ -141,11 +141,13 @@ class ToHtml(Tokenizer):
|
||||
# For novel files for export, we bump the titles one level
|
||||
# up as this is more useful for printing and word processor
|
||||
# imports.
|
||||
h1 = "h1 class='title'"
|
||||
h1Cl = " class='title'"
|
||||
h1 = "h1"
|
||||
h2 = "h1"
|
||||
h3 = "h2"
|
||||
h4 = "h3"
|
||||
else:
|
||||
h1Cl = ""
|
||||
h1 = "h1"
|
||||
h2 = "h2"
|
||||
h3 = "h3"
|
||||
@@ -193,7 +195,7 @@ class ToHtml(Tokenizer):
|
||||
else:
|
||||
aNm = ""
|
||||
|
||||
# Process TextType
|
||||
# Process Text Type
|
||||
if tType == self.T_EMPTY:
|
||||
if parStyle is None:
|
||||
parStyle = ""
|
||||
@@ -214,7 +216,7 @@ class ToHtml(Tokenizer):
|
||||
|
||||
elif tType == self.T_HEAD1:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
tmpResult.append("<%s%s>%s%s</%s>\n" % (h1, hStyle, aNm, tHead, h1))
|
||||
tmpResult.append("<%s%s%s>%s%s</%s>\n" % (h1, h1Cl, hStyle, aNm, tHead, h1))
|
||||
|
||||
elif tType == self.T_HEAD2:
|
||||
tHead = tText.replace(r"\\", "<br/>")
|
||||
|
||||
@@ -180,7 +180,7 @@ class Tokenizer():
|
||||
##
|
||||
|
||||
def addRootHeading(self, theHandle):
|
||||
"""Add a heading at the start if a new root folder.
|
||||
"""Add a heading at the start of a new root folder.
|
||||
"""
|
||||
theItem = self.theProject.projTree[theHandle]
|
||||
if theItem is None:
|
||||
@@ -205,7 +205,7 @@ class Tokenizer():
|
||||
self.theHandle = theHandle
|
||||
self.theItem = self.theProject.projTree[theHandle]
|
||||
if self.theItem is None:
|
||||
return
|
||||
return False
|
||||
|
||||
if theText is not None:
|
||||
# If the text is set, just use that
|
||||
@@ -234,7 +234,7 @@ class Tokenizer():
|
||||
self.isNote = self.theItem.itemLayout == nwItemLayout.NOTE
|
||||
self.isNovel = self.isBook or self.isUnNum or self.isChap or self.isScene
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
def getResult(self):
|
||||
"""Return the result from the conversion.
|
||||
@@ -244,6 +244,8 @@ class Tokenizer():
|
||||
def getResultSize(self):
|
||||
"""Return the size of the result from the conversion.
|
||||
"""
|
||||
if self.theResult is None:
|
||||
return 0
|
||||
return len(self.theResult)
|
||||
|
||||
def getFilteredMarkdown(self):
|
||||
@@ -445,7 +447,7 @@ class Tokenizer():
|
||||
"""
|
||||
# No special header formatting for notes and no-layout files
|
||||
if self.isNone or self.isNote:
|
||||
return
|
||||
return False
|
||||
|
||||
# For novel files, we need to handle chapter numbering, scene
|
||||
# numbering, and scene breaks
|
||||
@@ -479,8 +481,7 @@ class Tokenizer():
|
||||
if self.isUnNum:
|
||||
tTemp = self._formatHeading(self.fmtUnNum, tToken[2])
|
||||
elif tToken[2].startswith("*"):
|
||||
tTemp = self._formatHeading(self.fmtUnNum, tToken[2])
|
||||
tTemp = tTemp[1:].lstrip()
|
||||
tTemp = self._formatHeading(self.fmtUnNum, tToken[2][1:].lstrip())
|
||||
else:
|
||||
self.numChapter += 1
|
||||
tTemp = self._formatHeading(self.fmtChapter, tToken[2])
|
||||
@@ -663,7 +664,7 @@ class Tokenizer():
|
||||
self.A_LEFT
|
||||
)
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
|
||||
+15
-5
@@ -141,6 +141,15 @@ def _numberToWordEN(numVal):
|
||||
tenWord = ""
|
||||
hunWord = ""
|
||||
|
||||
if not isinstance(numVal, int):
|
||||
return "[NaN]"
|
||||
|
||||
if numVal < 0:
|
||||
return "[Negative]"
|
||||
|
||||
if numVal > 999:
|
||||
return "[Out of Range]"
|
||||
|
||||
if numVal == 0:
|
||||
return "Zero"
|
||||
|
||||
@@ -166,19 +175,20 @@ def _numberToWordEN(numVal):
|
||||
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine",
|
||||
}
|
||||
|
||||
retVale = ""
|
||||
hunWord = theHundreds.get(hunVal, "")
|
||||
if tenVal == 10:
|
||||
oneWord = theTeens.get(oneVal, "")
|
||||
return f"{hunWord} {oneWord}".strip()
|
||||
retVale = f"{hunWord} {oneWord}".strip()
|
||||
else:
|
||||
oneWord = theOnes.get(oneVal, "")
|
||||
if tenVal == 0:
|
||||
return f"{hunWord} {oneWord}".strip()
|
||||
retVale = f"{hunWord} {oneWord}".strip()
|
||||
else:
|
||||
tenWord = theTens.get(tenVal, "")
|
||||
if oneVal == 0:
|
||||
return f"{hunWord} {tenWord}".strip()
|
||||
retVale = f"{hunWord} {tenWord}".strip()
|
||||
else:
|
||||
return f"{hunWord} {tenWord}-{oneWord}".strip()
|
||||
retVale = f"{hunWord} {tenWord}-{oneWord}".strip()
|
||||
|
||||
return ""
|
||||
return retVale
|
||||
|
||||
+16
-7
@@ -48,8 +48,10 @@ class NWTree():
|
||||
self._treeOrder = [] # The order of the tree items on the tree view
|
||||
self._treeRoots = [] # The root items of the tree
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
self._archRoot = None # The handle of the archive root folder
|
||||
self._theIndex = 0 # The current iterator index
|
||||
self._treeChanged = False # True if tree structure has changed
|
||||
|
||||
self._handleSeed = None # Used for generating handles for testing
|
||||
|
||||
return
|
||||
@@ -83,14 +85,15 @@ class NWTree():
|
||||
if tHandle is None:
|
||||
tHandle = self._makeHandle()
|
||||
|
||||
if tHandle in self._projTree:
|
||||
logger.warning("Duplicate handle %s detected, skipping" % tHandle)
|
||||
return False
|
||||
|
||||
logger.verbose("Adding item %s with parent %s" % (str(tHandle), str(pHandle)))
|
||||
|
||||
nwItem.setHandle(tHandle)
|
||||
nwItem.setParent(pHandle)
|
||||
|
||||
self._projTree[tHandle] = nwItem
|
||||
self._treeOrder.append(tHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.ROOT:
|
||||
logger.verbose("Item %s is a root item" % str(tHandle))
|
||||
self._treeRoots.append(tHandle)
|
||||
@@ -104,10 +107,13 @@ class NWTree():
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
return False
|
||||
|
||||
self._projTree[tHandle] = nwItem
|
||||
self._treeOrder.append(tHandle)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Pack the content of the tree into the provided XML object. In
|
||||
@@ -176,8 +182,9 @@ class NWTree():
|
||||
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
return False
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
def sumWords(self):
|
||||
"""Loops over all entries and adds up the word counts.
|
||||
@@ -244,6 +251,8 @@ class NWTree():
|
||||
return True
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return False
|
||||
return True
|
||||
@@ -397,7 +406,7 @@ class NWTree():
|
||||
del self._projTree[tHandle]
|
||||
else:
|
||||
logger.warning("Failed to delete item %s: item not found" % tHandle)
|
||||
return False
|
||||
return
|
||||
|
||||
if tHandle in self._treeRoots:
|
||||
self._treeRoots.remove(tHandle)
|
||||
@@ -408,7 +417,7 @@ class NWTree():
|
||||
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
def __contains__(self, tHandle):
|
||||
"""Checks if a handle exists in the tree.
|
||||
|
||||
+3
-8
@@ -135,10 +135,9 @@ class NWErrorMessage(QDialog):
|
||||
# END Class NWErrorMessage
|
||||
|
||||
|
||||
def exceptionHandler(exType, exValue, exTrace, testMode=False):
|
||||
def exceptionHandler(exType, exValue, exTrace):
|
||||
"""Function to catch unhandled global exceptions.
|
||||
"""
|
||||
import nw
|
||||
import logging
|
||||
from traceback import print_tb
|
||||
from PyQt5.QtWidgets import qApp
|
||||
@@ -160,8 +159,7 @@ def exceptionHandler(exType, exValue, exTrace, testMode=False):
|
||||
|
||||
errMsg = NWErrorMessage(nwGUI)
|
||||
errMsg.setMessage(exType, exValue, exTrace)
|
||||
if nw.CONFIG.showGUI:
|
||||
errMsg.exec_()
|
||||
errMsg.exec_()
|
||||
|
||||
try:
|
||||
# Try a controlled shudown
|
||||
@@ -173,10 +171,7 @@ def exceptionHandler(exType, exValue, exTrace, testMode=False):
|
||||
logger.critical("Could not close the project before exiting")
|
||||
logger.critical(str(e))
|
||||
|
||||
if testMode:
|
||||
return errMsg.msgBody.toPlainText()
|
||||
else:
|
||||
qApp.exit(1)
|
||||
qApp.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
logger.critical(str(e))
|
||||
|
||||
+40
-9
@@ -32,8 +32,9 @@ import os
|
||||
from datetime import datetime
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QCursor
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QTabWidget,
|
||||
qApp, QDialog, QHBoxLayout, QVBoxLayout, QDialogButtonBox, QTabWidget,
|
||||
QTextBrowser, QLabel
|
||||
)
|
||||
|
||||
@@ -80,6 +81,10 @@ class GuiAbout(QDialog):
|
||||
self.pageAbout.setOpenExternalLinks(True)
|
||||
self.pageAbout.document().setDocumentMargin(self.mainConf.pxInt(16))
|
||||
|
||||
self.pageNotes = QTextBrowser()
|
||||
self.pageNotes.setOpenExternalLinks(True)
|
||||
self.pageNotes.document().setDocumentMargin(self.mainConf.pxInt(16))
|
||||
|
||||
self.pageLicense = QTextBrowser()
|
||||
self.pageLicense.setOpenExternalLinks(True)
|
||||
self.pageLicense.document().setDocumentMargin(self.mainConf.pxInt(16))
|
||||
@@ -87,6 +92,7 @@ class GuiAbout(QDialog):
|
||||
# Main Tab Area
|
||||
self.tabBox = QTabWidget()
|
||||
self.tabBox.addTab(self.pageAbout, "About")
|
||||
self.tabBox.addTab(self.pageNotes, "Release")
|
||||
self.tabBox.addTab(self.pageLicense, "License")
|
||||
self.innerBox.addWidget(self.tabBox)
|
||||
|
||||
@@ -98,14 +104,27 @@ class GuiAbout(QDialog):
|
||||
self.outerBox.addWidget(self.buttonBox)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
self._setStyleSheet()
|
||||
self._fillAboutPage()
|
||||
self._fillLicensePage()
|
||||
|
||||
logger.debug("GuiAbout initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def populateGUI(self):
|
||||
"""Populate tabs with text.
|
||||
"""
|
||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
self._setStyleSheet()
|
||||
self._fillAboutPage()
|
||||
self._fillNotesPage()
|
||||
self._fillLicensePage()
|
||||
qApp.restoreOverrideCursor()
|
||||
return
|
||||
|
||||
def showReleaseNotes(self):
|
||||
"""Show the release notes.
|
||||
"""
|
||||
self.tabBox.setCurrentWidget(self.pageNotes)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
@@ -121,14 +140,14 @@ class GuiAbout(QDialog):
|
||||
"<p>novelWriter 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>novelWriter is free software: you can redistribute it and/or "
|
||||
"<p>novelWriter is free software: you can redistribute it and "
|
||||
"modify it under the terms of the GNU General Public License as "
|
||||
"published by the Free Software Foundation, either version 3 of "
|
||||
"the License, or (at your option) any later version.</p>"
|
||||
"<p>novelWriter is distributed in the hope that it will be useful, "
|
||||
"but WITHOUT ANY WARRANTY; without even the implied warranty of "
|
||||
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p>"
|
||||
"<p>See the License tab for the full text, or visit the GNU website "
|
||||
"<p>See the License tab for the full license text, or visit the GNU website "
|
||||
"at <a href='https://www.gnu.org/licenses/gpl-3.0.html'>GPL v3.0</a> "
|
||||
"for more details.</p>"
|
||||
"<h3>Credits</h3>"
|
||||
@@ -192,11 +211,22 @@ class GuiAbout(QDialog):
|
||||
|
||||
return
|
||||
|
||||
def _fillNotesPage(self):
|
||||
"""Load the content for the Release Notes page.
|
||||
"""
|
||||
docPath = os.path.join(self.mainConf.assetPath, "text", "release_notes.htm")
|
||||
if os.path.isfile(docPath):
|
||||
with open(docPath, mode="r", encoding="utf8") as inFile:
|
||||
helpText = inFile.read()
|
||||
self.pageNotes.setHtml(helpText)
|
||||
else:
|
||||
self.pageNotes.setHtml("Error loading release notes text ...")
|
||||
return
|
||||
|
||||
def _fillLicensePage(self):
|
||||
"""Load the content for the License page.
|
||||
"""
|
||||
docName = "gplv3_%s.htm" % self.mainConf.guiLang
|
||||
docPath = os.path.join(self.mainConf.assetPath, "text", docName)
|
||||
docPath = os.path.join(self.mainConf.assetPath, "text", "gplv3_en.htm")
|
||||
if os.path.isfile(docPath):
|
||||
with open(docPath, mode="r", encoding="utf8") as inFile:
|
||||
helpText = inFile.read()
|
||||
@@ -221,6 +251,7 @@ class GuiAbout(QDialog):
|
||||
hColB = self.theParent.theTheme.colHead[2],
|
||||
)
|
||||
self.pageAbout.document().setDefaultStyleSheet(styleSheet)
|
||||
self.pageNotes.document().setDefaultStyleSheet(styleSheet)
|
||||
self.pageLicense.document().setDefaultStyleSheet(styleSheet)
|
||||
|
||||
return
|
||||
|
||||
+4
-3
@@ -262,10 +262,10 @@ class GuiDocEditor(QTextEdit):
|
||||
# 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:
|
||||
self.redrawText()
|
||||
else:
|
||||
if self.theHandle is None:
|
||||
self.clearEditor()
|
||||
else:
|
||||
self.redrawText()
|
||||
|
||||
return True
|
||||
|
||||
@@ -355,6 +355,7 @@ class GuiDocEditor(QTextEdit):
|
||||
"""Redraw the text by marking the document content as "dirty".
|
||||
"""
|
||||
self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
def replaceText(self, theText):
|
||||
|
||||
+3
-2
@@ -149,7 +149,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
# If we have a document open, we should reload it in case the font changed
|
||||
if self.theHandle is not None:
|
||||
self.redrawText()
|
||||
self.reloadText()
|
||||
|
||||
return True
|
||||
|
||||
@@ -169,7 +169,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
sPos = self.verticalScrollBar().value()
|
||||
aDoc = ToHtml(self.theProject, self.theParent)
|
||||
aDoc.setPreview(True, self.mainConf.viewComments, self.mainConf.viewSynopsis)
|
||||
aDoc.setPreview(self.mainConf.viewComments, self.mainConf.viewSynopsis)
|
||||
aDoc.setLinkHeaders(True)
|
||||
|
||||
# Be extra careful here to prevent crashes when first opening a
|
||||
@@ -233,6 +233,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
"""Redraw the text by marking the document content as "dirty".
|
||||
"""
|
||||
self.qDocument.markContentsDirty(0, self.qDocument.characterCount())
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
def loadFromTag(self, theTag):
|
||||
|
||||
+26
-11
@@ -598,8 +598,8 @@ class GuiMainMenu(QMenuBar):
|
||||
# Insert > Separator
|
||||
self.insertMenu.addSeparator()
|
||||
|
||||
# Insert > Keywords and Tags
|
||||
self.mInsKeywords = self.insertMenu.addMenu("Keywords and Tags")
|
||||
# Insert > Tags and References
|
||||
self.mInsKeywords = self.insertMenu.addMenu("Tags and References")
|
||||
self.mInsKWItems = {}
|
||||
self.mInsKWItems[nwKeyWords.TAG_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, G")
|
||||
self.mInsKWItems[nwKeyWords.POV_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, V")
|
||||
@@ -908,7 +908,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.helpMenu.addAction(self.aHelpLoc)
|
||||
|
||||
self.aHelpWeb = QAction("Documentation (Online)", self)
|
||||
self.aHelpWeb.setStatusTip("View online documentation")
|
||||
self.aHelpWeb.setStatusTip("View online documentation at %s" % nw.__docurl__)
|
||||
self.aHelpWeb.triggered.connect(lambda: self._openWebsite(nw.__docurl__))
|
||||
if self.mainConf.hasHelp and self.mainConf.hasAssistant:
|
||||
self.aHelpWeb.setShortcut("Shift+F1")
|
||||
@@ -916,18 +916,33 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aHelpWeb.setShortcuts(["F1", "Shift+F1"])
|
||||
self.helpMenu.addAction(self.aHelpWeb)
|
||||
|
||||
# Document > Go to Website
|
||||
self.aWebsite = QAction("Open the novelWriter Website", self)
|
||||
self.aWebsite.setStatusTip("View the main website")
|
||||
self.aWebsite.triggered.connect(lambda: self._openWebsite(nw.__url__))
|
||||
self.helpMenu.addAction(self.aWebsite)
|
||||
# Help > Separator
|
||||
self.helpMenu.addSeparator()
|
||||
|
||||
# Document > Report Issue
|
||||
self.aIssue = QAction("Report an Issue", self)
|
||||
self.aIssue.setStatusTip("Report a bug or issue on GitHub")
|
||||
# Document > Report an Issue
|
||||
self.aIssue = QAction("Report an Issue (GitHub)", self)
|
||||
self.aIssue.setStatusTip("Report a bug or issue on GitHub at %s" % nw.__issuesurl__)
|
||||
self.aIssue.triggered.connect(lambda: self._openWebsite(nw.__issuesurl__))
|
||||
self.helpMenu.addAction(self.aIssue)
|
||||
|
||||
# Document > Ask a Question
|
||||
self.aQuestion = QAction("Ask a Question (GitHub)", self)
|
||||
self.aQuestion.setStatusTip("Ask a question on GitHub at %s" % nw.__helpurl__)
|
||||
self.aQuestion.triggered.connect(lambda: self._openWebsite(nw.__helpurl__))
|
||||
self.helpMenu.addAction(self.aQuestion)
|
||||
|
||||
# Document > Latest Release
|
||||
self.aRelease = QAction("Latest Release (GitHub)", self)
|
||||
self.aRelease.setStatusTip("Open the Releases page on GitHub at %s" % nw.__releaseurl__)
|
||||
self.aRelease.triggered.connect(lambda: self._openWebsite(nw.__releaseurl__))
|
||||
self.helpMenu.addAction(self.aRelease)
|
||||
|
||||
# Document > Main Website
|
||||
self.aWebsite = QAction("The novelWriter Website", self)
|
||||
self.aWebsite.setStatusTip("Open the novelWriter website at %s" % nw.__url__)
|
||||
self.aWebsite.triggered.connect(lambda: self._openWebsite(nw.__url__))
|
||||
self.helpMenu.addAction(self.aWebsite)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiMainMenu
|
||||
|
||||
+47
-35
@@ -227,7 +227,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Show full path in document header",
|
||||
self.showFullPath,
|
||||
"Shows the document title and parent folder names."
|
||||
"Add the parent folder names to the header."
|
||||
)
|
||||
|
||||
self.hideVScroll = QSwitch()
|
||||
@@ -235,7 +235,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Hide vertical scroll bars in main windows",
|
||||
self.hideVScroll,
|
||||
"Scrolling with mouse wheel and keys only."
|
||||
"Scrolling available with mouse wheel and keys only."
|
||||
)
|
||||
|
||||
self.hideHScroll = QSwitch()
|
||||
@@ -243,7 +243,7 @@ class GuiConfigEditGeneralTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Hide horizontal scroll bars in main windows",
|
||||
self.hideHScroll,
|
||||
"Scrolling with mouse wheel and keys only."
|
||||
"Scrolling available with mouse wheel and keys only."
|
||||
)
|
||||
|
||||
return
|
||||
@@ -325,8 +325,9 @@ class GuiConfigEditProjectsTab(QWidget):
|
||||
self.autoSaveDoc.setSingleStep(1)
|
||||
self.autoSaveDoc.setValue(self.mainConf.autoSaveDoc)
|
||||
self.backupPathRow = self.mainForm.addRow(
|
||||
"Save interval for the currently open document",
|
||||
"Save document interval",
|
||||
self.autoSaveDoc,
|
||||
"How often the open document is automatically saved.",
|
||||
theUnit="seconds"
|
||||
)
|
||||
|
||||
@@ -337,8 +338,9 @@ class GuiConfigEditProjectsTab(QWidget):
|
||||
self.autoSaveProj.setSingleStep(1)
|
||||
self.autoSaveProj.setValue(self.mainConf.autoSaveProj)
|
||||
self.backupPathRow = self.mainForm.addRow(
|
||||
"Save interval for the currently open project",
|
||||
"Save project interval",
|
||||
self.autoSaveProj,
|
||||
"How often the open project is automatically saved.",
|
||||
theUnit="seconds"
|
||||
)
|
||||
|
||||
@@ -361,9 +363,9 @@ class GuiConfigEditProjectsTab(QWidget):
|
||||
self.backupOnClose.setChecked(self.mainConf.backupOnClose)
|
||||
self.backupOnClose.toggled.connect(self._toggledBackupOnClose)
|
||||
self.mainForm.addRow(
|
||||
"Run backup when closing project",
|
||||
"Run backup when the project is closed",
|
||||
self.backupOnClose,
|
||||
"This option can be overridden in project settings."
|
||||
"Can be overridden for individual projects in project settings."
|
||||
)
|
||||
|
||||
## Ask before backup
|
||||
@@ -373,7 +375,8 @@ class GuiConfigEditProjectsTab(QWidget):
|
||||
self.askBeforeBackup.setEnabled(self.mainConf.backupOnClose)
|
||||
self.mainForm.addRow(
|
||||
"Ask before running backup",
|
||||
self.askBeforeBackup
|
||||
self.askBeforeBackup,
|
||||
"Disabling this will cause backups to run in the background."
|
||||
)
|
||||
|
||||
return
|
||||
@@ -426,7 +429,7 @@ class GuiConfigEditProjectsTab(QWidget):
|
||||
|
||||
def _toggledBackupOnClose(self, theState):
|
||||
"""Enable or disable switch that depends on the backup on close
|
||||
switch,
|
||||
switch.
|
||||
"""
|
||||
self.askBeforeBackup.setEnabled(theState)
|
||||
return
|
||||
@@ -449,7 +452,7 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
|
||||
# Text Style
|
||||
# ==========
|
||||
self.mainForm.addGroupLabel("Text Style")
|
||||
self.mainForm.addGroupLabel("Document Text Style")
|
||||
|
||||
## Font Family
|
||||
self.textStyleFont = QLineEdit()
|
||||
@@ -475,12 +478,13 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Font size",
|
||||
self.textStyleSize,
|
||||
"Font size for the document editor and viewer.",
|
||||
theUnit = "pt"
|
||||
)
|
||||
|
||||
# Text Flow
|
||||
# =========
|
||||
self.mainForm.addGroupLabel("Text Flow")
|
||||
self.mainForm.addGroupLabel("Document Text Flow")
|
||||
|
||||
## Max Text Width in Normal Mode
|
||||
self.textFlowMax = QSpinBox(self)
|
||||
@@ -491,6 +495,7 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Maximum text width in \"Normal Mode\"",
|
||||
self.textFlowMax,
|
||||
"Horizontal margins are scaled automatically.",
|
||||
theUnit="px"
|
||||
)
|
||||
|
||||
@@ -503,6 +508,7 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Maximum text width in \"Focus Mode\"",
|
||||
self.focusDocWidth,
|
||||
"Horizontal margins are scaled automatically.",
|
||||
theUnit="px"
|
||||
)
|
||||
|
||||
@@ -512,7 +518,7 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Disable maximum text width in \"Normal Mode\"",
|
||||
self.textFlowFixed,
|
||||
"If disabled, minimum text width is defined by the margin setting."
|
||||
"If disabled, minimum text width is defined by the margin."
|
||||
)
|
||||
|
||||
## Focus Mode Footer
|
||||
@@ -520,7 +526,8 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.hideFocusFooter.setChecked(self.mainConf.hideFocusFooter)
|
||||
self.mainForm.addRow(
|
||||
"Hide document footer in \"Focus Mode\"",
|
||||
self.hideFocusFooter
|
||||
self.hideFocusFooter,
|
||||
"Hide the information bar at the bottom of the document."
|
||||
)
|
||||
|
||||
## Justify Text
|
||||
@@ -528,7 +535,8 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.textJustify.setChecked(self.mainConf.textFixedW)
|
||||
self.mainForm.addRow(
|
||||
"Justify the text margins in editor and viewer",
|
||||
self.textJustify
|
||||
self.textJustify,
|
||||
"Lay out text with straight edges in the editor and viewer."
|
||||
)
|
||||
|
||||
## Document Margins
|
||||
@@ -538,9 +546,9 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.textMargin.setSingleStep(1)
|
||||
self.textMargin.setValue(self.mainConf.textMargin)
|
||||
self.mainForm.addRow(
|
||||
"Document text margin",
|
||||
"Text margin",
|
||||
self.textMargin,
|
||||
"If max width is enabled, this is the minimum margin.",
|
||||
"If maximum width is set, this becomes the minimum margin.",
|
||||
theUnit="px"
|
||||
)
|
||||
|
||||
@@ -551,8 +559,9 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.tabWidth.setSingleStep(1)
|
||||
self.tabWidth.setValue(self.mainConf.tabWidth)
|
||||
self.mainForm.addRow(
|
||||
"Document tab width",
|
||||
"Tab width",
|
||||
self.tabWidth,
|
||||
"The width of a tab key press in the editor and viewer.",
|
||||
theUnit="px"
|
||||
)
|
||||
|
||||
@@ -566,7 +575,7 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Scroll past end of the document",
|
||||
self.scrollPastEnd,
|
||||
"Allows scrolling until last line is at the top."
|
||||
"Allow scrolling until the last line is centred in the editor."
|
||||
)
|
||||
|
||||
## Typewriter Scrolling
|
||||
@@ -575,7 +584,7 @@ class GuiConfigEditLayoutTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Typewriter style scrolling when you type",
|
||||
self.autoScroll,
|
||||
"Tries to keep the cursor at a fixed vertical position."
|
||||
"Try to keep the cursor at a fixed vertical position."
|
||||
)
|
||||
|
||||
## Font Size
|
||||
@@ -678,7 +687,7 @@ class GuiConfigEditEditingTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Highlight theme",
|
||||
self.selectSyntax,
|
||||
""
|
||||
"Colour theme to apply to the editor and viewer."
|
||||
)
|
||||
|
||||
self.highlightQuotes = QSwitch()
|
||||
@@ -686,7 +695,7 @@ class GuiConfigEditEditingTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Highlight text wrapped in quotes",
|
||||
self.highlightQuotes,
|
||||
helpText="Applies to single, double and straight quotes."
|
||||
"Applies to single, double and straight quotes."
|
||||
)
|
||||
|
||||
self.highlightEmph = QSwitch()
|
||||
@@ -694,7 +703,7 @@ class GuiConfigEditEditingTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Add highlight colour to emphasised text",
|
||||
self.highlightEmph,
|
||||
helpText="Applies to emphasis, strong and strikethrough."
|
||||
"Applies to emphasis, strong and strikethrough."
|
||||
)
|
||||
|
||||
# Spell Checking
|
||||
@@ -724,7 +733,8 @@ class GuiConfigEditEditingTab(QWidget):
|
||||
)
|
||||
self.mainForm.addRow(
|
||||
"Spell check language",
|
||||
self.spellLangList
|
||||
self.spellLangList,
|
||||
"Available languages are determined by your system."
|
||||
)
|
||||
|
||||
## Big Document Size Limit
|
||||
@@ -736,7 +746,7 @@ class GuiConfigEditEditingTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Big document limit",
|
||||
self.bigDocLimit,
|
||||
"Disables full spell checking over the size limit.",
|
||||
"Full spell checking is disabled above this limit.",
|
||||
theUnit="kB"
|
||||
)
|
||||
|
||||
@@ -749,7 +759,8 @@ class GuiConfigEditEditingTab(QWidget):
|
||||
self.showTabsNSpaces.setChecked(self.mainConf.showTabsNSpaces)
|
||||
self.mainForm.addRow(
|
||||
"Show tabs and spaces",
|
||||
self.showTabsNSpaces
|
||||
self.showTabsNSpaces,
|
||||
"Add symbols to indicate tabs and spaces in the editor."
|
||||
)
|
||||
|
||||
## Show Line Endings
|
||||
@@ -757,7 +768,8 @@ class GuiConfigEditEditingTab(QWidget):
|
||||
self.showLineEndings.setChecked(self.mainConf.showLineEndings)
|
||||
self.mainForm.addRow(
|
||||
"Show line endings",
|
||||
self.showLineEndings
|
||||
self.showLineEndings,
|
||||
"Add a symbol to indicate line endings in the editor."
|
||||
)
|
||||
|
||||
return
|
||||
@@ -866,7 +878,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Auto-replace text as you type",
|
||||
self.autoReplaceMain,
|
||||
"Apply formatting to word under cursor if no selection is made."
|
||||
"Allow the editor to replace symbols as you type."
|
||||
)
|
||||
|
||||
# Auto-Replace
|
||||
@@ -880,7 +892,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Auto-replace single quotes",
|
||||
self.autoReplaceSQ,
|
||||
"The feature will try to guess opening or closing single quote."
|
||||
"Try to guess which is an opening or a closing single quote."
|
||||
)
|
||||
|
||||
## Auto-Replace Double Quotes
|
||||
@@ -890,7 +902,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Auto-replace double quotes",
|
||||
self.autoReplaceDQ,
|
||||
"The feature will try to guess opening or closing quote quote."
|
||||
"Try to guess which is an opening or a closing double quote."
|
||||
)
|
||||
|
||||
## Auto-Replace Hyphens
|
||||
@@ -900,7 +912,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Auto-replace dashes",
|
||||
self.autoReplaceDash,
|
||||
"Auto-replace double and triple hyphens with short and long dash."
|
||||
"Double and triple hyphens become short and long dashes."
|
||||
)
|
||||
|
||||
## Auto-Replace Dots
|
||||
@@ -910,7 +922,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Auto-replace dots",
|
||||
self.autoReplaceDots,
|
||||
"Auto-replace three dots with ellipsis."
|
||||
"Three consecutive dots becomes ellipsis."
|
||||
)
|
||||
|
||||
# Quotation Style
|
||||
@@ -934,7 +946,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Single quote open style",
|
||||
self.quoteSym["SO"],
|
||||
"Auto-replaces apostrophe before words.",
|
||||
"The symbol to use for a leading single quote.",
|
||||
theButton=self.btnSingleStyleO
|
||||
)
|
||||
|
||||
@@ -950,7 +962,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Single quote close style",
|
||||
self.quoteSym["SC"],
|
||||
"Auto-replaces apostrophe after words.",
|
||||
"The symbol to use for a trailing single quote.",
|
||||
theButton=self.btnSingleStyleC
|
||||
)
|
||||
|
||||
@@ -967,7 +979,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Double quote open style",
|
||||
self.quoteSym["DO"],
|
||||
"Auto-replaces straight quotes before words.",
|
||||
"The symbol to use for a leading double quote.",
|
||||
theButton=self.btnDoubleStyleO
|
||||
)
|
||||
|
||||
@@ -983,7 +995,7 @@ class GuiConfigEditAutoReplaceTab(QWidget):
|
||||
self.mainForm.addRow(
|
||||
"Double quote close style",
|
||||
self.quoteSym["DC"],
|
||||
"Auto-replaces straight quotes after words.",
|
||||
"The symbol to use for a trailing double quote.",
|
||||
theButton=self.btnDoubleStyleC
|
||||
)
|
||||
|
||||
|
||||
+47
-43
@@ -286,36 +286,36 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
hasFocus = qApp.focusWidget() == self or not self.mainConf.showGUI
|
||||
if hasFocus and self.theParent.hasProject:
|
||||
if qApp.focusWidget() != self and self.mainConf.showGUI:
|
||||
return False
|
||||
|
||||
tHandle = self.getSelectedHandle()
|
||||
tItem = self._getTreeItem(tHandle)
|
||||
pItem = tItem.parent()
|
||||
if pItem is None:
|
||||
tIndex = self.indexOfTopLevelItem(tItem)
|
||||
nChild = self.topLevelItemCount()
|
||||
nIndex = tIndex + nStep
|
||||
if nIndex < 0 or nIndex >= nChild:
|
||||
return False
|
||||
cItem = self.takeTopLevelItem(tIndex)
|
||||
self.insertTopLevelItem(nIndex, cItem)
|
||||
tHandle = self.getSelectedHandle()
|
||||
tItem = self._getTreeItem(tHandle)
|
||||
if tItem is None:
|
||||
return False
|
||||
|
||||
else:
|
||||
tIndex = pItem.indexOfChild(tItem)
|
||||
nChild = pItem.childCount()
|
||||
nIndex = tIndex + nStep
|
||||
if nIndex < 0 or nIndex >= nChild:
|
||||
return False
|
||||
cItem = pItem.takeChild(tIndex)
|
||||
pItem.insertChild(nIndex, cItem)
|
||||
|
||||
self.clearSelection()
|
||||
cItem.setSelected(True)
|
||||
self._setTreeChanged(True)
|
||||
pItem = tItem.parent()
|
||||
if pItem is None:
|
||||
tIndex = self.indexOfTopLevelItem(tItem)
|
||||
nChild = self.topLevelItemCount()
|
||||
nIndex = tIndex + nStep
|
||||
if nIndex < 0 or nIndex >= nChild:
|
||||
return False
|
||||
cItem = self.takeTopLevelItem(tIndex)
|
||||
self.insertTopLevelItem(nIndex, cItem)
|
||||
|
||||
else:
|
||||
return False
|
||||
tIndex = pItem.indexOfChild(tItem)
|
||||
nChild = pItem.childCount()
|
||||
nIndex = tIndex + nStep
|
||||
if nIndex < 0 or nIndex >= nChild:
|
||||
return False
|
||||
cItem = pItem.takeChild(tIndex)
|
||||
pItem.insertChild(nIndex, cItem)
|
||||
|
||||
self.clearSelection()
|
||||
cItem.setSelected(True)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return True
|
||||
|
||||
@@ -431,7 +431,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.theProject.projTree[tHandle]
|
||||
|
||||
if nwItemS is None:
|
||||
if trItemS is None or nwItemS is None:
|
||||
return False
|
||||
|
||||
wCount = int(trItemS.data(self.C_COUNT, Qt.UserRole))
|
||||
@@ -582,18 +582,23 @@ class GuiProjectTree(QTreeWidget):
|
||||
properly reported to the function.
|
||||
"""
|
||||
tItem = self._getTreeItem(tHandle)
|
||||
if tItem is not None:
|
||||
tItem.setText(self.C_COUNT, f"{theCount:n}")
|
||||
tItem.setData(self.C_COUNT, Qt.UserRole, int(theCount))
|
||||
pItem = tItem.parent()
|
||||
if pItem is not None:
|
||||
pCount = 0
|
||||
for i in range(pItem.childCount()):
|
||||
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
||||
if tItem is None:
|
||||
return
|
||||
|
||||
if not nDepth > nwConst.MAX_DEPTH + 1 and pHandle != "":
|
||||
self.propagateCount(pHandle, pCount, nDepth+1)
|
||||
tItem.setText(self.C_COUNT, f"{theCount:n}")
|
||||
tItem.setData(self.C_COUNT, Qt.UserRole, int(theCount))
|
||||
|
||||
pItem = tItem.parent()
|
||||
if pItem is None:
|
||||
return
|
||||
|
||||
pCount = 0
|
||||
for i in range(pItem.childCount()):
|
||||
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
||||
|
||||
if not nDepth > nwConst.MAX_DEPTH + 1 and pHandle != "":
|
||||
self.propagateCount(pHandle, pCount, nDepth+1)
|
||||
|
||||
return
|
||||
|
||||
@@ -1024,12 +1029,12 @@ class GuiProjectTreeMenu(QMenu):
|
||||
return
|
||||
|
||||
def filterActions(self, theItem):
|
||||
"""Update item settings from the nwItem.
|
||||
"""Filter the menu entries available based on the properties of
|
||||
the item the menu was activated on.
|
||||
"""
|
||||
self.theItem = theItem
|
||||
theRoot = self.theTree.theProject.projTree.getRootItem(theItem.itemHandle)
|
||||
|
||||
if theItem is None or theRoot is None:
|
||||
if theItem is None:
|
||||
logger.error("Failed to extract information to build tree context menu")
|
||||
return False
|
||||
|
||||
@@ -1038,14 +1043,13 @@ class GuiProjectTreeMenu(QMenu):
|
||||
inTrash = theItem.itemParent == trashHandle and trashHandle is not None
|
||||
isTrash = theItem.itemHandle == trashHandle and trashHandle is not None
|
||||
isFile = theItem.itemType == nwItemType.FILE
|
||||
isArch = theRoot.itemClass == nwItemClass.ARCHIVE
|
||||
isOrph = isFile and theItem.itemParent is None
|
||||
|
||||
showOpen = isFile
|
||||
showView = isFile
|
||||
showEdit = not isTrash and not isOrph
|
||||
showExport = isFile
|
||||
showNewFile = not (isTrash or inTrash or isOrph or isArch)
|
||||
showNewFile = not (isTrash or inTrash or isOrph)
|
||||
showNewFolder = not (isTrash or inTrash or isOrph)
|
||||
showDelete = not isTrash
|
||||
showEmpty = isTrash
|
||||
|
||||
+7
-7
@@ -281,11 +281,11 @@ class GuiTheme:
|
||||
cnfSec = "Main"
|
||||
if confParser.has_section(cnfSec):
|
||||
self.themeName = self._parseLine(confParser, cnfSec, "name", "")
|
||||
self.themeDescription = self._parseLine(confParser, cnfSec, "description", "")
|
||||
self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "")
|
||||
self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "")
|
||||
self.themeDescription = self._parseLine(confParser, cnfSec, "description", "N/A")
|
||||
self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A")
|
||||
self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A")
|
||||
self.themeUrl = self._parseLine(confParser, cnfSec, "url", "")
|
||||
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "")
|
||||
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A")
|
||||
self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
|
||||
|
||||
## Palette
|
||||
@@ -638,10 +638,10 @@ class GuiIcons:
|
||||
if confParser.has_section(cnfSec):
|
||||
self.themeName = self._parseLine(confParser, cnfSec, "name", "")
|
||||
self.themeDescription = self._parseLine(confParser, cnfSec, "description", "")
|
||||
self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "")
|
||||
self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "")
|
||||
self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "N/A")
|
||||
self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "N/A")
|
||||
self.themeUrl = self._parseLine(confParser, cnfSec, "url", "")
|
||||
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "")
|
||||
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "N/A")
|
||||
self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
|
||||
|
||||
## Palette
|
||||
|
||||
+26
-6
@@ -81,14 +81,14 @@ class GuiMain(QMainWindow):
|
||||
# Core Classes
|
||||
# ============
|
||||
|
||||
# Core Classes and settings
|
||||
# Core Classes and Settings
|
||||
self.theTheme = GuiTheme(self)
|
||||
self.theProject = NWProject(self)
|
||||
self.theIndex = NWIndex(self.theProject, self)
|
||||
self.hasProject = False
|
||||
self.isFocusMode = False
|
||||
|
||||
# Prepare main window
|
||||
# Prepare Main Window
|
||||
self.resize(*self.mainConf.getWinSize())
|
||||
self._setWindowTitle()
|
||||
self.setWindowIcon(QIcon(self.mainConf.appIcon))
|
||||
@@ -238,6 +238,12 @@ class GuiMain(QMainWindow):
|
||||
if self.mainConf.showGUI:
|
||||
self.showProjectLoadDialog()
|
||||
|
||||
# Show the latest release notes, if they haven't been shown before
|
||||
if self.mainConf.lastNotes != nw.__version__:
|
||||
if self.mainConf.showGUI:
|
||||
self.showAboutNWDialog(showNotes=True)
|
||||
self.mainConf.lastNotes = nw.__version__
|
||||
|
||||
logger.debug("novelWriter is ready ...")
|
||||
self.setStatus("novelWriter is ready ...")
|
||||
|
||||
@@ -915,11 +921,18 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return
|
||||
|
||||
def showAboutNWDialog(self):
|
||||
def showAboutNWDialog(self, showNotes=False):
|
||||
"""Show the about dialog for novelWriter.
|
||||
"""
|
||||
dlgAbout = GuiAbout(self)
|
||||
dlgAbout.exec_()
|
||||
dlgAbout.setModal(True)
|
||||
dlgAbout.show()
|
||||
qApp.processEvents()
|
||||
dlgAbout.populateGUI()
|
||||
|
||||
if showNotes:
|
||||
dlgAbout.showReleaseNotes()
|
||||
|
||||
return
|
||||
|
||||
def showAboutQtDialog(self):
|
||||
@@ -969,6 +982,13 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return
|
||||
|
||||
def askQuestion(self, theTitle, theQuestion):
|
||||
"""Ask the user a Yes/No question.
|
||||
"""
|
||||
msgBox = QMessageBox()
|
||||
msgRes = msgBox.question(self, theTitle, theQuestion)
|
||||
return msgRes == QMessageBox.Yes
|
||||
|
||||
def reportConfErr(self):
|
||||
"""Checks if the Config module has any errors to report, and let
|
||||
the user know if this is the case. The Config module caches
|
||||
@@ -1060,10 +1080,10 @@ class GuiMain(QMainWindow):
|
||||
|
||||
self.isFocusMode = not self.isFocusMode
|
||||
if self.isFocusMode:
|
||||
logger.debug("Activating Focus mode")
|
||||
logger.debug("Activating Focus Mode")
|
||||
self.tabWidget.setCurrentWidget(self.splitDocs)
|
||||
else:
|
||||
logger.debug("Deactivating Focus mode")
|
||||
logger.debug("Deactivating Focus Mode")
|
||||
|
||||
isVisible = not self.isFocusMode
|
||||
self.treePane.setVisible(isVisible)
|
||||
|
||||
Reference in New Issue
Block a user