Improve alert boxes (#1501)
@@ -21,6 +21,7 @@ General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import getopt
|
import getopt
|
||||||
@@ -71,13 +72,12 @@ logger = logging.getLogger(__name__)
|
|||||||
# Main Program
|
# Main Program
|
||||||
##
|
##
|
||||||
|
|
||||||
# Load the main config as a global object
|
# Global config singleton
|
||||||
CONFIG = Config()
|
CONFIG = Config()
|
||||||
|
|
||||||
|
|
||||||
def main(sysArgs=None):
|
def main(sysArgs: list | None = None):
|
||||||
"""Parse command line, set up logging, and launch main GUI.
|
"""Parse command line, set up logging, and launch main GUI."""
|
||||||
"""
|
|
||||||
if sysArgs is None:
|
if sysArgs is None:
|
||||||
sysArgs = sys.argv[1:]
|
sysArgs = sys.argv[1:]
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
|||||||
|
|
||||||
[Map]
|
[Map]
|
||||||
add = typ_plus.svg
|
add = typ_plus.svg
|
||||||
|
alert_error = typ_delete-full.svg
|
||||||
|
alert_info = typ_lightbulb-full.svg
|
||||||
|
alert_question = typ_directions-full.svg
|
||||||
|
alert_warn = typ_warning-full.svg
|
||||||
backward = typ_chevron-left.svg
|
backward = typ_chevron-left.svg
|
||||||
bookmark = typ_bookmark.svg
|
bookmark = typ_bookmark.svg
|
||||||
browse = typ_folder-open.svg
|
browse = typ_folder-open.svg
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m12 0c-6.6285 0-12 5.373-12 12s5.3715 12 12 12 12-5.373 12-12-5.3715-12-12-12zm5.5605 15.44c0.5865 0.5865 0.5865 1.5345 0 2.121-0.2925 0.2925-0.6765 0.4395-1.0605 0.4395s-0.768-0.147-1.0605-0.4395l-3.4395-3.4395-3.4395 3.4395c-0.2925 0.2925-0.6765 0.4395-1.0605 0.4395s-0.768-0.147-1.0605-0.4395c-0.5865-0.5865-0.5865-1.5345 0-2.121l3.4395-3.4395-3.4395-3.4395c-0.5865-0.5865-0.5865-1.5345 0-2.121s1.5345-0.5865 2.121 0l3.4395 3.4395 3.4395-3.4395c0.5865-0.5865 1.5345-0.5865 2.121 0s0.5865 1.5345 0 2.121l-3.4395 3.4395z" fill="#d64848" stroke-width="1.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 713 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m24 7.3333-3.6909-3.476c-0.76122-0.72133-1.9392-1.1907-2.998-1.1907h-3.9093v-0.66667c0-1.104-0.9006-2-2.0103-2-1.1097 0-2.0103 0.896-2.0103 2v0.66667h-4.6906c-2.5866 0-4.6906 2.0933-4.6906 4.6667 0 1.8613 1.1097 3.4613 2.7018 4.2093l-2.4713 2.4573 3.4322 3.4147c0.7639 0.76 1.9567 1.252 3.0395 1.252h2.9484l1.0721 5.3333h1.3402l1.0721-5.3333h3.6185c2.5879 0 4.6906-2.0933 4.6906-4.6667 0-1.2027-0.47308-2.2907-1.2263-3.1187l0.09649-0.07467zm-7.2477 8.6667h-10.051c-0.36855 0-0.88184-0.21067-1.1445-0.472l-1.5358-1.528 1.5358-1.528c0.26134-0.26 0.77329-0.472 1.1445-0.472h10.051c1.1097 0 2.0103 0.896 2.0103 2s-0.9006 2-2.0103 2zm1.7141-7.1253c-0.2667 0.25333-0.78535 0.45867-1.1552 0.45867h-12.621c-1.1097 0-2.0103-0.896-2.0103-2s0.9006-2 2.0103-2h12.621c0.36989 0 0.88854 0.20533 1.1552 0.45867l1.6364 1.5413z" fill="#69c" stroke-width="1.3368"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1002 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m12 5.1429c-0.31543 0-0.57143 0.256-0.57143 0.57143s0.256 0.57143 0.57143 0.57143c1.2377 0 2.2446 1.0069 2.2446 2.2446 0 0.31543 0.256 0.57143 0.57143 0.57143s0.57143-0.256 0.57143-0.57143c0-1.8674-1.52-3.3874-3.3874-3.3874zm0-5.1429c-4.7269 0-8.5714 3.8446-8.5714 8.5714 0 1.6983 0.50286 3.3394 1.456 4.76l0.091429 0.15428c2.0857 2.9783 2.4526 3.92 2.4526 4.8v3.4286c0 0.63086 0.512 1.1429 1.1429 1.1429h2.2857c0 0.29714 0.12572 0.59429 0.33143 0.81143 0.21714 0.20572 0.51429 0.33143 0.81143 0.33143s0.59428-0.12571 0.81143-0.33143c0.20571-0.21714 0.33143-0.51428 0.33143-0.81143h2.2857c0.63086 0 1.1429-0.512 1.1429-1.1429v-3.4286c0-0.89371 0.36457-1.84 2.4366-4.7989 1.0229-1.4571 1.5634-3.1566 1.5634-4.9154 0-4.7269-3.8446-8.5714-8.5714-8.5714zm2.2857 20.571h-4.5714v-1.1429h4.5714zm2.8514-8.3966c-1.6754 2.392-2.4491 3.7589-2.7257 4.968h-1.2686v-2.2857c0-0.63086-0.512-1.1429-1.1429-1.1429-0.63086 0-1.1429 0.512-1.1429 1.1429v2.2857h-1.272c-0.27428-1.1771-1.0263-2.5143-2.6354-4.8229l-0.088-0.14743c-0.75086-1.0674-1.1474-2.3131-1.1474-3.6011 0-3.4663 2.8194-6.2857 6.2857-6.2857s6.2857 2.8194 6.2857 6.2857c0 1.2869-0.39657 2.5326-1.1486 3.6034z" fill="#69c" stroke-width="1.1429"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m23.197 15.837-7.2179-12.031c-0.94741-1.5786-2.3966-2.4821-3.9789-2.4821s-3.0315 0.90347-3.9789 2.4796l-7.2179 12.033c-0.95962 1.5981-1.0646 3.3025-0.28691 4.6761 0.77648 1.3747 2.2928 2.1634 4.1583 2.1634h14.651c1.8655 0 3.3819-0.7887 4.1583-2.1622 0.7777-1.3735 0.67271-3.0778-0.28691-4.6772zm-11.197 2.6262c-1.0426 0-1.8924-0.84852-1.8924-1.8912 0-1.0439 0.84852-1.8936 1.8924-1.8936 1.0439 0 1.8924 0.84975 1.8924 1.8936 0 1.0426-0.84974 1.8912-1.8924 1.8912zm1.9937-9.064c-0.01343 0.037847-1.7105 4.2341-1.7105 4.2341-0.04639 0.11477-0.15872 0.19046-0.28203 0.19046s-0.23563-0.0757-0.28202-0.19046l-1.6983-4.1974c-0.10988-0.28447-0.15749-0.54085-0.15749-0.79969 0-1.1782 0.9584-2.1366 2.1366-2.1366s2.1366 0.95836 2.1366 2.1366c0 0.25883-0.04762 0.51522-0.14284 0.76306z" fill="#f99157" stroke-width="1.2209"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 970 B |
@@ -17,6 +17,10 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
|||||||
|
|
||||||
[Map]
|
[Map]
|
||||||
add = typ_plus.svg
|
add = typ_plus.svg
|
||||||
|
alert_error = typ_delete-full.svg
|
||||||
|
alert_info = typ_lightbulb-full.svg
|
||||||
|
alert_question = typ_directions-full.svg
|
||||||
|
alert_warn = typ_warning-full.svg
|
||||||
backward = typ_chevron-left.svg
|
backward = typ_chevron-left.svg
|
||||||
bookmark = typ_bookmark.svg
|
bookmark = typ_bookmark.svg
|
||||||
browse = typ_folder-open.svg
|
browse = typ_folder-open.svg
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m12 0c-6.6285 0-12 5.373-12 12s5.3715 12 12 12 12-5.373 12-12-5.3715-12-12-12zm5.5605 15.44c0.5865 0.5865 0.5865 1.5345 0 2.121-0.2925 0.2925-0.6765 0.4395-1.0605 0.4395s-0.768-0.147-1.0605-0.4395l-3.4395-3.4395-3.4395 3.4395c-0.2925 0.2925-0.6765 0.4395-1.0605 0.4395s-0.768-0.147-1.0605-0.4395c-0.5865-0.5865-0.5865-1.5345 0-2.121l3.4395-3.4395-3.4395-3.4395c-0.5865-0.5865-0.5865-1.5345 0-2.121s1.5345-0.5865 2.121 0l3.4395 3.4395 3.4395-3.4395c0.5865-0.5865 1.5345-0.5865 2.121 0s0.5865 1.5345 0 2.121l-3.4395 3.4395z" fill="#c82829" stroke-width="1.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 713 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m24 7.3333-3.6909-3.476c-0.76122-0.72133-1.9392-1.1907-2.998-1.1907h-3.9093v-0.66667c0-1.104-0.9006-2-2.0103-2-1.1097 0-2.0103 0.896-2.0103 2v0.66667h-4.6906c-2.5866 0-4.6906 2.0933-4.6906 4.6667 0 1.8613 1.1097 3.4613 2.7018 4.2093l-2.4713 2.4573 3.4322 3.4147c0.7639 0.76 1.9567 1.252 3.0395 1.252h2.9484l1.0721 5.3333h1.3402l1.0721-5.3333h3.6185c2.5879 0 4.6906-2.0933 4.6906-4.6667 0-1.2027-0.47308-2.2907-1.2263-3.1187l0.09649-0.07467zm-7.2477 8.6667h-10.051c-0.36855 0-0.88184-0.21067-1.1445-0.472l-1.5358-1.528 1.5358-1.528c0.26134-0.26 0.77329-0.472 1.1445-0.472h10.051c1.1097 0 2.0103 0.896 2.0103 2s-0.9006 2-2.0103 2zm1.7141-7.1253c-0.2667 0.25333-0.78535 0.45867-1.1552 0.45867h-12.621c-1.1097 0-2.0103-0.896-2.0103-2s0.9006-2 2.0103-2h12.621c0.36989 0 0.88854 0.20533 1.1552 0.45867l1.6364 1.5413z" fill="#4271ae" stroke-width="1.3368"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1005 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m12 5.1429c-0.31543 0-0.57143 0.256-0.57143 0.57143s0.256 0.57143 0.57143 0.57143c1.2377 0 2.2446 1.0069 2.2446 2.2446 0 0.31543 0.256 0.57143 0.57143 0.57143s0.57143-0.256 0.57143-0.57143c0-1.8674-1.52-3.3874-3.3874-3.3874zm0-5.1429c-4.7269 0-8.5714 3.8446-8.5714 8.5714 0 1.6983 0.50286 3.3394 1.456 4.76l0.091429 0.15428c2.0857 2.9783 2.4526 3.92 2.4526 4.8v3.4286c0 0.63086 0.512 1.1429 1.1429 1.1429h2.2857c0 0.29714 0.12572 0.59429 0.33143 0.81143 0.21714 0.20572 0.51429 0.33143 0.81143 0.33143s0.59428-0.12571 0.81143-0.33143c0.20571-0.21714 0.33143-0.51428 0.33143-0.81143h2.2857c0.63086 0 1.1429-0.512 1.1429-1.1429v-3.4286c0-0.89371 0.36457-1.84 2.4366-4.7989 1.0229-1.4571 1.5634-3.1566 1.5634-4.9154 0-4.7269-3.8446-8.5714-8.5714-8.5714zm2.2857 20.571h-4.5714v-1.1429h4.5714zm2.8514-8.3966c-1.6754 2.392-2.4491 3.7589-2.7257 4.968h-1.2686v-2.2857c0-0.63086-0.512-1.1429-1.1429-1.1429-0.63086 0-1.1429 0.512-1.1429 1.1429v2.2857h-1.272c-0.27428-1.1771-1.0263-2.5143-2.6354-4.8229l-0.088-0.14743c-0.75086-1.0674-1.1474-2.3131-1.1474-3.6011 0-3.4663 2.8194-6.2857 6.2857-6.2857s6.2857 2.8194 6.2857 6.2857c0 1.2869-0.39657 2.5326-1.1486 3.6034z" fill="#4271ae" stroke-width="1.1429"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m23.197 15.837-7.2179-12.031c-0.94741-1.5786-2.3966-2.4821-3.9789-2.4821s-3.0315 0.90347-3.9789 2.4796l-7.2179 12.033c-0.95962 1.5981-1.0646 3.3025-0.28691 4.6761 0.77648 1.3747 2.2928 2.1634 4.1583 2.1634h14.651c1.8655 0 3.3819-0.7887 4.1583-2.1622 0.7777-1.3735 0.67271-3.0778-0.28691-4.6772zm-11.197 2.6262c-1.0426 0-1.8924-0.84852-1.8924-1.8912 0-1.0439 0.84852-1.8936 1.8924-1.8936 1.0439 0 1.8924 0.84975 1.8924 1.8936 0 1.0426-0.84974 1.8912-1.8924 1.8912zm1.9937-9.064c-0.01343 0.037847-1.7105 4.2341-1.7105 4.2341-0.04639 0.11477-0.15872 0.19046-0.28203 0.19046s-0.23563-0.0757-0.28202-0.19046l-1.6983-4.1974c-0.10988-0.28447-0.15749-0.54085-0.15749-0.79969 0-1.1782 0.9584-2.1366 2.1366-2.1366s2.1366 0.95836 2.1366 2.1366c0 0.25883-0.04762 0.51522-0.14284 0.76306z" fill="#f5871f" stroke-width="1.2209"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 970 B |
@@ -1,10 +1,9 @@
|
|||||||
"""
|
"""
|
||||||
novelWriter – Config Class
|
novelWriter – Config Class
|
||||||
==========================
|
==========================
|
||||||
Class holding the user preferences and handling the config file
|
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2018-09-22 [0.0.1]
|
Created: 2018-09-22 [0.0.1] Config
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2018–2023, Veronica Berglyd Olsen
|
Copyright 2018–2023, Veronica Berglyd Olsen
|
||||||
@@ -22,7 +21,6 @@ General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
novelWriter – Constants
|
novelWriter – Constants
|
||||||
=======================
|
=======================
|
||||||
Constants and maps for translating flags and enums to text
|
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2019-04-28 [0.0.1]
|
Created: 2019-04-28 [0.0.1]
|
||||||
@@ -26,7 +25,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
||||||
|
|
||||||
from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
|
from novelwriter.enum import nwAlert, nwBuildFmt, nwItemClass, nwItemLayout, nwOutline
|
||||||
|
|
||||||
|
|
||||||
def trConst(text: str) -> str:
|
def trConst(text: str) -> str:
|
||||||
@@ -163,6 +162,12 @@ class nwLabels:
|
|||||||
nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"),
|
nwItemLayout.DOCUMENT: QT_TRANSLATE_NOOP("Constant", "Novel Document"),
|
||||||
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"),
|
nwItemLayout.NOTE: QT_TRANSLATE_NOOP("Constant", "Project Note"),
|
||||||
}
|
}
|
||||||
|
ALERT_NAME = {
|
||||||
|
nwAlert.INFO: QT_TRANSLATE_NOOP("Constant", "Information"),
|
||||||
|
nwAlert.WARN: QT_TRANSLATE_NOOP("Constant", "Warning"),
|
||||||
|
nwAlert.ERROR: QT_TRANSLATE_NOOP("Constant", "Error"),
|
||||||
|
nwAlert.ASK: QT_TRANSLATE_NOOP("Constant", "Question"),
|
||||||
|
}
|
||||||
ITEM_DESCRIPTION = {
|
ITEM_DESCRIPTION = {
|
||||||
"none": QT_TRANSLATE_NOOP("Constant", "None"),
|
"none": QT_TRANSLATE_NOOP("Constant", "None"),
|
||||||
"root": QT_TRANSLATE_NOOP("Constant", "Root Folder"),
|
"root": QT_TRANSLATE_NOOP("Constant", "Root Folder"),
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ class ProjectBuilder:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Failed to create a new example project."
|
"Failed to create a new example project."
|
||||||
), nwAlert.ERROR, exception=exc)
|
), level=nwAlert.ERROR, exception=exc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -487,7 +487,7 @@ class ProjectBuilder:
|
|||||||
"Failed to create a new example project. "
|
"Failed to create a new example project. "
|
||||||
"Could not find the necessary files. "
|
"Could not find the necessary files. "
|
||||||
"They seem to be missing from this installation."
|
"They seem to be missing from this installation."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -173,9 +173,10 @@ class NWProject(QObject):
|
|||||||
if self._tree.checkType(tHandle, nwItemType.FILE):
|
if self._tree.checkType(tHandle, nwItemType.FILE):
|
||||||
delDoc = self._storage.getDocument(tHandle)
|
delDoc = self._storage.getDocument(tHandle)
|
||||||
if not delDoc.deleteDocument():
|
if not delDoc.deleteDocument():
|
||||||
self.mainGui.makeAlert([
|
self.mainGui.makeAlert(
|
||||||
self.tr("Could not delete document file."), delDoc.getError()
|
self.tr("Could not delete document file."),
|
||||||
], nwAlert.ERROR)
|
info=delDoc.getError(), level=nwAlert.ERROR
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._index.deleteHandle(tHandle)
|
self._index.deleteHandle(tHandle)
|
||||||
@@ -227,7 +228,7 @@ class NWProject(QObject):
|
|||||||
if not self._storage.openProjectInPlace(projPath):
|
if not self._storage.openProjectInPlace(projPath):
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Could not open project with path: {0}"
|
"Could not open project with path: {0}"
|
||||||
).format(projPath), nwAlert.ERROR)
|
).format(projPath), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Project Lock
|
# Project Lock
|
||||||
@@ -266,17 +267,17 @@ class NWProject(QObject):
|
|||||||
if xmlReader.state == XMLReadState.NOT_NWX_FILE:
|
if xmlReader.state == XMLReadState.NOT_NWX_FILE:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Project file does not appear to be a novelWriterXML file."
|
"Project file does not appear to be a novelWriterXML file."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
elif xmlReader.state == XMLReadState.UNKNOWN_VERSION:
|
elif xmlReader.state == XMLReadState.UNKNOWN_VERSION:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Unknown or unsupported novelWriter project file format. "
|
"Unknown or unsupported novelWriter project file format. "
|
||||||
"The project cannot be opened by this version of novelWriter. "
|
"The project cannot be opened by this version of novelWriter. "
|
||||||
"The file was saved with novelWriter version {0}."
|
"The file was saved with novelWriter version {0}."
|
||||||
).format(appVersion), nwAlert.ERROR)
|
).format(appVersion), level=nwAlert.ERROR)
|
||||||
else:
|
else:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Failed to parse project xml."
|
"Failed to parse project xml."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
|
|
||||||
self.clearProject()
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
@@ -285,14 +286,11 @@ class NWProject(QObject):
|
|||||||
# ====================
|
# ====================
|
||||||
|
|
||||||
if xmlReader.state == XMLReadState.WAS_LEGACY:
|
if xmlReader.state == XMLReadState.WAS_LEGACY:
|
||||||
msgYes = self.mainGui.askQuestion(
|
msgYes = self.mainGui.askQuestion(self.tr(
|
||||||
self.tr("File Version"),
|
"The file format of your project is about to be updated. "
|
||||||
self.tr(
|
"If you proceed, older versions of novelWriter will no "
|
||||||
"The file format of your project is about to be updated. "
|
"longer be able to open this project. Continue?"
|
||||||
"If you proceed, older versions of novelWriter will no "
|
))
|
||||||
"longer be able to open this project. Continue?"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
self.clearProject()
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
@@ -301,16 +299,13 @@ class NWProject(QObject):
|
|||||||
# =========================
|
# =========================
|
||||||
|
|
||||||
if xmlReader.hexVersion > hexToInt(__hexversion__):
|
if xmlReader.hexVersion > hexToInt(__hexversion__):
|
||||||
msgYes = self.mainGui.askQuestion(
|
msgYes = self.mainGui.askQuestion(self.tr(
|
||||||
self.tr("Version Conflict"),
|
"This project was saved by a newer version of "
|
||||||
self.tr(
|
"novelWriter, version {0}. This is version {1}. If you "
|
||||||
"This project was saved by a newer version of "
|
"continue to open the project, some attributes and "
|
||||||
"novelWriter, version {0}. This is version {1}. If you "
|
"settings may not be preserved, but the overall project "
|
||||||
"continue to open the project, some attributes and "
|
"should be fine. Continue opening the project?"
|
||||||
"settings may not be preserved, but the overall project "
|
).format(appVersion, __version__))
|
||||||
"should be fine. Continue opening the project?"
|
|
||||||
).format(appVersion, __version__)
|
|
||||||
)
|
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
self.clearProject()
|
self.clearProject()
|
||||||
return False
|
return False
|
||||||
@@ -333,7 +328,7 @@ class NWProject(QObject):
|
|||||||
if orphans > 0:
|
if orphans > 0:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Found {0} orphaned file(s) in the project. {1} file(s) were recovered."
|
"Found {0} orphaned file(s) in the project. {1} file(s) were recovered."
|
||||||
).format(orphans, recovered), nwAlert.WARN)
|
).format(orphans, recovered), level=nwAlert.WARN)
|
||||||
|
|
||||||
self._index.loadIndex()
|
self._index.loadIndex()
|
||||||
if xmlReader.state == XMLReadState.WAS_LEGACY:
|
if xmlReader.state == XMLReadState.WAS_LEGACY:
|
||||||
@@ -357,7 +352,7 @@ class NWProject(QObject):
|
|||||||
if not self._storage.isOpen():
|
if not self._storage.isOpen():
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"There is no project open."
|
"There is no project open."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
saveTime = time()
|
saveTime = time()
|
||||||
@@ -382,7 +377,7 @@ class NWProject(QObject):
|
|||||||
if not xmlWriter.write(self._data, content, saveTime, editTime):
|
if not xmlWriter.write(self._data, content, saveTime, editTime):
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Failed to save project."
|
"Failed to save project."
|
||||||
), nwAlert.ERROR, exception=xmlWriter.error)
|
), level=nwAlert.ERROR, exception=xmlWriter.error)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Save other project data
|
# Save other project data
|
||||||
@@ -426,14 +421,14 @@ class NWProject(QObject):
|
|||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Cannot backup project because no valid backup path is set. "
|
"Cannot backup project because no valid backup path is set. "
|
||||||
"Please set a valid backup location in Preferences."
|
"Please set a valid backup location in Preferences."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self._data.name:
|
if not self._data.name:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Cannot backup project because no project name is set. "
|
"Cannot backup project because no project name is set. "
|
||||||
"Please set a Project Name in Project Settings."
|
"Please set a Project Name in Project Settings."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
cleanName = makeFileNameSafe(self._data.name)
|
cleanName = makeFileNameSafe(self._data.name)
|
||||||
@@ -443,7 +438,7 @@ class NWProject(QObject):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Could not create backup folder."
|
"Could not create backup folder."
|
||||||
), nwAlert.ERROR, exception=exc)
|
), level=nwAlert.ERROR, exception=exc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
timeStamp = formatTimeStamp(time(), fileSafe=True)
|
timeStamp = formatTimeStamp(time(), fileSafe=True)
|
||||||
@@ -453,11 +448,11 @@ class NWProject(QObject):
|
|||||||
if doNotify:
|
if doNotify:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Backup archive file written to: {0} [{1}B]"
|
"Backup archive file written to: {0} [{1}B]"
|
||||||
).format(str(archName), formatInt(size)), nwAlert.INFO)
|
).format(str(archName), formatInt(size)))
|
||||||
else:
|
else:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Could not write backup archive."
|
"Could not write backup archive."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.mainGui.setStatus(self.tr(
|
self.mainGui.setStatus(self.tr(
|
||||||
|
|||||||
@@ -225,13 +225,10 @@ class GuiProjectLoad(QDialog):
|
|||||||
selList = self.listBox.selectedItems()
|
selList = self.listBox.selectedItems()
|
||||||
if selList:
|
if selList:
|
||||||
projName = selList[0].text(self.C_NAME)
|
projName = selList[0].text(self.C_NAME)
|
||||||
msgYes = self.mainGui.askQuestion(
|
msgYes = self.mainGui.askQuestion(self.tr(
|
||||||
self.tr("Remove Entry"),
|
"Remove '{0}' from the recent projects list? "
|
||||||
self.tr(
|
"The project files will not be deleted."
|
||||||
"Remove '{0}' from the recent projects list? "
|
).format(projName))
|
||||||
"The project files will not be deleted."
|
|
||||||
).format(projName)
|
|
||||||
)
|
|
||||||
if msgYes:
|
if msgYes:
|
||||||
CONFIG.recentProjects.remove(
|
CONFIG.recentProjects.remove(
|
||||||
selList[0].data(self.C_NAME, self.D_PATH)
|
selList[0].data(self.C_NAME, self.D_PATH)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
novelWriter – GUI Project Settings
|
novelWriter – GUI Project Settings
|
||||||
==================================
|
==================================
|
||||||
GUI classes for the project settings dialog
|
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2018-09-29 [0.0.1]
|
Created: 2018-09-29 [0.0.1]
|
||||||
@@ -22,9 +21,12 @@ General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
||||||
from PyQt5.QtCore import Qt, QLocale, pyqtSlot
|
from PyQt5.QtCore import Qt, QLocale, pyqtSlot
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
@@ -39,6 +41,9 @@ from novelwriter.extensions.switch import NSwitch
|
|||||||
from novelwriter.extensions.pageddialog import NPagedDialog
|
from novelwriter.extensions.pageddialog import NPagedDialog
|
||||||
from novelwriter.extensions.configlayout import NConfigLayout
|
from novelwriter.extensions.configlayout import NConfigLayout
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from novelwriter.guimain import GuiMain
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +54,7 @@ class GuiProjectSettings(NPagedDialog):
|
|||||||
TAB_IMPORT = 2
|
TAB_IMPORT = 2
|
||||||
TAB_REPLACE = 3
|
TAB_REPLACE = 3
|
||||||
|
|
||||||
def __init__(self, mainGui, focusTab=TAB_MAIN):
|
def __init__(self, mainGui: GuiMain, focusTab: int = TAB_MAIN) -> None:
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=mainGui)
|
||||||
|
|
||||||
logger.debug("Create: GuiProjectSettings")
|
logger.debug("Create: GuiProjectSettings")
|
||||||
@@ -441,7 +446,7 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
|
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Cannot delete a status item that is in use."
|
"Cannot delete a status item that is in use."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
else:
|
else:
|
||||||
self.listBox.takeTopLevelItem(iRow)
|
self.listBox.takeTopLevelItem(iRow)
|
||||||
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||||
|
|||||||
@@ -128,13 +128,13 @@ class GuiWordList(QDialog):
|
|||||||
if word == "":
|
if word == "":
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Cannot add a blank word."
|
"Cannot add a blank word."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.listBox.findItems(word, Qt.MatchExactly):
|
if self.listBox.findItems(word, Qt.MatchExactly):
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"The word '{0}' is already in the word list."
|
"The word '{0}' is already in the word list."
|
||||||
).format(word), nwAlert.ERROR)
|
).format(word), level=nwAlert.ERROR)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.listBox.addItem(word)
|
self.listBox.addItem(word)
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class nwAlert(Enum):
|
|||||||
INFO = 0
|
INFO = 0
|
||||||
WARN = 1
|
WARN = 1
|
||||||
ERROR = 2
|
ERROR = 2
|
||||||
BUG = 3
|
ASK = 3
|
||||||
|
|
||||||
# END Enum nwAlert
|
# END Enum nwAlert
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
novelWriter – Exception Handling
|
novelWriter – Exception Handling
|
||||||
================================
|
================================
|
||||||
Error handling function and error dialog
|
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2020-08-02 [0.10.2]
|
Created: 2020-08-02 [0.10.2]
|
||||||
@@ -22,18 +21,24 @@ General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import random
|
import random
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtGui import QFont, QFontDatabase
|
from PyQt5.QtGui import QFont, QFontDatabase
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt, pyqtSlot
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
|
QWidget, qApp, QDialog, QGridLayout, QStyle, QPlainTextEdit, QLabel,
|
||||||
QDialogButtonBox
|
QDialogButtonBox
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from types import TracebackType
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -41,15 +46,15 @@ logger = logging.getLogger(__name__)
|
|||||||
# Utility Functions
|
# Utility Functions
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
|
|
||||||
def logException():
|
def logException() -> None:
|
||||||
"""Log the content of an exception message.
|
"""Log the content of an exception message."""
|
||||||
"""
|
|
||||||
exType, exValue, _ = sys.exc_info()
|
exType, exValue, _ = sys.exc_info()
|
||||||
if exType is not None:
|
if exType is not None:
|
||||||
logger.error("%s: %s", exType.__name__, str(exValue))
|
logger.error("%s: %s", exType.__name__, str(exValue))
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def formatException(exc):
|
def formatException(exc) -> str:
|
||||||
"""Format an exception as a string the same way the default
|
"""Format an exception as a string the same way the default
|
||||||
exception handler does.
|
exception handler does.
|
||||||
"""
|
"""
|
||||||
@@ -62,7 +67,7 @@ def formatException(exc):
|
|||||||
|
|
||||||
class NWErrorMessage(QDialog):
|
class NWErrorMessage(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent: QWidget) -> None:
|
||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
self.setObjectName("NWErrorMessage")
|
self.setObjectName("NWErrorMessage")
|
||||||
|
|
||||||
@@ -113,7 +118,7 @@ class NWErrorMessage(QDialog):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def setMessage(self, exType, exValue, exTrace):
|
def setMessage(self, exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
|
||||||
"""Generate a message and append session data, error info and
|
"""Generate a message and append session data, error info and
|
||||||
error traceback.
|
error traceback.
|
||||||
"""
|
"""
|
||||||
@@ -142,7 +147,7 @@ class NWErrorMessage(QDialog):
|
|||||||
enchantVersion = "Unknown"
|
enchantVersion = "Unknown"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
exTrace = "\n".join(format_tb(exTrace))
|
txtTrace = "\n".join(format_tb(exTrace))
|
||||||
self.msgBody.setPlainText((
|
self.msgBody.setPlainText((
|
||||||
"Environment:\n"
|
"Environment:\n"
|
||||||
f"novelWriter Version: {__version__}\n"
|
f"novelWriter Version: {__version__}\n"
|
||||||
@@ -151,7 +156,7 @@ class NWErrorMessage(QDialog):
|
|||||||
f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n"
|
f"Qt: {QT_VERSION_STR}, PyQt: {PYQT_VERSION_STR}\n"
|
||||||
f"enchant: {enchantVersion}\n\n"
|
f"enchant: {enchantVersion}\n\n"
|
||||||
f"{exType.__name__}:\n{str(exValue)}\n\n"
|
f"{exType.__name__}:\n{str(exValue)}\n\n"
|
||||||
f"Traceback:\n{exTrace}\n"
|
f"Traceback:\n{txtTrace}\n"
|
||||||
))
|
))
|
||||||
except Exception:
|
except Exception:
|
||||||
self.msgBody.setPlainText("Failed to generate error report ...")
|
self.msgBody.setPlainText("Failed to generate error report ...")
|
||||||
@@ -162,18 +167,17 @@ class NWErrorMessage(QDialog):
|
|||||||
# Slots
|
# Slots
|
||||||
##
|
##
|
||||||
|
|
||||||
def _doClose(self):
|
@pyqtSlot()
|
||||||
"""Close the dialog.
|
def _doClose(self) -> None:
|
||||||
"""
|
"""Close the dialog."""
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class NWErrorMessage
|
# END Class NWErrorMessage
|
||||||
|
|
||||||
|
|
||||||
def exceptionHandler(exType, exValue, exTrace):
|
def exceptionHandler(exType: type, exValue: BaseException, exTrace: TracebackType) -> None:
|
||||||
"""Function to catch unhandled global exceptions.
|
"""Function to catch unhandled global exceptions."""
|
||||||
"""
|
|
||||||
from traceback import print_tb
|
from traceback import print_tb
|
||||||
from PyQt5.QtWidgets import qApp
|
from PyQt5.QtWidgets import qApp
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
novelWriter – GUI Document Editor
|
novelWriter – GUI Document Editor
|
||||||
=================================
|
=================================
|
||||||
GUI classes for the main document editor
|
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2018-09-29 [0.0.1] GuiDocEditor
|
Created: 2018-09-29 [0.0.1] GuiDocEditor
|
||||||
@@ -28,12 +27,14 @@ General Public License for more details.
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import bisect
|
import bisect
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from time import time
|
from time import time
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression,
|
Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression,
|
||||||
@@ -44,9 +45,8 @@ from PyQt5.QtGui import (
|
|||||||
QPalette, QTextDocument, QCursor, QPixmap
|
QPalette, QTextDocument, QCursor, QPixmap
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox, QWidget, QLabel,
|
QAction, qApp, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu,
|
||||||
QToolBar, QToolButton, QHBoxLayout, QGridLayout, QLineEdit, QPushButton,
|
QPushButton, QShortcut, QTextEdit, QToolBar, QToolButton, QWidget
|
||||||
QFrame
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG
|
from novelwriter import CONFIG
|
||||||
@@ -57,6 +57,9 @@ from novelwriter.core.index import countWords
|
|||||||
from novelwriter.core.spellcheck import NWSpellEnchant
|
from novelwriter.core.spellcheck import NWSpellEnchant
|
||||||
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from novelwriter.guimain import GuiMain
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +78,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
novelStructureChanged = pyqtSignal()
|
novelStructureChanged = pyqtSignal()
|
||||||
novelItemMetaChanged = pyqtSignal(str)
|
novelItemMetaChanged = pyqtSignal(str)
|
||||||
|
|
||||||
def __init__(self, mainGui):
|
def __init__(self, mainGui: GuiMain):
|
||||||
super().__init__(parent=mainGui)
|
super().__init__(parent=mainGui)
|
||||||
|
|
||||||
logger.debug("Create: GuiDocEditor")
|
logger.debug("Create: GuiDocEditor")
|
||||||
@@ -357,7 +360,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
).format(
|
).format(
|
||||||
f"{docSize/1.0e6:.2f}",
|
f"{docSize/1.0e6:.2f}",
|
||||||
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
self.clearEditor()
|
self.clearEditor()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -457,7 +460,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
).format(
|
).format(
|
||||||
f"{docSize/1.0e6:.2f}",
|
f"{docSize/1.0e6:.2f}",
|
||||||
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||||
@@ -496,20 +499,19 @@ class GuiDocEditor(QTextEdit):
|
|||||||
if not self._nwDocument.writeDocument(docText):
|
if not self._nwDocument.writeDocument(docText):
|
||||||
saveOk = False
|
saveOk = False
|
||||||
if self._nwDocument._currHash != self._nwDocument._prevHash:
|
if self._nwDocument._currHash != self._nwDocument._prevHash:
|
||||||
msgYes = self.mainGui.askQuestion(
|
msgYes = self.mainGui.askQuestion(self.tr(
|
||||||
self.tr("File Changed on Disk"),
|
"This document has been changed outside of novelWriter "
|
||||||
self.tr(
|
"while it was open. Overwrite the file on disk?"
|
||||||
"This document has been changed outside of novelWriter "
|
))
|
||||||
"while it was open. Overwrite the file on disk?"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if msgYes:
|
if msgYes:
|
||||||
saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
|
saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
|
||||||
|
|
||||||
if not saveOk:
|
if not saveOk:
|
||||||
self.mainGui.makeAlert([
|
self.mainGui.makeAlert(
|
||||||
self.tr("Could not save document."), self._nwDocument.getError()
|
self.tr("Could not save document."),
|
||||||
], nwAlert.ERROR)
|
info=self._nwDocument.getError(),
|
||||||
|
level=nwAlert.ERROR
|
||||||
|
)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -726,7 +728,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Spell checking requires the package PyEnchant. "
|
"Spell checking requires the package PyEnchant. "
|
||||||
"It does not appear to be installed."
|
"It does not appear to be installed."
|
||||||
), nwAlert.INFO)
|
))
|
||||||
theMode = False
|
theMode = False
|
||||||
|
|
||||||
if self.spEnchant.spellLanguage is None:
|
if self.spEnchant.spellLanguage is None:
|
||||||
@@ -868,17 +870,10 @@ class GuiDocEditor(QTextEdit):
|
|||||||
if self._nwDocument is None:
|
if self._nwDocument is None:
|
||||||
logger.error("No document open")
|
logger.error("No document open")
|
||||||
return False
|
return False
|
||||||
|
self.mainGui.makeAlert(
|
||||||
msgBox = QMessageBox()
|
self.tr("The currently open file is saved in:"),
|
||||||
msgBox.information(
|
info=self._nwDocument.getFileLocation()
|
||||||
self,
|
|
||||||
self.tr("File Location"),
|
|
||||||
"%s<br>%s" % (
|
|
||||||
self.tr("The currently open file is saved in:"),
|
|
||||||
self._nwDocument.getFileLocation()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def insertText(self, theInsert):
|
def insertText(self, theInsert):
|
||||||
@@ -1116,7 +1111,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"The maximum size of a single novelWriter document is {0} MB."
|
"The maximum size of a single novelWriter document is {0} MB."
|
||||||
).format(
|
).format(
|
||||||
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
f"{nwConst.MAX_DOCSIZE/1.0e6:.2f}"
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
self.undo()
|
self.undo()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1675,7 +1670,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
if not theCursor.hasSelection():
|
if not theCursor.hasSelection():
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Please select some text before calling replace quotes."
|
"Please select some text before calling replace quotes."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
posS = theCursor.selectionStart()
|
posS = theCursor.selectionStart()
|
||||||
|
|||||||
@@ -589,7 +589,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if sHandle is None or pItem is None:
|
if sHandle is None or pItem is None:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Did not find anywhere to add the file or folder!"
|
"Did not find anywhere to add the file or folder!"
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Collect some information about the selected item
|
# Collect some information about the selected item
|
||||||
@@ -600,7 +600,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if self.theProject.tree.isTrash(sHandle):
|
if self.theProject.tree.isTrash(sHandle):
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Cannot add new files or folders to the Trash folder."
|
"Cannot add new files or folders to the Trash folder."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Set default label and determine if new item is to be added
|
# Set default label and determine if new item is to be added
|
||||||
@@ -838,7 +838,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if trashHandle is None:
|
if trashHandle is None:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"There is currently no Trash folder in this project."
|
"There is currently no Trash folder in this project."
|
||||||
), nwAlert.INFO)
|
))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
theTrash = self.getTreeFromHandle(trashHandle)
|
theTrash = self.getTreeFromHandle(trashHandle)
|
||||||
@@ -849,11 +849,10 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if nTrash == 0:
|
if nTrash == 0:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"The Trash folder is already empty."
|
"The Trash folder is already empty."
|
||||||
), nwAlert.INFO)
|
))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
msgYes = self.mainGui.askQuestion(
|
msgYes = self.mainGui.askQuestion(
|
||||||
self.tr("Empty Trash"),
|
|
||||||
self.tr("Permanently delete {0} file(s) from Trash?").format(nTrash)
|
self.tr("Permanently delete {0} file(s) from Trash?").format(nTrash)
|
||||||
)
|
)
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
@@ -900,7 +899,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
if askFirst:
|
if askFirst:
|
||||||
msgYes = self.mainGui.askQuestion(
|
msgYes = self.mainGui.askQuestion(
|
||||||
self.tr("Delete"),
|
|
||||||
self.tr("Move '{0}' to Trash?").format(nwItemS.itemName),
|
self.tr("Move '{0}' to Trash?").format(nwItemS.itemName),
|
||||||
)
|
)
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
@@ -937,7 +935,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if trItemS.childCount() > 0:
|
if trItemS.childCount() > 0:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"Root folders can only be deleted when they are empty."
|
"Root folders can only be deleted when they are empty."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.debug("Permanently deleting root folder '%s'", tHandle)
|
logger.debug("Permanently deleting root folder '%s'", tHandle)
|
||||||
@@ -956,7 +954,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
else:
|
else:
|
||||||
if askFirst:
|
if askFirst:
|
||||||
msgYes = self.mainGui.askQuestion(
|
msgYes = self.mainGui.askQuestion(
|
||||||
self.tr("Delete"),
|
|
||||||
self.tr("Permanently delete '{0}'?").format(nwItemS.itemName)
|
self.tr("Permanently delete '{0}'?").format(nwItemS.itemName)
|
||||||
)
|
)
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
@@ -1525,13 +1522,10 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""Convert a folder to a note or document."""
|
"""Convert a folder to a note or document."""
|
||||||
tItem = self.theProject.tree[tHandle]
|
tItem = self.theProject.tree[tHandle]
|
||||||
if tItem is not None and tItem.isFolderType():
|
if tItem is not None and tItem.isFolderType():
|
||||||
msgYes = self.mainGui.askQuestion(
|
msgYes = self.mainGui.askQuestion(self.tr(
|
||||||
self.tr("Convert Folder"),
|
"Do you want to convert the folder to a {0}? "
|
||||||
self.tr(
|
"This action cannot be reversed."
|
||||||
"Do you want to convert the folder to a {0}? "
|
).format(trConst(nwLabels.LAYOUT_NAME[itemLayout])))
|
||||||
"This action cannot be reversed."
|
|
||||||
).format(trConst(nwLabels.LAYOUT_NAME[itemLayout]))
|
|
||||||
)
|
|
||||||
if msgYes and itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
|
if msgYes and itemLayout == nwItemLayout.DOCUMENT and tItem.documentAllowed():
|
||||||
tItem.setType(nwItemType.FILE)
|
tItem.setType(nwItemType.FILE)
|
||||||
tItem.setLayout(nwItemLayout.DOCUMENT)
|
tItem.setLayout(nwItemLayout.DOCUMENT)
|
||||||
@@ -1570,9 +1564,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
mrgData = dlgMerge.getData()
|
mrgData = dlgMerge.getData()
|
||||||
mrgList = mrgData.get("finalItems", [])
|
mrgList = mrgData.get("finalItems", [])
|
||||||
if not mrgList:
|
if not mrgList:
|
||||||
self.mainGui.makeAlert([
|
self.mainGui.makeAlert(self.tr("No documents selected for merging."))
|
||||||
self.tr("No documents selected for merging.")
|
|
||||||
], nwAlert.INFO)
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Save the open document first, in case it's part of merge
|
# Save the open document first, in case it's part of merge
|
||||||
@@ -1595,9 +1587,10 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
docMerger.appendText(sHandle, True, mLabel)
|
docMerger.appendText(sHandle, True, mLabel)
|
||||||
|
|
||||||
if not docMerger.writeTargetDoc():
|
if not docMerger.writeTargetDoc():
|
||||||
self.mainGui.makeAlert([
|
self.mainGui.makeAlert(
|
||||||
self.tr("Could not write document content."), docMerger.getError()
|
self.tr("Could not write document content."),
|
||||||
], nwAlert.ERROR)
|
info=docMerger.getError(), level=nwAlert.ERROR
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.theProject.index.reIndexHandle(mHandle)
|
self.theProject.index.reIndexHandle(mHandle)
|
||||||
@@ -1658,9 +1651,10 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
|
self.revealNewTreeItem(dHandle, nHandle=nHandle, wordCount=True)
|
||||||
self._alertTreeChange(dHandle, flush=False)
|
self._alertTreeChange(dHandle, flush=False)
|
||||||
if not writeOk:
|
if not writeOk:
|
||||||
self.mainGui.makeAlert([
|
self.mainGui.makeAlert(
|
||||||
self.tr("Could not write document content."), docSplit.getError()
|
self.tr("Could not write document content."),
|
||||||
], nwAlert.ERROR)
|
info=docSplit.getError(), level=nwAlert.ERROR
|
||||||
|
)
|
||||||
|
|
||||||
if splitData.get("moveToTrash", False):
|
if splitData.get("moveToTrash", False):
|
||||||
self.moveItemToTrash(tHandle, askFirst=False, flush=True)
|
self.moveItemToTrash(tHandle, askFirst=False, flush=True)
|
||||||
@@ -1680,13 +1674,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if nItems == 0:
|
if nItems == 0:
|
||||||
return False
|
return False
|
||||||
elif nItems == 1:
|
elif nItems == 1:
|
||||||
qTitle = self.tr("Duplicate Document")
|
question = self.tr("Do you want to duplicate this document?")
|
||||||
qText = self.tr("Do you want to duplicate this document?")
|
|
||||||
else:
|
else:
|
||||||
qTitle = self.tr("Duplicate from Here")
|
question = self.tr("Do you want to duplicate this item and all child items?")
|
||||||
qText = self.tr("Do you want to duplicate this item and all child items?")
|
|
||||||
|
|
||||||
if not self.mainGui.askQuestion(qTitle, qText):
|
if not self.mainGui.askQuestion(question):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
docDup = DocDuplicator(self.theProject)
|
docDup = DocDuplicator(self.theProject)
|
||||||
@@ -1698,7 +1690,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
dupCount += 1
|
dupCount += 1
|
||||||
|
|
||||||
if dupCount != nItems:
|
if dupCount != nItems:
|
||||||
self.mainGui.makeAlert(self.tr("Could not duplicate all items."), nwAlert.WARN)
|
self.mainGui.makeAlert(self.tr("Could not duplicate all items."), level=nwAlert.WARN)
|
||||||
|
|
||||||
self.saveTreeOrder()
|
self.saveTreeOrder()
|
||||||
|
|
||||||
@@ -1757,7 +1749,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
else:
|
else:
|
||||||
self.mainGui.makeAlert(self.tr(
|
self.mainGui.makeAlert(self.tr(
|
||||||
"There is nowhere to add item with name '{0}'."
|
"There is nowhere to add item with name '{0}'."
|
||||||
).format(nwItem.itemName), nwAlert.ERROR)
|
).format(nwItem.itemName), level=nwAlert.ERROR)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
byIndex = -1
|
byIndex = -1
|
||||||
|
|||||||
@@ -445,14 +445,15 @@ class GuiIcons:
|
|||||||
|
|
||||||
ICON_KEYS = {
|
ICON_KEYS = {
|
||||||
# Project and GUI Icons
|
# Project and GUI Icons
|
||||||
"novelwriter", "build_excluded", "build_filtered", "build_included", "cls_archive",
|
"novelwriter", "alert_error", "alert_info", "alert_question", "alert_warn",
|
||||||
"cls_character", "cls_custom", "cls_entity", "cls_none", "cls_novel", "cls_object",
|
"build_excluded", "build_filtered", "build_included", "cls_archive", "cls_character",
|
||||||
"cls_plot", "cls_timeline", "cls_trash", "cls_world", "proj_chapter", "proj_details",
|
"cls_custom", "cls_entity", "cls_none", "cls_novel", "cls_object", "cls_plot",
|
||||||
"proj_document", "proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene",
|
"cls_timeline", "cls_trash", "cls_world", "proj_chapter", "proj_details", "proj_document",
|
||||||
"proj_stats", "proj_title", "search_cancel", "search_case", "search_loop",
|
"proj_folder", "proj_note", "proj_nwx", "proj_section", "proj_scene", "proj_stats",
|
||||||
"search_preserve", "search_project", "search_regex", "search_word", "status_idle",
|
"proj_title", "search_cancel", "search_case", "search_loop", "search_preserve",
|
||||||
"status_lang", "status_lines", "status_stats", "status_time", "view_build", "view_editor",
|
"search_project", "search_regex", "search_word", "status_idle", "status_lang",
|
||||||
"view_novel", "view_outline",
|
"status_lines", "status_stats", "status_time", "view_build", "view_editor", "view_novel",
|
||||||
|
"view_outline",
|
||||||
|
|
||||||
# General Button Icons
|
# General Button Icons
|
||||||
"add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit",
|
"add", "backward", "bookmark", "browse", "checked", "close", "cross", "down", "edit",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from pathlib import Path
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
|
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
|
||||||
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence
|
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence, QPixmap
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QDialog, QFileDialog, QMainWindow, QMessageBox, QShortcut, QSplitter,
|
qApp, QDialog, QFileDialog, QMainWindow, QMessageBox, QShortcut, QSplitter,
|
||||||
QStackedWidget, QVBoxLayout, QWidget
|
QStackedWidget, QVBoxLayout, QWidget
|
||||||
@@ -66,7 +66,7 @@ from novelwriter.enum import (
|
|||||||
nwDocAction, nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwView
|
nwDocAction, nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwView
|
||||||
)
|
)
|
||||||
from novelwriter.common import getGuiItem, hexToInt
|
from novelwriter.common import getGuiItem, hexToInt
|
||||||
from novelwriter.constants import nwFiles
|
from novelwriter.constants import nwFiles, nwLabels, trConst
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -112,9 +112,11 @@ class GuiMain(QMainWindow):
|
|||||||
# Core Classes
|
# Core Classes
|
||||||
# ============
|
# ============
|
||||||
|
|
||||||
# Core Classes and Settings
|
# Core Classes
|
||||||
self.mainTheme = GuiTheme()
|
self.mainTheme = GuiTheme()
|
||||||
self.theProject = NWProject(self)
|
self.theProject = NWProject(self)
|
||||||
|
|
||||||
|
# Core Settings
|
||||||
self.hasProject = False
|
self.hasProject = False
|
||||||
self.isFocusMode = False
|
self.isFocusMode = False
|
||||||
self.idleRefTime = time()
|
self.idleRefTime = time()
|
||||||
@@ -133,6 +135,7 @@ class GuiMain(QMainWindow):
|
|||||||
# =============
|
# =============
|
||||||
|
|
||||||
# Sizes
|
# Sizes
|
||||||
|
iPx = self.mainTheme.fontPixelSize
|
||||||
mPx = CONFIG.pxInt(4)
|
mPx = CONFIG.pxInt(4)
|
||||||
hWd = CONFIG.pxInt(4)
|
hWd = CONFIG.pxInt(4)
|
||||||
|
|
||||||
@@ -301,6 +304,15 @@ class GuiMain(QMainWindow):
|
|||||||
# Forward Functions
|
# Forward Functions
|
||||||
self.setStatus = self.mainStatus.setStatus
|
self.setStatus = self.mainStatus.setStatus
|
||||||
|
|
||||||
|
# Cache Alert Pixmaps
|
||||||
|
pxSize = (2*iPx, 2*iPx)
|
||||||
|
self.alertPix: dict[nwAlert, QPixmap] = {
|
||||||
|
nwAlert.INFO: self.mainTheme.getPixmap("alert_info", pxSize),
|
||||||
|
nwAlert.WARN: self.mainTheme.getPixmap("alert_warn", pxSize),
|
||||||
|
nwAlert.ERROR: self.mainTheme.getPixmap("alert_error", pxSize),
|
||||||
|
nwAlert.ASK: self.mainTheme.getPixmap("alert_question", pxSize),
|
||||||
|
}
|
||||||
|
|
||||||
# Check that config loaded fine
|
# Check that config loaded fine
|
||||||
self.reportConfErr()
|
self.reportConfErr()
|
||||||
|
|
||||||
@@ -320,7 +332,7 @@ class GuiMain(QMainWindow):
|
|||||||
"You are running an untested development version of novelWriter. "
|
"You are running an untested development version of novelWriter. "
|
||||||
"Please be careful when working on a live project "
|
"Please be careful when working on a live project "
|
||||||
"and make sure you take regular backups."
|
"and make sure you take regular backups."
|
||||||
), nwAlert.WARN)
|
), level=nwAlert.WARN)
|
||||||
|
|
||||||
logger.info("novelWriter is ready ...")
|
logger.info("novelWriter is ready ...")
|
||||||
self.setStatus(self.tr("novelWriter is ready ..."))
|
self.setStatus(self.tr("novelWriter is ready ..."))
|
||||||
@@ -380,7 +392,7 @@ class GuiMain(QMainWindow):
|
|||||||
if not self.closeProject():
|
if not self.closeProject():
|
||||||
self.makeAlert(self.tr(
|
self.makeAlert(self.tr(
|
||||||
"Cannot create a new project when another project is open."
|
"Cannot create a new project when another project is open."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if projData is None:
|
if projData is None:
|
||||||
@@ -398,7 +410,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.makeAlert(self.tr(
|
self.makeAlert(self.tr(
|
||||||
"A project already exists in that location. "
|
"A project already exists in that location. "
|
||||||
"Please choose another folder."
|
"Please choose another folder."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info("Creating new project")
|
logger.info("Creating new project")
|
||||||
@@ -420,13 +432,10 @@ class GuiMain(QMainWindow):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if not isYes:
|
if not isYes:
|
||||||
msgYes = self.askQuestion(
|
msgYes = self.askQuestion("%s<br>%s" % (
|
||||||
self.tr("Close Project"),
|
self.tr("Close the current project?"),
|
||||||
"%s<br>%s" % (
|
self.tr("Changes are saved automatically.")
|
||||||
self.tr("Close the current project?"),
|
))
|
||||||
self.tr("Changes are saved automatically.")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -438,10 +447,7 @@ class GuiMain(QMainWindow):
|
|||||||
if self.theProject.data.doBackup and CONFIG.backupOnClose:
|
if self.theProject.data.doBackup and CONFIG.backupOnClose:
|
||||||
doBackup = True
|
doBackup = True
|
||||||
if CONFIG.askBeforeBackup:
|
if CONFIG.askBeforeBackup:
|
||||||
msgYes = self.askQuestion(
|
msgYes = self.askQuestion(self.tr("Backup the current project?"))
|
||||||
self.tr("Backup Project"),
|
|
||||||
self.tr("Backup the current project?")
|
|
||||||
)
|
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
doBackup = False
|
doBackup = False
|
||||||
|
|
||||||
@@ -480,19 +486,28 @@ class GuiMain(QMainWindow):
|
|||||||
# Try to open the project
|
# Try to open the project
|
||||||
if not self.theProject.openProject(projFile):
|
if not self.theProject.openProject(projFile):
|
||||||
# The project open failed.
|
# The project open failed.
|
||||||
|
|
||||||
lockStatus = self.theProject.getLockStatus()
|
lockStatus = self.theProject.getLockStatus()
|
||||||
if lockStatus is None:
|
if lockStatus is None:
|
||||||
# The project is not locked, so failed for some other
|
# The project is not locked, so failed for some other
|
||||||
# reason handled by the project class.
|
# reason handled by the project class.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
lockText = self.tr(
|
||||||
|
"The project is already open by another instance of "
|
||||||
|
"novelWriter, and is therefore locked. Override lock "
|
||||||
|
"and continue anyway?"
|
||||||
|
)
|
||||||
|
lockInfo = self.tr(
|
||||||
|
"Note: If the program or the computer previously "
|
||||||
|
"crashed, the lock can safely be overridden. However, "
|
||||||
|
"overriding it is not recommended if the project is "
|
||||||
|
"open in another instance of novelWriter. Doing so "
|
||||||
|
"may corrupt the project."
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
lockDetails = (
|
lockDetails = self.tr(
|
||||||
"<br>%s" % self.tr(
|
"The project was locked by the computer "
|
||||||
"The project was locked by the computer "
|
"'{0}' ({1} {2}), last active on {3}."
|
||||||
"'{0}' ({1} {2}), last active on {3}."
|
|
||||||
)
|
|
||||||
).format(
|
).format(
|
||||||
lockStatus[0], lockStatus[1], lockStatus[2],
|
lockStatus[0], lockStatus[1], lockStatus[2],
|
||||||
datetime.fromtimestamp(int(lockStatus[3])).strftime("%x %X")
|
datetime.fromtimestamp(int(lockStatus[3])).strftime("%x %X")
|
||||||
@@ -500,27 +515,7 @@ class GuiMain(QMainWindow):
|
|||||||
except Exception:
|
except Exception:
|
||||||
lockDetails = ""
|
lockDetails = ""
|
||||||
|
|
||||||
msgBox = QMessageBox()
|
if self.askQuestion(lockText, info=lockInfo, details=lockDetails, level=nwAlert.WARN):
|
||||||
msgRes = msgBox.warning(
|
|
||||||
self, self.tr("Project Locked"),
|
|
||||||
"%s<br><br>%s<br>%s" % (
|
|
||||||
self.tr(
|
|
||||||
"The project is already open by another instance of "
|
|
||||||
"novelWriter, and is therefore locked. Override lock "
|
|
||||||
"and continue anyway?"
|
|
||||||
),
|
|
||||||
self.tr(
|
|
||||||
"Note: If the program or the computer previously "
|
|
||||||
"crashed, the lock can safely be overridden. However, "
|
|
||||||
"overriding it is not recommended if the project is "
|
|
||||||
"open in another instance of novelWriter. Doing so "
|
|
||||||
"may corrupt the project."
|
|
||||||
),
|
|
||||||
lockDetails
|
|
||||||
),
|
|
||||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
|
|
||||||
)
|
|
||||||
if msgRes == QMessageBox.Yes:
|
|
||||||
if not self.theProject.openProject(projFile, overrideLock=True):
|
if not self.theProject.openProject(projFile, overrideLock=True):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@@ -560,9 +555,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
# Check if we need to rebuild the index
|
# Check if we need to rebuild the index
|
||||||
if self.theProject.index.indexBroken:
|
if self.theProject.index.indexBroken:
|
||||||
self.makeAlert(self.tr(
|
self.makeAlert(self.tr("The project index is outdated or broken. Rebuilding index."))
|
||||||
"The project index is outdated or broken. Rebuilding index."
|
|
||||||
), nwAlert.INFO)
|
|
||||||
self.rebuildIndex()
|
self.rebuildIndex()
|
||||||
|
|
||||||
# Make sure the changed status is set to false on things opened
|
# Make sure the changed status is set to false on things opened
|
||||||
@@ -751,23 +744,20 @@ class GuiMain(QMainWindow):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.makeAlert(self.tr(
|
self.makeAlert(self.tr(
|
||||||
"Could not read file. The file must be an existing text file."
|
"Could not read file. The file must be an existing text file."
|
||||||
), nwAlert.ERROR, exception=exc)
|
), level=nwAlert.ERROR, exception=exc)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if self.docEditor.docHandle() is None:
|
if self.docEditor.docHandle() is None:
|
||||||
self.makeAlert(self.tr(
|
self.makeAlert(self.tr(
|
||||||
"Please open a document to import the text file into."
|
"Please open a document to import the text file into."
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.docEditor.isEmpty():
|
if not self.docEditor.isEmpty():
|
||||||
msgYes = self.askQuestion(
|
msgYes = self.askQuestion(self.tr(
|
||||||
self.tr("Import Document"),
|
"Importing the file will overwrite the current content of "
|
||||||
self.tr(
|
"the document. Do you want to proceed?"
|
||||||
"Importing the file will overwrite the current content of "
|
))
|
||||||
"the document. Do you want to proceed?"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -866,9 +856,7 @@ class GuiMain(QMainWindow):
|
|||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
|
|
||||||
if not beQuiet:
|
if not beQuiet:
|
||||||
self.makeAlert(self.tr(
|
self.makeAlert(self.tr("The project index has been successfully rebuilt."))
|
||||||
"The project index has been successfully rebuilt."
|
|
||||||
), nwAlert.INFO)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -916,7 +904,7 @@ class GuiMain(QMainWindow):
|
|||||||
if dlgConf.needsRestart:
|
if dlgConf.needsRestart:
|
||||||
self.makeAlert(self.tr(
|
self.makeAlert(self.tr(
|
||||||
"Some changes will not be applied until novelWriter has been restarted."
|
"Some changes will not be applied until novelWriter has been restarted."
|
||||||
), nwAlert.INFO)
|
))
|
||||||
|
|
||||||
if dlgConf.refreshTree:
|
if dlgConf.refreshTree:
|
||||||
self.projView.populateTree()
|
self.projView.populateTree()
|
||||||
@@ -1097,53 +1085,48 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def makeAlert(self, message: list[str] | str, level: nwAlert = nwAlert.INFO,
|
def makeAlert(self, text: str, info: str = "", details: str = "",
|
||||||
exception: Exception | None = None) -> None:
|
level: nwAlert = nwAlert.INFO, exception: Exception | None = None) -> None:
|
||||||
"""Alert both the user and the logger at the same time. The
|
"""Alert both the user and the logger at the same time. The
|
||||||
message can be either a string or a list of strings.
|
message can be either a string or a list of strings.
|
||||||
"""
|
"""
|
||||||
if isinstance(message, list):
|
logText = " ".join(filter(None, [text, info, details]))
|
||||||
message = list(filter(None, message)) # Strip empty strings
|
if level == nwAlert.INFO:
|
||||||
popMsg = "<br>".join(message)
|
logger.info(logText, stacklevel=2)
|
||||||
logMsg = " ".join(message)
|
elif level == nwAlert.WARN:
|
||||||
else:
|
logger.warning(logText, stacklevel=2)
|
||||||
popMsg = str(message)
|
elif level == nwAlert.ERROR:
|
||||||
logMsg = str(message)
|
logger.error(logText, stacklevel=2, exc_info=exception)
|
||||||
|
|
||||||
kw = {}
|
|
||||||
if exception is not None:
|
if exception is not None:
|
||||||
kw["exc_info"] = exception
|
excText = f"{type(exception).__name__}: {str(exception)}"
|
||||||
popMsg = f"{popMsg}<br>{type(exception).__name__}: {str(exception)}"
|
info = f"{info}<br>{excText}" if info else excText
|
||||||
|
|
||||||
# Write to Log
|
msgBox = QMessageBox(self)
|
||||||
if level == nwAlert.INFO:
|
msgBox.setWindowTitle(trConst(nwLabels.ALERT_NAME[level]))
|
||||||
logger.info(logMsg, **kw)
|
msgBox.setText(text)
|
||||||
elif level == nwAlert.WARN:
|
msgBox.setInformativeText(info)
|
||||||
logger.warning(logMsg, **kw)
|
msgBox.setDetailedText(details)
|
||||||
elif level == nwAlert.ERROR:
|
msgBox.setStandardButtons(QMessageBox.Ok)
|
||||||
logger.error(logMsg, **kw)
|
msgBox.setIconPixmap(self.alertPix[level])
|
||||||
elif level == nwAlert.BUG:
|
msgBox.adjustSize()
|
||||||
logger.error(logMsg, **kw)
|
msgBox.exec_()
|
||||||
|
|
||||||
# Popup
|
|
||||||
msgBox = QMessageBox()
|
|
||||||
if level == nwAlert.INFO:
|
|
||||||
msgBox.information(self, self.tr("Information"), popMsg)
|
|
||||||
elif level == nwAlert.WARN:
|
|
||||||
msgBox.warning(self, self.tr("Warning"), popMsg)
|
|
||||||
elif level == nwAlert.ERROR:
|
|
||||||
msgBox.critical(self, self.tr("Error"), popMsg)
|
|
||||||
elif level == nwAlert.BUG:
|
|
||||||
popMsg += "<br>%s" % self.tr("This is a bug!")
|
|
||||||
msgBox.critical(self, self.tr("Internal Error"), popMsg)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def askQuestion(self, title: str, question: str) -> bool:
|
def askQuestion(self, text: str, info: str = "", details: str = "",
|
||||||
|
level: nwAlert = nwAlert.ASK) -> bool:
|
||||||
"""Ask the user a Yes/No question, and return the answer."""
|
"""Ask the user a Yes/No question, and return the answer."""
|
||||||
msgBox = QMessageBox()
|
msgBox = QMessageBox(self)
|
||||||
msgRes = msgBox.question(self, title, question, QMessageBox.Yes | QMessageBox.No)
|
msgBox.setWindowTitle(trConst(nwLabels.ALERT_NAME[level]))
|
||||||
return msgRes == QMessageBox.Yes
|
msgBox.setText(text)
|
||||||
|
msgBox.setInformativeText(info)
|
||||||
|
msgBox.setDetailedText(details)
|
||||||
|
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
||||||
|
msgBox.setIconPixmap(self.alertPix[level])
|
||||||
|
msgBox.adjustSize()
|
||||||
|
msgBox.exec_()
|
||||||
|
return msgBox.result() == QMessageBox.Yes
|
||||||
|
|
||||||
def reportConfErr(self) -> bool:
|
def reportConfErr(self) -> bool:
|
||||||
"""Checks if the Config module has any errors to report, and let
|
"""Checks if the Config module has any errors to report, and let
|
||||||
@@ -1151,7 +1134,7 @@ class GuiMain(QMainWindow):
|
|||||||
errors since it is initialised before the GUI itself.
|
errors since it is initialised before the GUI itself.
|
||||||
"""
|
"""
|
||||||
if CONFIG.hasError:
|
if CONFIG.hasError:
|
||||||
self.makeAlert(CONFIG.errorText(), nwAlert.ERROR)
|
self.makeAlert(CONFIG.errorText(), level=nwAlert.ERROR)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1162,13 +1145,10 @@ class GuiMain(QMainWindow):
|
|||||||
def closeMain(self) -> bool:
|
def closeMain(self) -> bool:
|
||||||
"""Save everything, and close novelWriter."""
|
"""Save everything, and close novelWriter."""
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
msgYes = self.askQuestion(
|
msgYes = self.askQuestion("%s<br>%s" % (
|
||||||
self.tr("Exit"),
|
self.tr("Do you want to exit novelWriter?"),
|
||||||
"%s<br>%s" % (
|
self.tr("Changes are saved automatically.")
|
||||||
self.tr("Do you want to exit novelWriter?"),
|
))
|
||||||
self.tr("Changes are saved automatically.")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if not msgYes:
|
if not msgYes:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1419,7 +1399,7 @@ class GuiMain(QMainWindow):
|
|||||||
"from the Tools menu, or by pressing {1}."
|
"from the Tools menu, or by pressing {1}."
|
||||||
).format(
|
).format(
|
||||||
tag, "F9"
|
tag, "F9"
|
||||||
), nwAlert.ERROR)
|
), level=nwAlert.ERROR)
|
||||||
return None, None
|
return None, None
|
||||||
return tHandle, sTitle
|
return tHandle, sTitle
|
||||||
|
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
self.buildProgress.setValue(0)
|
self.buildProgress.setValue(0)
|
||||||
bPath = Path(self.buildPath.text())
|
bPath = Path(self.buildPath.text())
|
||||||
if not bPath.is_dir():
|
if not bPath.is_dir():
|
||||||
self.mainGui.makeAlert(self.tr("Output folder does not exist."), nwAlert.ERROR)
|
self.mainGui.makeAlert(self.tr("Output folder does not exist."), level=nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
bExt = nwLabels.BUILD_EXT[bFormat]
|
bExt = nwLabels.BUILD_EXT[bFormat]
|
||||||
@@ -318,7 +318,6 @@ class GuiManuscriptBuild(QDialog):
|
|||||||
|
|
||||||
if buildPath.exists():
|
if buildPath.exists():
|
||||||
if not self.mainGui.askQuestion(
|
if not self.mainGui.askQuestion(
|
||||||
self.tr("File Exists"),
|
|
||||||
self.tr("The file already exists. Do you want to overwrite it?")
|
self.tr("The file already exists. Do you want to overwrite it?")
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -270,11 +270,7 @@ class GuiManuscript(QDialog):
|
|||||||
"""Delete the currently selected build settings entry."""
|
"""Delete the currently selected build settings entry."""
|
||||||
build = self._getSelectedBuild()
|
build = self._getSelectedBuild()
|
||||||
if build is not None:
|
if build is not None:
|
||||||
proceed = self.mainGui.askQuestion(
|
if self.mainGui.askQuestion(self.tr("Delete build '{0}'?".format(build.name))):
|
||||||
self.tr("Delete Build"),
|
|
||||||
self.tr("Delete build '{0}'?".format(build.name))
|
|
||||||
)
|
|
||||||
if proceed:
|
|
||||||
self._builds.removeBuild(build.buildID)
|
self._builds.removeBuild(build.buildID)
|
||||||
self._updateBuildsList()
|
self._updateBuildsList()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -244,15 +244,14 @@ class GuiBuildSettings(QDialog):
|
|||||||
##
|
##
|
||||||
|
|
||||||
def _askToSaveBuild(self) -> None:
|
def _askToSaveBuild(self) -> None:
|
||||||
"""Check if there are unsaved changes, and if there are, ask if
|
"""Check if there are unsaved changes, and if there are, ask
|
||||||
it's ok to reject them.
|
whether the user wants to save them.
|
||||||
"""
|
"""
|
||||||
if self._build.changed:
|
if self._build.changed:
|
||||||
doSave = self.mainGui.askQuestion(
|
response = self.mainGui.askQuestion(self.tr(
|
||||||
self.tr("Build Settings"),
|
"Do you want to save your changes to '{0}'?".format(self._build.name)
|
||||||
self.tr("Do you want to save your changes?")
|
))
|
||||||
)
|
if response:
|
||||||
if doSave:
|
|
||||||
self._emitBuildData()
|
self._emitBuildData()
|
||||||
self._build.resetChangedState()
|
self._build.resetChangedState()
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -415,13 +415,15 @@ class GuiWritingStats(QDialog):
|
|||||||
|
|
||||||
# Report to user
|
# Report to user
|
||||||
if wSuccess:
|
if wSuccess:
|
||||||
self.mainGui.makeAlert([
|
self.mainGui.makeAlert(
|
||||||
self.tr("{0} file successfully written to:").format(textFmt), savePath
|
self.tr("{0} file successfully written to:").format(textFmt),
|
||||||
], nwAlert.INFO)
|
info=savePath
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.mainGui.makeAlert([
|
self.mainGui.makeAlert(
|
||||||
self.tr("Failed to write {0} file.").format(textFmt), errMsg
|
self.tr("Failed to write {0} file.").format(textFmt),
|
||||||
], nwAlert.ERROR)
|
info=errMsg, level=nwAlert.ERROR
|
||||||
|
)
|
||||||
|
|
||||||
return wSuccess
|
return wSuccess
|
||||||
|
|
||||||
|
|||||||
@@ -145,10 +145,8 @@ def mockGUI():
|
|||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def nwGUI(qtbot, monkeypatch, functionFixture):
|
def nwGUI(qtbot, monkeypatch, functionFixture):
|
||||||
"""Create an instance of the novelWriter GUI."""
|
"""Create an instance of the novelWriter GUI."""
|
||||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Ok)
|
monkeypatch.setattr(QMessageBox, "exec_", lambda *a: None)
|
||||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
|
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.Yes)
|
||||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Ok)
|
|
||||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
|
||||||
|
|
||||||
nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"])
|
nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"])
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
|
|||||||
@@ -39,22 +39,22 @@ class MockGuiMain(QObject):
|
|||||||
# Test Variables
|
# Test Variables
|
||||||
self.askResponse = True
|
self.askResponse = True
|
||||||
self.lastAlert = ""
|
self.lastAlert = ""
|
||||||
self.lastQuestion = ("", "")
|
self.lastQuestion = ""
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def postLaunchTasks(self, cmdOpen):
|
def postLaunchTasks(self, cmdOpen):
|
||||||
return
|
return
|
||||||
|
|
||||||
def makeAlert(self, message, level=0, exception=None):
|
def makeAlert(self, text, info="", detals="", level=0, exception=None):
|
||||||
assert isinstance(message, str) or isinstance(message, list)
|
assert isinstance(text, str)
|
||||||
print("%s: %s" % (str(level), message))
|
print("%s: %s" % (str(level), text))
|
||||||
self.lastAlert = str(message)
|
self.lastAlert = str(text)
|
||||||
return
|
return
|
||||||
|
|
||||||
def askQuestion(self, title, qustion):
|
def askQuestion(self, text, info="", details="", level=3):
|
||||||
print("Question: %s" % qustion)
|
print("Question: %s" % text)
|
||||||
self.lastQuestion = (title, qustion)
|
self.lastQuestion = text
|
||||||
return self.askResponse
|
return self.askResponse
|
||||||
|
|
||||||
def setStatus(self, theMessage):
|
def setStatus(self, theMessage):
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
|||||||
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
|
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
|
||||||
mockGUI.askResponse = False
|
mockGUI.askResponse = False
|
||||||
assert theProject.openProject(fncPath) is False
|
assert theProject.openProject(fncPath) is False
|
||||||
assert "The file format of your project is about to be" in mockGUI.lastQuestion[1]
|
assert "The file format of your project is about to be" in mockGUI.lastQuestion
|
||||||
mockGUI.askResponse = True
|
mockGUI.askResponse = True
|
||||||
|
|
||||||
# Won't open project from newer version
|
# Won't open project from newer version
|
||||||
@@ -232,7 +232,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
|||||||
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
|
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
|
||||||
mockGUI.askResponse = False
|
mockGUI.askResponse = False
|
||||||
assert theProject.openProject(fncPath) is False
|
assert theProject.openProject(fncPath) is False
|
||||||
assert "This project was saved by a newer version" in mockGUI.lastQuestion[1]
|
assert "This project was saved by a newer version" in mockGUI.lastQuestion
|
||||||
mockGUI.askResponse = True
|
mockGUI.askResponse = True
|
||||||
|
|
||||||
# Fail checking items should still pass
|
# Fail checking items should still pass
|
||||||
@@ -249,7 +249,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
|||||||
mockGUI.askResponse = True
|
mockGUI.askResponse = True
|
||||||
theProject.index._indexBroken = True
|
theProject.index._indexBroken = True
|
||||||
assert theProject.openProject(fncPath) is True
|
assert theProject.openProject(fncPath) is True
|
||||||
assert "The file format of your project is about to be" in mockGUI.lastQuestion[1]
|
assert "The file format of your project is about to be" in mockGUI.lastQuestion
|
||||||
assert theProject.index._indexBroken is False
|
assert theProject.index._indexBroken is False
|
||||||
|
|
||||||
theProject.closeProject()
|
theProject.closeProject()
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, projPath):
|
|||||||
# Close project
|
# Close project
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
nwGUI.hasProject = True
|
nwGUI.hasProject = True
|
||||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||||
assert nwGUI.newProject(projData={"projPath": projPath}) is False
|
assert nwGUI.newProject(projData={"projPath": projPath}) is False
|
||||||
|
|
||||||
# No project path
|
# No project path
|
||||||
|
|||||||
@@ -34,12 +34,9 @@ LANG_DATA = CONFIG.listLanguages(CONFIG.LANG_NW)
|
|||||||
@pytest.mark.skipif(not LANG_DATA, reason="No i18n Data")
|
@pytest.mark.skipif(not LANG_DATA, reason="No i18n Data")
|
||||||
@pytest.mark.parametrize("language", [a for a, b in LANG_DATA])
|
@pytest.mark.parametrize("language", [a for a, b in LANG_DATA])
|
||||||
def testI18n_Localisation(qtbot, monkeypatch, language, fncPath):
|
def testI18n_Localisation(qtbot, monkeypatch, language, fncPath):
|
||||||
"""test loading the gui with a specific language.
|
"""Test loading the gui with a specific language."""
|
||||||
"""
|
monkeypatch.setattr(QMessageBox, "exec_", lambda *a: None)
|
||||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Ok)
|
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.Yes)
|
||||||
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
|
|
||||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Ok)
|
|
||||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
|
||||||
|
|
||||||
# Set the test langauge
|
# Set the test langauge
|
||||||
CONFIG.guiLocale = language
|
CONFIG.guiLocale = language
|
||||||
|
|||||||
@@ -642,7 +642,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
|
|||||||
|
|
||||||
# The document isn't empty, so the message box should pop
|
# The document isn't empty, so the message box should pop
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr(QMessageBox, "question", lambda *a, **k: QMessageBox.No)
|
mp.setattr(QMessageBox, "result", lambda *a, **k: QMessageBox.No)
|
||||||
assert not nwGUI.importDocument()
|
assert not nwGUI.importDocument()
|
||||||
assert nwGUI.docEditor.getText() == "Bar"
|
assert nwGUI.docEditor.getText() == "Bar"
|
||||||
|
|
||||||
@@ -655,16 +655,16 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
|
|||||||
|
|
||||||
theMessage = ""
|
theMessage = ""
|
||||||
|
|
||||||
def recordMsg(*args):
|
def recordMsg(*args, **kwargs):
|
||||||
nonlocal theMessage
|
nonlocal theMessage
|
||||||
theMessage = args[3]
|
theMessage = "%s|%s" % (args[0], kwargs["info"])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
assert not theMessage
|
assert not theMessage
|
||||||
monkeypatch.setattr(QMessageBox, "information", recordMsg)
|
monkeypatch.setattr(nwGUI, "makeAlert", recordMsg)
|
||||||
nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)
|
nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)
|
||||||
|
|
||||||
theBits = theMessage.split("<br>")
|
theBits = theMessage.split("|")
|
||||||
assert len(theBits) == 2
|
assert len(theBits) == 2
|
||||||
assert theBits[0] == "The currently open file is saved in:"
|
assert theBits[0] == "The currently open file is saved in:"
|
||||||
assert theBits[1] == str(projPath / "content" / "000000000000f.nwd")
|
assert theBits[1] == str(projPath / "content" / "000000000000f.nwd")
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, projPath,
|
|||||||
|
|
||||||
# User cancels action
|
# User cancels action
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||||
assert projTree.moveItemToTrash(C.hTitlePage) is False
|
assert projTree.moveItemToTrash(C.hTitlePage) is False
|
||||||
assert theProject.tree.isTrash(C.hTitlePage) is False
|
assert theProject.tree.isTrash(C.hTitlePage) is False
|
||||||
|
|
||||||
@@ -444,7 +444,7 @@ def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, pro
|
|||||||
|
|
||||||
# User cancels action
|
# User cancels action
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||||
assert projTree.permDeleteItem(C.hTitlePage) is False
|
assert projTree.permDeleteItem(C.hTitlePage) is False
|
||||||
assert C.hTitlePage in theProject.tree
|
assert C.hTitlePage in theProject.tree
|
||||||
|
|
||||||
@@ -496,7 +496,7 @@ def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, projPath, mock
|
|||||||
|
|
||||||
# User cancels
|
# User cancels
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||||
assert projTree.emptyTrash() is False
|
assert projTree.emptyTrash() is False
|
||||||
assert C.hTitlePage in theProject.tree
|
assert C.hTitlePage in theProject.tree
|
||||||
assert C.hChapterDir in theProject.tree
|
assert C.hChapterDir in theProject.tree
|
||||||
@@ -617,7 +617,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
# Click no on the dialog
|
# Click no on the dialog
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||||
projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT)
|
projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT)
|
||||||
assert nwGUI.theProject.tree[hNewFolderOne].isFolderType()
|
assert nwGUI.theProject.tree[hNewFolderOne].isFolderType()
|
||||||
|
|
||||||
@@ -864,7 +864,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock
|
|||||||
|
|
||||||
# Duplicate title page, but select no
|
# Duplicate title page, but select no
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||||
assert projTree._duplicateFromHandle(C.hTitlePage) is False
|
assert projTree._duplicateFromHandle(C.hTitlePage) is False
|
||||||
assert len(nwGUI.theProject.tree) == 8
|
assert len(nwGUI.theProject.tree) == 8
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ def testManuscriptBuild_Main(
|
|||||||
manus.buildPath.setText(str(fncPath))
|
manus.buildPath.setText(str(fncPath))
|
||||||
manus.buildName.setText("TestBuild")
|
manus.buildName.setText("TestBuild")
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
|
||||||
assert manus._runBuild() is False
|
assert manus._runBuild() is False
|
||||||
|
|
||||||
# Finish
|
# Finish
|
||||||
|
|||||||