Merge branch 'main' into feature/status_shapes

This commit is contained in:
Veronica Berglyd Olsen
2024-04-13 01:04:24 +02:00
committed by GitHub
11 changed files with 44 additions and 38 deletions
+3 -3
View File
@@ -47,9 +47,9 @@ __license__ = "GPLv3"
__author__ = "Veronica Berglyd Olsen" __author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen"
__email__ = "code@vkbo.net" __email__ = "code@vkbo.net"
__version__ = "2.4rc1" __version__ = "2.5rc1"
__hexversion__ = "0x020400c1" __hexversion__ = "0x020500a1"
__date__ = "2024-04-06" __date__ = "2024-04-13"
__status__ = "Stable" __status__ = "Stable"
__domain__ = "novelwriter.io" __domain__ = "novelwriter.io"
+4
View File
@@ -390,6 +390,10 @@ class Config:
"""Un-scale fixed gui sizes by the screen scale factor.""" """Un-scale fixed gui sizes by the screen scale factor."""
return int(value/self.guiScale) return int(value/self.guiScale)
def homePath(self) -> Path:
"""The user's home folder."""
return self._homePath
def dataPath(self, target: str | None = None) -> Path: def dataPath(self, target: str | None = None) -> Path:
"""Return a path in the data folder.""" """Return a path in the data folder."""
if isinstance(target, str): if isinstance(target, str):
+3 -3
View File
@@ -178,7 +178,7 @@ class BuildSettings:
def __init__(self) -> None: def __init__(self) -> None:
self._name = "" self._name = ""
self._uuid = str(uuid.uuid4()) self._uuid = str(uuid.uuid4())
self._path = Path.home() self._path = CONFIG.homePath()
self._build = "" self._build = ""
self._order = 0 self._order = 0
self._format = nwBuildFmt.ODT self._format = nwBuildFmt.ODT
@@ -220,7 +220,7 @@ class BuildSettings:
"""The last used build path.""" """The last used build path."""
if self._path.is_dir(): if self._path.is_dir():
return self._path return self._path
return Path.home() return CONFIG.homePath()
@property @property
def lastBuildName(self) -> str: def lastBuildName(self) -> str:
@@ -297,7 +297,7 @@ class BuildSettings:
if isinstance(path, Path) and path.is_dir(): if isinstance(path, Path) and path.is_dir():
self._path = path self._path = path
else: else:
self._path = Path.home() self._path = CONFIG.homePath()
self._changed = True self._changed = True
return return
+2 -2
View File
@@ -263,17 +263,17 @@ class _SettingsPage(NScrollableForm):
) )
# Project Language # Project Language
projLang = data.language or CONFIG.guiLocale
self.projLang = NComboBox(self) self.projLang = NComboBox(self)
self.projLang.setMinimumWidth(xW) self.projLang.setMinimumWidth(xW)
for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ):
self.projLang.addItem(language, tag) self.projLang.addItem(language, tag)
self.projLang.setCurrentData(projLang, projLang)
self.addRow( self.addRow(
self.tr("Project language"), self.projLang, self.tr("Project language"), self.projLang,
self.tr("Only used when building the manuscript."), self.tr("Only used when building the manuscript."),
stretch=(3, 2) stretch=(3, 2)
) )
if (idx := self.projLang.findData(data.language)) != -1:
self.projLang.setCurrentIndex(idx)
# Spell Check Language # Spell Check Language
self.spellLang = NComboBox(self) self.spellLang = NComboBox(self)
+2 -2
View File
@@ -190,7 +190,7 @@ class GuiWordList(QDialog):
)) ))
ffilter = formatFileFilter(["*.txt", "*"]) ffilter = formatFileFilter(["*.txt", "*"])
path, _ = QFileDialog.getOpenFileName( path, _ = QFileDialog.getOpenFileName(
self, self.tr("Import File"), str(Path.home()), filter=ffilter self, self.tr("Import File"), str(CONFIG.homePath()), filter=ffilter
) )
if path: if path:
try: try:
@@ -207,7 +207,7 @@ class GuiWordList(QDialog):
def _exportWords(self) -> None: def _exportWords(self) -> None:
"""Export words to file.""" """Export words to file."""
path, _ = QFileDialog.getSaveFileName( path, _ = QFileDialog.getSaveFileName(
self, self.tr("Export File"), str(Path.home()) self, self.tr("Export File"), str(CONFIG.homePath())
) )
if path: if path:
try: try:
+1
View File
@@ -57,6 +57,7 @@ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]:
continue continue
if line[0] == ">": if line[0] == ">":
line = line.lstrip(">").lstrip(" ") line = line.lstrip(">").lstrip(" ")
if line: # Above block can return empty line (Issue #1816)
if line[-1] == "<": if line[-1] == "<":
line = line.rstrip("<").rstrip(" ") line = line.rstrip("<").rstrip(" ")
if "[" in line: if "[" in line:
+7 -14
View File
@@ -303,25 +303,21 @@ class GuiManuscript(QDialog):
@pyqtSlot() @pyqtSlot()
def _editSelectedBuild(self) -> None: def _editSelectedBuild(self) -> None:
"""Edit the currently selected build settings entry.""" """Edit the currently selected build settings entry."""
build = self._getSelectedBuild() if build := self._getSelectedBuild():
if build is not None:
self._openSettingsDialog(build) self._openSettingsDialog(build)
return return
@pyqtSlot("QListWidgetItem*", "QListWidgetItem*") @pyqtSlot("QListWidgetItem*", "QListWidgetItem*")
def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None: def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None:
"""Process change of build selection to update the details.""" """Process change of build selection to update the details."""
if isinstance(current, QListWidgetItem): if current and (build := self._builds.getBuild(current.data(self.D_KEY))):
build = self._builds.getBuild(current.data(self.D_KEY)) self.buildDetails.updateInfo(build)
if build is not None:
self.buildDetails.updateInfo(build)
return return
@pyqtSlot() @pyqtSlot()
def _deleteSelectedBuild(self) -> None: def _deleteSelectedBuild(self) -> None:
"""Delete the currently selected build settings entry.""" """Delete the currently selected build settings entry."""
build = self._getSelectedBuild() if build := self._getSelectedBuild():
if build is not None:
if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))): if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))):
self._builds.removeBuild(build.buildID) self._builds.removeBuild(build.buildID)
self._updateBuildsList() self._updateBuildsList()
@@ -332,8 +328,7 @@ class GuiManuscript(QDialog):
"""Process new build settings from the settings dialog.""" """Process new build settings from the settings dialog."""
self._builds.setBuild(build) self._builds.setBuild(build)
self._updateBuildItem(build) self._updateBuildItem(build)
current = self.buildList.currentItem() if (current := self.buildList.currentItem()) and current.data(self.D_KEY) == build.buildID:
if isinstance(current, QListWidgetItem) and current.data(self.D_KEY) == build.buildID:
self._updateBuildDetails(current, current) self._updateBuildDetails(current, current)
return return
@@ -342,8 +337,7 @@ class GuiManuscript(QDialog):
"""Run the document builder on the current build settings for """Run the document builder on the current build settings for
the preview widget. the preview widget.
""" """
build = self._getSelectedBuild() if not (build := self._getSelectedBuild()):
if build is None:
return return
docBuild = NWBuildDocument(SHARED.project, build) docBuild = NWBuildDocument(SHARED.project, build)
@@ -383,8 +377,7 @@ class GuiManuscript(QDialog):
@pyqtSlot() @pyqtSlot()
def _buildManuscript(self) -> None: def _buildManuscript(self) -> None:
"""Open the build dialog and build the manuscript.""" """Open the build dialog and build the manuscript."""
build = self._getSelectedBuild() if build := self._getSelectedBuild():
if isinstance(build, BuildSettings):
dlgBuild = GuiManuscriptBuild(self, build) dlgBuild = GuiManuscriptBuild(self, build)
dlgBuild.exec() dlgBuild.exec()
+3 -4
View File
@@ -560,7 +560,7 @@ class _NewProjectForm(QWidget):
def __init__(self, parent: QWidget) -> None: def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent) super().__init__(parent=parent)
self._basePath = CONFIG.lastPath() self._basePath = CONFIG.homePath()
self._fillMode = self.FILL_BLANK self._fillMode = self.FILL_BLANK
self._copyPath = None self._copyPath = None
@@ -632,7 +632,7 @@ class _NewProjectForm(QWidget):
self.numChapters = NSpinBox(self) self.numChapters = NSpinBox(self)
self.numChapters.setRange(0, 200) self.numChapters.setRange(0, 200)
self.numChapters.setValue(5) self.numChapters.setValue(0)
self.numChapters.setToolTip(self.tr("Set to 0 to only add scenes")) self.numChapters.setToolTip(self.tr("Set to 0 to only add scenes"))
self.chapterBox = NWrappedWidgetBox( self.chapterBox = NWrappedWidgetBox(
@@ -642,7 +642,7 @@ class _NewProjectForm(QWidget):
self.numScenes = NSpinBox(self) self.numScenes = NSpinBox(self)
self.numScenes.setRange(0, 200) self.numScenes.setRange(0, 200)
self.numScenes.setValue(5) self.numScenes.setValue(0)
self.sceneBox = NWrappedWidgetBox( self.sceneBox = NWrappedWidgetBox(
self.tr("Add {0} scene documents (to each chapter)"), self.numScenes self.tr("Add {0} scene documents (to each chapter)"), self.numScenes
@@ -742,7 +742,6 @@ class _NewProjectForm(QWidget):
): ):
self._basePath = Path(projDir) self._basePath = Path(projDir)
self._updateProjPath() self._updateProjPath()
CONFIG.setLastPath(self._basePath)
return return
@pyqtSlot() @pyqtSlot()
+3
View File
@@ -200,6 +200,9 @@ def testBaseConfig_Methods(fncPath):
tstConf = Config() tstConf = Config()
tstConf.initConfig(confPath=fncPath, dataPath=fncPath) tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
# Home Path
assert tstConf.homePath() == Path.home().absolute()
# Data Path # Data Path
assert tstConf.dataPath() == fncPath assert tstConf.dataPath() == fncPath
assert tstConf.dataPath("stuff") == fncPath / "stuff" assert tstConf.dataPath("stuff") == fncPath / "stuff"
+6 -5
View File
@@ -27,6 +27,7 @@ import shutil
from pathlib import Path from pathlib import Path
from novelwriter import CONFIG
from tools import C, buildTestProject from tools import C, buildTestProject
from mocked import causeOSError from mocked import causeOSError
@@ -58,7 +59,7 @@ def testCoreBuildSettings_ClassAttributes(fncPath: Path):
build.setName("Test Build") build.setName("Test Build")
assert build.name == "Test Build" assert build.name == "Test Build"
# Only valid UUIDs are accpeted, anything else generates a new UUID # Only valid UUIDs are accepted, anything else generates a new UUID
build.setBuildID("5cf45d24-f496-42c9-8733-529a9e52a62b") build.setBuildID("5cf45d24-f496-42c9-8733-529a9e52a62b")
assert build.buildID == "5cf45d24-f496-42c9-8733-529a9e52a62b" assert build.buildID == "5cf45d24-f496-42c9-8733-529a9e52a62b"
@@ -72,14 +73,14 @@ def testCoreBuildSettings_ClassAttributes(fncPath: Path):
# Last path must be valid, if not it defaults to $HOME # Last path must be valid, if not it defaults to $HOME
build.setLastPath("/path/to/nowhere") build.setLastPath("/path/to/nowhere")
assert build.lastPath == Path.home() assert build.lastPath == CONFIG.homePath()
build.setLastPath(None) build.setLastPath(None)
assert build.lastPath == Path.home() assert build.lastPath == CONFIG.homePath()
(fncPath / "test.txt").write_text("foobar") (fncPath / "test.txt").write_text("foobar")
build.setLastPath(fncPath / "test.txt") # Can't be a file build.setLastPath(fncPath / "test.txt") # Can't be a file
assert build.lastPath == Path.home() assert build.lastPath == CONFIG.homePath()
build.setLastPath(fncPath) build.setLastPath(fncPath)
assert build.lastPath == fncPath assert build.lastPath == fncPath
@@ -93,7 +94,7 @@ def testCoreBuildSettings_ClassAttributes(fncPath: Path):
build.setLastPath(testDir) build.setLastPath(testDir)
assert build.lastPath == testDir assert build.lastPath == testDir
testDir.rmdir() testDir.rmdir()
assert build.lastPath == Path.home() assert build.lastPath == CONFIG.homePath()
# Last build name # Last build name
build.setLastBuildName(None) # type: ignore build.setLastBuildName(None) # type: ignore
+10 -5
View File
@@ -29,7 +29,7 @@ from novelwriter.text.counting import bodyTextCounter, preProcessText, standardC
def testTextCounting_preProcessText(): def testTextCounting_preProcessText():
"""Test the text preprocessor for counters.""" """Test the text preprocessor for counters."""
# Not Text # Not Text
assert preProcessText(None) == [] assert preProcessText(None) == [] # type: ignore
# No Text # No Text
assert preProcessText("") == [] assert preProcessText("") == []
@@ -81,6 +81,10 @@ def testTextCounting_standardCounter():
assert standardCounter(None) == (0, 0, 0) # type: ignore assert standardCounter(None) == (0, 0, 0) # type: ignore
assert standardCounter(1234) == (0, 0, 0) # type: ignore assert standardCounter(1234) == (0, 0, 0) # type: ignore
# Test Corner Cases, Bug #1816
assert standardCounter("> ") == (0, 0, 0)
assert standardCounter(" <") == (0, 0, 0)
# General Text # General Text
cC, wC, pC = standardCounter(( cC, wC, pC = standardCounter((
"#! Title\n\n" "#! Title\n\n"
@@ -88,7 +92,8 @@ def testTextCounting_standardCounter():
"# Heading One\n" "# Heading One\n"
"## Heading Two\n" "## Heading Two\n"
"### Heading Three\n" "### Heading Three\n"
"#### Heading Four\n\n" "###! Heading Four\n"
"#### Heading Five\n\n"
"@tag: value\n\n" "@tag: value\n\n"
"% A comment that should not be counted.\n\n" "% A comment that should not be counted.\n\n"
"The first paragraph.\n\n" "The first paragraph.\n\n"
@@ -96,8 +101,8 @@ def testTextCounting_standardCounter():
"The third paragraph.\n\n" "The third paragraph.\n\n"
"Dashes\u2013and even longer\u2014dashes." "Dashes\u2013and even longer\u2014dashes."
)) ))
assert cC == 151 assert cC == 163
assert wC == 24 assert wC == 26
assert pC == 4 assert pC == 4
# Text Alignment # Text Alignment
@@ -182,7 +187,7 @@ def testTextCounting_standardCounter():
def testTextCounting_bodyTextCounter(): def testTextCounting_bodyTextCounter():
"""Test the body text counter.""" """Test the body text counter."""
# Not Text # Not Text
assert bodyTextCounter(None) == (0, 0, 0) assert bodyTextCounter(None) == (0, 0, 0) # type: ignore
# General Text # General Text
wC, cC, sC = bodyTextCounter(( wC, cC, sC = bodyTextCounter((