diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index fa3ac8e2..4253632c 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -47,9 +47,9 @@ __license__ = "GPLv3" __author__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen" __email__ = "code@vkbo.net" -__version__ = "2.4rc1" -__hexversion__ = "0x020400c1" -__date__ = "2024-04-06" +__version__ = "2.5rc1" +__hexversion__ = "0x020500a1" +__date__ = "2024-04-13" __status__ = "Stable" __domain__ = "novelwriter.io" diff --git a/novelwriter/config.py b/novelwriter/config.py index 1f817e0f..e9fab918 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -390,6 +390,10 @@ class Config: """Un-scale fixed gui sizes by the screen scale factor.""" 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: """Return a path in the data folder.""" if isinstance(target, str): diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index 3957ed27..b68dd97d 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -178,7 +178,7 @@ class BuildSettings: def __init__(self) -> None: self._name = "" self._uuid = str(uuid.uuid4()) - self._path = Path.home() + self._path = CONFIG.homePath() self._build = "" self._order = 0 self._format = nwBuildFmt.ODT @@ -220,7 +220,7 @@ class BuildSettings: """The last used build path.""" if self._path.is_dir(): return self._path - return Path.home() + return CONFIG.homePath() @property def lastBuildName(self) -> str: @@ -297,7 +297,7 @@ class BuildSettings: if isinstance(path, Path) and path.is_dir(): self._path = path else: - self._path = Path.home() + self._path = CONFIG.homePath() self._changed = True return diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index bd9123e5..10077c84 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -263,17 +263,17 @@ class _SettingsPage(NScrollableForm): ) # Project Language + projLang = data.language or CONFIG.guiLocale self.projLang = NComboBox(self) self.projLang.setMinimumWidth(xW) for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): self.projLang.addItem(language, tag) + self.projLang.setCurrentData(projLang, projLang) self.addRow( self.tr("Project language"), self.projLang, self.tr("Only used when building the manuscript."), stretch=(3, 2) ) - if (idx := self.projLang.findData(data.language)) != -1: - self.projLang.setCurrentIndex(idx) # Spell Check Language self.spellLang = NComboBox(self) diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index c82ca3d6..54ec2f2f 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -190,7 +190,7 @@ class GuiWordList(QDialog): )) ffilter = formatFileFilter(["*.txt", "*"]) 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: try: @@ -207,7 +207,7 @@ class GuiWordList(QDialog): def _exportWords(self) -> None: """Export words to file.""" path, _ = QFileDialog.getSaveFileName( - self, self.tr("Export File"), str(Path.home()) + self, self.tr("Export File"), str(CONFIG.homePath()) ) if path: try: diff --git a/novelwriter/text/counting.py b/novelwriter/text/counting.py index 59974ee5..61804edb 100644 --- a/novelwriter/text/counting.py +++ b/novelwriter/text/counting.py @@ -57,6 +57,7 @@ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]: continue if line[0] == ">": line = line.lstrip(">").lstrip(" ") + if line: # Above block can return empty line (Issue #1816) if line[-1] == "<": line = line.rstrip("<").rstrip(" ") if "[" in line: diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 149e1a88..782a7109 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -303,25 +303,21 @@ class GuiManuscript(QDialog): @pyqtSlot() def _editSelectedBuild(self) -> None: """Edit the currently selected build settings entry.""" - build = self._getSelectedBuild() - if build is not None: + if build := self._getSelectedBuild(): self._openSettingsDialog(build) return @pyqtSlot("QListWidgetItem*", "QListWidgetItem*") def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None: """Process change of build selection to update the details.""" - if isinstance(current, QListWidgetItem): - build = self._builds.getBuild(current.data(self.D_KEY)) - if build is not None: - self.buildDetails.updateInfo(build) + if current and (build := self._builds.getBuild(current.data(self.D_KEY))): + self.buildDetails.updateInfo(build) return @pyqtSlot() def _deleteSelectedBuild(self) -> None: """Delete the currently selected build settings entry.""" - build = self._getSelectedBuild() - if build is not None: + if build := self._getSelectedBuild(): if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))): self._builds.removeBuild(build.buildID) self._updateBuildsList() @@ -332,8 +328,7 @@ class GuiManuscript(QDialog): """Process new build settings from the settings dialog.""" self._builds.setBuild(build) self._updateBuildItem(build) - current = self.buildList.currentItem() - if isinstance(current, QListWidgetItem) and current.data(self.D_KEY) == build.buildID: + if (current := self.buildList.currentItem()) and current.data(self.D_KEY) == build.buildID: self._updateBuildDetails(current, current) return @@ -342,8 +337,7 @@ class GuiManuscript(QDialog): """Run the document builder on the current build settings for the preview widget. """ - build = self._getSelectedBuild() - if build is None: + if not (build := self._getSelectedBuild()): return docBuild = NWBuildDocument(SHARED.project, build) @@ -383,8 +377,7 @@ class GuiManuscript(QDialog): @pyqtSlot() def _buildManuscript(self) -> None: """Open the build dialog and build the manuscript.""" - build = self._getSelectedBuild() - if isinstance(build, BuildSettings): + if build := self._getSelectedBuild(): dlgBuild = GuiManuscriptBuild(self, build) dlgBuild.exec() diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index 2c08cdf5..ce37539b 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -560,7 +560,7 @@ class _NewProjectForm(QWidget): def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) - self._basePath = CONFIG.lastPath() + self._basePath = CONFIG.homePath() self._fillMode = self.FILL_BLANK self._copyPath = None @@ -632,7 +632,7 @@ class _NewProjectForm(QWidget): self.numChapters = NSpinBox(self) 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.chapterBox = NWrappedWidgetBox( @@ -642,7 +642,7 @@ class _NewProjectForm(QWidget): self.numScenes = NSpinBox(self) self.numScenes.setRange(0, 200) - self.numScenes.setValue(5) + self.numScenes.setValue(0) self.sceneBox = NWrappedWidgetBox( self.tr("Add {0} scene documents (to each chapter)"), self.numScenes @@ -742,7 +742,6 @@ class _NewProjectForm(QWidget): ): self._basePath = Path(projDir) self._updateProjPath() - CONFIG.setLastPath(self._basePath) return @pyqtSlot() diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 5ec474fe..7c9cc88d 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -200,6 +200,9 @@ def testBaseConfig_Methods(fncPath): tstConf = Config() tstConf.initConfig(confPath=fncPath, dataPath=fncPath) + # Home Path + assert tstConf.homePath() == Path.home().absolute() + # Data Path assert tstConf.dataPath() == fncPath assert tstConf.dataPath("stuff") == fncPath / "stuff" diff --git a/tests/test_core/test_core_buildsettings.py b/tests/test_core/test_core_buildsettings.py index bccccae1..10c63253 100644 --- a/tests/test_core/test_core_buildsettings.py +++ b/tests/test_core/test_core_buildsettings.py @@ -27,6 +27,7 @@ import shutil from pathlib import Path +from novelwriter import CONFIG from tools import C, buildTestProject from mocked import causeOSError @@ -58,7 +59,7 @@ def testCoreBuildSettings_ClassAttributes(fncPath: Path): build.setName("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") 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 build.setLastPath("/path/to/nowhere") - assert build.lastPath == Path.home() + assert build.lastPath == CONFIG.homePath() build.setLastPath(None) - assert build.lastPath == Path.home() + assert build.lastPath == CONFIG.homePath() (fncPath / "test.txt").write_text("foobar") build.setLastPath(fncPath / "test.txt") # Can't be a file - assert build.lastPath == Path.home() + assert build.lastPath == CONFIG.homePath() build.setLastPath(fncPath) assert build.lastPath == fncPath @@ -93,7 +94,7 @@ def testCoreBuildSettings_ClassAttributes(fncPath: Path): build.setLastPath(testDir) assert build.lastPath == testDir testDir.rmdir() - assert build.lastPath == Path.home() + assert build.lastPath == CONFIG.homePath() # Last build name build.setLastBuildName(None) # type: ignore diff --git a/tests/test_text/test_core_counting.py b/tests/test_text/test_core_counting.py index bab0596e..05780c49 100644 --- a/tests/test_text/test_core_counting.py +++ b/tests/test_text/test_core_counting.py @@ -29,7 +29,7 @@ from novelwriter.text.counting import bodyTextCounter, preProcessText, standardC def testTextCounting_preProcessText(): """Test the text preprocessor for counters.""" # Not Text - assert preProcessText(None) == [] + assert preProcessText(None) == [] # type: ignore # No Text assert preProcessText("") == [] @@ -81,6 +81,10 @@ def testTextCounting_standardCounter(): assert standardCounter(None) == (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 cC, wC, pC = standardCounter(( "#! Title\n\n" @@ -88,7 +92,8 @@ def testTextCounting_standardCounter(): "# Heading One\n" "## Heading Two\n" "### Heading Three\n" - "#### Heading Four\n\n" + "###! Heading Four\n" + "#### Heading Five\n\n" "@tag: value\n\n" "% A comment that should not be counted.\n\n" "The first paragraph.\n\n" @@ -96,8 +101,8 @@ def testTextCounting_standardCounter(): "The third paragraph.\n\n" "Dashes\u2013and even longer\u2014dashes." )) - assert cC == 151 - assert wC == 24 + assert cC == 163 + assert wC == 26 assert pC == 4 # Text Alignment @@ -182,7 +187,7 @@ def testTextCounting_standardCounter(): def testTextCounting_bodyTextCounter(): """Test the body text counter.""" # Not Text - assert bodyTextCounter(None) == (0, 0, 0) + assert bodyTextCounter(None) == (0, 0, 0) # type: ignore # General Text wC, cC, sC = bodyTextCounter((