From 642b5e368168bb31f928d9935512ed3339edd9bb Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 11 Feb 2024 20:57:27 +0100 Subject: [PATCH 1/2] Add a formatting function for file extension filters for file dialogs --- novelwriter/common.py | 15 ++++++++++++++- novelwriter/constants.py | 6 ++++++ tests/test_base/test_base_common.py | 22 +++++++++++++++++----- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/novelwriter/common.py b/novelwriter/common.py index b658cf6df..0c160fe95 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -41,7 +41,7 @@ from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout from novelwriter.error import logException -from novelwriter.constants import nwConst, nwUnicode +from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst if TYPE_CHECKING: # pragma: no cover from typing import TypeGuard # Requires Python 3.10 @@ -248,6 +248,19 @@ def formatVersion(value: str) -> str: return value.lower().replace("a", " Alpha ").replace("b", " Beta ").replace("rc", " RC ") +def formatFileFilter(extensions: list[str | tuple[str, str]]) -> str: + """Format a list of extensions, or extension + label pairs into a + QFileDialog extensions filter. + """ + result = [] + for ext in extensions: + if isinstance(ext, str): + result.append(f"{trConst(nwLabels.FILE_FILTERS.get(ext))} ({ext})") + elif isinstance(ext, tuple) and len(ext) == 2: + result.append(f"{ext[0]} ({ext[1]})") + return ";;".join(result) + + ## # String Functions ## diff --git a/novelwriter/constants.py b/novelwriter/constants.py index e25995125..bd8d366da 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -270,6 +270,12 @@ class nwLabels: nwBuildFmt.J_HTML: ".json", nwBuildFmt.J_NWD: ".json", } + FILE_FILTERS = { + "*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"), + "*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"), + "*.nwd": QT_TRANSLATE_NOOP("Constant", "novelWriter files"), + "*": QT_TRANSLATE_NOOP("Constant", "All files"), + } UNIT_NAME = { "mm": QT_TRANSLATE_NOOP("Constant", "Millimetres"), "cm": QT_TRANSLATE_NOOP("Constant", "Centimetres"), diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py index 0aa96456f..7cadfe9e0 100644 --- a/tests/test_base/test_base_common.py +++ b/tests/test_base/test_base_common.py @@ -34,11 +34,11 @@ from novelwriter.common import ( checkBool, checkFloat, checkInt, checkIntTuple, checkPath, checkString, - checkStringNone, checkUuid, formatInt, formatTime, formatTimeStamp, - formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, - isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax, - numberToRoman, NWConfigParser, openExternalPath, readTextFile, simplified, - transferCase, xmlIndent, yesNo + checkStringNone, checkUuid, formatFileFilter, formatInt, formatTime, + formatTimeStamp, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle, + isItemClass, isItemLayout, isItemType, isTitleTag, jsonEncode, + makeFileNameSafe, minmax, numberToRoman, NWConfigParser, openExternalPath, + readTextFile, simplified, transferCase, xmlIndent, yesNo ) @@ -346,6 +346,18 @@ def testBaseCommon_formatVersion(): # END Test testBaseCommon_formatVersion +@pytest.mark.base +def testBaseCommon_formatFileFilter(): + """Test the formatFileFilter function.""" + assert formatFileFilter(["*.txt"]) == "Text files (*.txt)" + assert formatFileFilter(["*.txt", "*"]) == "Text files (*.txt);;All files (*)" + assert formatFileFilter([("Stuff", "*.stuff"), "*.txt", "*"]) == ( + "Stuff (*.stuff);;Text files (*.txt);;All files (*)" + ) + +# END Test testBaseCommon_formatFileFilter + + @pytest.mark.base def testBaseCommon_simplified(): """Test the simplified function.""" From 701fe29de910fb0e42505a61de606cb634927d83 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 11 Feb 2024 20:59:29 +0100 Subject: [PATCH 2/2] Add the file filter function where needed --- novelwriter/dialogs/wordlist.py | 8 +++----- novelwriter/guimain.py | 11 +++-------- novelwriter/shared.py | 8 ++++---- novelwriter/tools/dictionaries.py | 11 +++++------ novelwriter/tools/writingstats.py | 2 +- 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index 536418303..7c65f535e 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -36,6 +36,7 @@ ) from novelwriter import CONFIG, SHARED +from novelwriter.common import formatFileFilter from novelwriter.core.spellcheck import UserDictionary from novelwriter.extensions.configlayout import NColourLabel @@ -184,12 +185,9 @@ def _importWords(self) -> None: SHARED.info(self.tr( "Note: The import file must be a plain text file with UTF-8 or ASCII encoding." )) - extFilter = [ - "{0} (*.txt)".format(self.tr("Text files")), - "{0} (*)".format(self.tr("All files")), - ] + ffilter = formatFileFilter(["*.txt", "*"]) path, _ = QFileDialog.getOpenFileName( - self, self.tr("Import File"), str(Path.home()), filter=";;".join(extFilter) + self, self.tr("Import File"), str(Path.home()), filter=ffilter ) if path: try: diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 0f3fa7199..842a9e6c9 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -63,7 +63,7 @@ from novelwriter.enum import ( nwDocAction, nwDocInsert, nwDocMode, nwItemType, nwWidget, nwView ) -from novelwriter.common import hexToInt +from novelwriter.common import formatFileFilter, hexToInt logger = logging.getLogger(__name__) @@ -664,14 +664,9 @@ def importDocument(self) -> bool: return False lastPath = CONFIG.lastPath() - extFilter = [ - "{0} (*.txt)".format(self.tr("Text files")), - "{0} (*.md)".format(self.tr("Markdown files")), - "{0} (*.nwd)".format(self.tr("novelWriter files")), - "{0} (*)".format(self.tr("All files")), - ] + ffilter = formatFileFilter(["*.txt", "*.md", "*.nwd", "*"]) loadFile, _ = QFileDialog.getOpenFileName( - self, self.tr("Import File"), str(lastPath), filter=";;".join(extFilter) + self, self.tr("Import File"), str(lastPath), filter=ffilter ) if not loadFile: return False diff --git a/novelwriter/shared.py b/novelwriter/shared.py index 1c003687e..c7f7f70df 100644 --- a/novelwriter/shared.py +++ b/novelwriter/shared.py @@ -32,6 +32,7 @@ from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal from PyQt5.QtWidgets import QFileDialog, QMessageBox, QWidget +from novelwriter.common import formatFileFilter from novelwriter.constants import nwFiles from novelwriter.core.spellcheck import NWSpellEnchant @@ -221,13 +222,12 @@ def runInThreadPool(self, runnable: QRunnable, priority: int = 0) -> None: def getProjectPath(self, parent: QWidget, path: str | Path | None = None, allowZip: bool = False) -> Path | None: """Open the file dialog and select a novelWriter project file.""" - label = (self.tr("novelWriter Project File or Zip") + label = (self.tr("novelWriter Project File or Zip File") if allowZip else self.tr("novelWriter Project File")) ext = f"{nwFiles.PROJ_FILE} *.zip" if allowZip else nwFiles.PROJ_FILE + ffilter = formatFileFilter([(label, ext), "*"]) selected, _ = QFileDialog.getOpenFileName( - parent, self.tr("Open Project"), str(path or ""), filter=";;".join( - [f"{label} ({ext})", "{0} (*)".format(self.tr("All Files"))] - ) + parent, self.tr("Open Project"), str(path or ""), filter=ffilter ) return Path(selected) if selected else None diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py index 23e29bcfb..7d1223488 100644 --- a/novelwriter/tools/dictionaries.py +++ b/novelwriter/tools/dictionaries.py @@ -37,7 +37,7 @@ from novelwriter import CONFIG, SHARED from novelwriter.error import formatException -from novelwriter.common import openExternalPath, formatInt, getFileSize +from novelwriter.common import formatFileFilter, openExternalPath, formatInt, getFileSize logger = logging.getLogger(__name__) @@ -180,12 +180,11 @@ def closeEvent(self, event: QCloseEvent) -> None: @pyqtSlot() def _doBrowseHunspell(self): """Browse for a Free/Libre Office dictionary.""" - extFilter = [ - self.tr("Free or Libre Office extension ({0})").format("*.sox *.oxt"), - self.tr("All files ({0})").format("*"), - ] + ffilter = formatFileFilter([ + (self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*" + ]) soxFile, _ = QFileDialog.getOpenFileName( - self, self.tr("Browse Files"), "", filter=";;".join(extFilter) + self, self.tr("Browse Files"), "", filter=ffilter ) if soxFile: path = Path(soxFile).absolute() diff --git a/novelwriter/tools/writingstats.py b/novelwriter/tools/writingstats.py index a215df20c..95f737e22 100644 --- a/novelwriter/tools/writingstats.py +++ b/novelwriter/tools/writingstats.py @@ -381,7 +381,7 @@ def _saveData(self, dataFmt: int) -> bool: # Generate the file name savePath = CONFIG.lastPath() / f"sessionStats.{fileExt}" savePath, _ = QFileDialog.getSaveFileName( - self, self.tr("Save Data As"), str(savePath), "%s (*.%s)" % (textFmt, fileExt) + self, self.tr("Save Data As"), str(savePath), f"{textFmt} (*.{fileExt})" ) if not savePath: return False