diff --git a/docs/source/tech_source.rst b/docs/source/tech_source.rst index 87d616a3f..9b100ebd9 100644 --- a/docs/source/tech_source.rst +++ b/docs/source/tech_source.rst @@ -39,11 +39,9 @@ The following Python packages are needed to run all features of novelWriter: * ``PyQt5`` – needed for connecting with the Qt5 libraries. * ``PyEnchant`` – needed for spell checking (optional). -PyQt/Qt should be at least 5.10, but ideally 5.13 or higher for all features to work. For instance, -searching using regular expressions with full Unicode support requires 5.13. - -If you want spell checking, you must install the ``PyEnchant`` package. The spell check library -must be at least 3.0 to work with Windows. On Linux, 2.0 also works fine. +PyQt/Qt must be at least 5.15.0. If you want spell checking, you must install the ``PyEnchant`` +package. The spell check library must be at least 3.0 to work with Windows. On Linux, 2.0 also +works fine. If you install from PyPi, these dependencies should be installed automatically. If you install from source, dependencies can still be installed from PyPi with: @@ -55,7 +53,7 @@ source, dependencies can still be installed from PyPi with: .. note:: On Linux distros, the Qt library is usually split up into multiple packages. In some cases, secondary dependencies may not be installed automatically. For novelWriter, the library files - for renderring the SVG icons may be left out and needs to be installed manually. This is the + for rendering the SVG icons may be left out and needs to be installed manually. This is the case on for instance Arch Linux. diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index dde2ca29f..cf1fa5c2f 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -167,14 +167,14 @@ def main(sysArgs: list | None = None) -> GuiMain | None: "At least Python 3.8 is required, found %s" % CONFIG.verPyString ) errorCode |= 0x04 - if CONFIG.verQtValue < 0x050a00: + if CONFIG.verQtValue < 0x050f00: errorData.append( - "At least Qt5 version 5.10 is required, found %s" % CONFIG.verQtString + "At least Qt5 version 5.15.0 is required, found %s" % CONFIG.verQtString ) errorCode |= 0x08 - if CONFIG.verPyQtValue < 0x050a00: + if CONFIG.verPyQtValue < 0x050f00: errorData.append( - "At least PyQt5 version 5.10 is required, found %s" % CONFIG.verPyQtString + "At least PyQt5 version 5.15.0 is required, found %s" % CONFIG.verPyQtString ) errorCode |= 0x10 diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index 925db9018..8b29bf397 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -370,17 +370,7 @@ def searchText(self, text: str) -> tuple[list[tuple[int, int, str]], bool]: def _buildPattern(self, search: str) -> str: """Build the search pattern string.""" if self._escape: - if CONFIG.verQtValue >= 0x050f00: - search = QRegularExpression.escape(search) - else: - # For older Qt versions, we escape manually - escaped = "" - for c in search: - if c.isalnum() or c == "_": - escaped += c - else: - escaped += f"\\{c}" - search = escaped + search = QRegularExpression.escape(search) if self._words: search = f"(?:^|\\b){search}(?:$|\\b)" return search diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 4d603289d..bb09e72f0 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -39,8 +39,8 @@ from typing import TYPE_CHECKING from PyQt5.QtCore import ( - pyqtSignal, pyqtSlot, QObject, QPoint, QRegExp, QRegularExpression, - QRunnable, Qt, QTimer + pyqtSignal, pyqtSlot, QObject, QPoint, QRegularExpression, QRunnable, Qt, + QTimer ) from PyQt5.QtGui import ( QColor, QCursor, QFont, QKeyEvent, QKeySequence, QMouseEvent, QPalette, @@ -2538,32 +2538,18 @@ def replaceText(self) -> str: # Getters ## - def getSearchObject(self) -> str | QRegularExpression | QRegExp: + def getSearchObject(self) -> str | QRegularExpression: """Return the current search text either as text or as a regular expression object. """ text = self.searchBox.text() if CONFIG.searchRegEx: - # Using the Unicode-capable QRegularExpression class was - # only added in Qt 5.13. Otherwise, 5.3 and up supports - # only the QRegExp class. - if CONFIG.verQtValue >= 0x050d00: - rxOpt = QRegularExpression.PatternOption.UseUnicodePropertiesOption - if not CONFIG.searchCase: - rxOpt |= QRegularExpression.PatternOption.CaseInsensitiveOption - regEx = QRegularExpression(text, rxOpt) - self._alertSearchValid(regEx.isValid()) - return regEx - else: # pragma: no cover - # >= 50300 to < 51300 - if CONFIG.searchCase: - rxOpt = Qt.CaseSensitivity.CaseSensitive - else: - rxOpt = Qt.CaseSensitivity.CaseInsensitive - regEx = QRegExp(text, rxOpt) - self._alertSearchValid(regEx.isValid()) - return regEx - + rxOpt = QRegularExpression.PatternOption.UseUnicodePropertiesOption + if not CONFIG.searchCase: + rxOpt |= QRegularExpression.PatternOption.CaseInsensitiveOption + regEx = QRegularExpression(text, rxOpt) + self._alertSearchValid(regEx.isValid()) + return regEx return text ## diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index ed4d9ba6c..b82c5d52e 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -209,9 +209,8 @@ def _toggleHideInactive(self, state: bool) -> None: def _updateTabVisibility(self) -> None: """Hide class tabs with no content.""" - if CONFIG.verQtValue >= 0x050f00: - for tClass, cTab in self.kwTabs.items(): - self.mainTabs.setTabVisible(self.idTabs[tClass], cTab.countEntries() > 0) + for tClass, cTab in self.kwTabs.items(): + self.mainTabs.setTabVisible(self.idTabs[tClass], cTab.countEntries() > 0) return def _loadAllTags(self) -> None: diff --git a/pkgutils.py b/pkgutils.py index fb6bf1b67..328fe78a4 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -33,6 +33,8 @@ import subprocess import email.utils +from pathlib import Path + OS_NONE = 0 OS_LINUX = 1 OS_WIN = 2 @@ -54,16 +56,15 @@ def getValue(text: str) -> str: numVers = "0" hexVers = "0x0" relDate = "Unknown" - initFile = os.path.join("novelwriter", "__init__.py") + initFile = Path("novelwriter") / "__init__.py" try: - with open(initFile, mode="r", encoding="utf-8") as inFile: - for aLine in inFile: - if aLine.startswith("__version__"): - numVers = getValue((aLine)) - if aLine.startswith("__hexversion__"): - hexVers = getValue((aLine)) - if aLine.startswith("__date__"): - relDate = getValue((aLine)) + for aLine in initFile.read_text(encoding="utf-8").splitlines(): + if aLine.startswith("__version__"): + numVers = getValue((aLine)) + if aLine.startswith("__hexversion__"): + hexVers = getValue((aLine)) + if aLine.startswith("__date__"): + relDate = getValue((aLine)) except Exception as exc: print("Could not read file: %s" % initFile) print(str(exc)) @@ -88,26 +89,23 @@ def stripVersion(version: str) -> str: def readFile(fileName: str) -> str: """Read an entire file and return as a string.""" - with open(fileName, mode="r", encoding="utf-8") as inFile: - return inFile.read() + return Path(fileName).read_text(encoding="utf-8") -def writeFile(fileName: str, writeText: str) -> None: +def writeFile(fileName: str, text: str) -> None: """Write string to file.""" - with open(fileName, mode="w+", encoding="utf-8") as outFile: - outFile.write(writeText) + Path(fileName).write_text(text, encoding="utf-8") + return -def toUpload(srcPath: str, dstName: str | None = None) -> None: +def toUpload(srcPath: str | Path, dstName: str | None = None) -> None: """Copy a file produced by one of the build functions to the upload directory. The file can optionally be given a new name. """ - uplDir = "dist_upload" - if not os.path.isdir(uplDir): - os.mkdir(uplDir) - if dstName is None: - dstName = os.path.basename(srcPath) - shutil.copyfile(srcPath, os.path.join(uplDir, dstName)) + uplDir = Path("dist_upload") + uplDir.mkdir(exist_ok=True) + srcPath = Path(srcPath) + shutil.copyfile(srcPath, uplDir / (dstName or srcPath.name)) return @@ -561,43 +559,24 @@ def importI18nUpdates(sysArgs: list[str]) -> None: # Make Minimal Package (minimal-zip) ## -def makeMinimalPackage(targetOS: int) -> None: +def makeWindowsZip() -> None: """Pack the core source file in a single zip file.""" from zipfile import ZipFile, ZIP_DEFLATED print("") - print("Building Minimal ZIP File") + print("Building Windows ZIP File") print("=========================") bldDir = "dist_minimal" if not os.path.isdir(bldDir): os.mkdir(bldDir) - if targetOS == OS_LINUX: - targName = "-linux" - print("Target OS: Linux") - elif targetOS == OS_DARWIN: - targName = "-darwin" - print("Target OS: Darwin") - elif targetOS == OS_WIN: - targName = "-win" - print("Target OS: Windows") - else: - targName = "" - print("") - - # Check Additional Assets - # ======================= - if not checkAssetsExist(): print("ERROR: Missing build assets") sys.exit(1) - # Build Minimal Zip - # ================= - pkgVers, _, _ = extractVersion() - zipFile = f"novelwriter-{pkgVers}-minimal{targName}.zip" + zipFile = f"novelwriter-{pkgVers}-minimal-win.zip" outFile = os.path.join(bldDir, zipFile) if os.path.isfile(outFile): os.unlink(outFile) @@ -627,26 +606,8 @@ def makeMinimalPackage(targetOS: int) -> None: continue zipObj.write(os.path.join(nRoot, aFile)) - if targetOS == OS_WIN: - zipObj.write("novelWriter.py", "novelWriter.pyw") - print("Added: novelWriter.pyw") - zipObj.write(os.path.join("setup", "windows_install.bat"), "windows_install.bat") - print("Added: windows_install.bat") - zipObj.write(os.path.join("setup", "windows_uninstall.bat"), "windows_uninstall.bat") - print("Added: windows_uninstall.bat") - - else: # Linux and Mac - # Add icons - for nRoot, _, nFiles in os.walk(os.path.join("setup", "data")): - print("Added: %s/* [Files: %d]" % (nRoot, len(nFiles))) - for aFile in nFiles: - zipObj.write(os.path.join(nRoot, aFile)) - - zipObj.write(os.path.join("setup", "description_pypi.md")) - print("Added: setup/description_pypi.md") - - zipObj.write("novelWriter.py") - print("Added: novelWriter.py") + zipObj.write("novelWriter.py", "novelWriter.pyw") + print("Added: novelWriter.pyw") for aFile in rootFiles: print("Added: %s" % aFile) @@ -658,14 +619,9 @@ def makeMinimalPackage(targetOS: int) -> None: print("") print("Created File: %s" % outFile) - # Create Checksum File - # ==================== - shaFile = makeCheckSum(zipFile, cwd=bldDir) - toUpload(outFile) toUpload(shaFile) - print("") return @@ -780,19 +736,28 @@ def makeDebianPackage( )) print("Wrote: setup.py") - setupCfg = readFile("pyproject.toml").replace( - "setup/description_pypi.md", "data/description_short.txt" - ) - writeFile(f"{outDir}/pyproject.toml", setupCfg) - print("Wrote: pyproject.toml") - if oldSetuptools: + # This is needed for Ubuntu up to 22.04 setupCfg = readFile("setup/launchpad_setup.cfg").replace( "file: setup/description_pypi.md", "file: data/description_short.txt" ) writeFile(f"{outDir}/setup.cfg", setupCfg) print("Wrote: setup.cfg") + writeFile(f"{outDir}/pyproject.toml", ( + "[build-system]\n" + "requires = [\"setuptools\"]\n" + "build-backend = \"setuptools.build_meta\"\n" + )) + print("Wrote: pyproject.toml") + + else: + pyProject = readFile("pyproject.toml").replace( + "setup/description_pypi.md", "data/description_short.txt" + ) + writeFile(f"{outDir}/pyproject.toml", pyProject) + print("Wrote: pyproject.toml") + # Copy/Write Debian Files # ======================= @@ -869,7 +834,6 @@ def makeForLaunchpad(doSign: bool = False, isFirst: bool = False) -> None: bldNum = "0" distLoop = [ - ("20.04", "focal", True), ("22.04", "jammy", True), ("23.10", "mantic", False), ("24.04", "noble", False), @@ -877,7 +841,7 @@ def makeForLaunchpad(doSign: bool = False, isFirst: bool = False) -> None: print("Building Ubuntu packages for:") print("") - for distNum, codeName, oldSetup in distLoop: + for distNum, codeName, _ in distLoop: print(f" * Ubuntu {distNum} {codeName.title()}") print("") @@ -1565,226 +1529,6 @@ def xdgUninstall() -> None: return -## -# WIN Installation (win-install) -## - -def winInstall() -> None: - """Will attempt to install icons and make a launcher for Windows.""" - if sys.platform != "win32": - raise Exception("This method only runs on Windows") - - import winreg - try: - import win32com.client - except ImportError: - print( - "ERROR: Package 'pywin32' is missing on this system.\n" - " Please run 'pkgutils.py pip' to automatically install\n" - " dependecies, or run 'pip install --user pywin32'." - ) - sys.exit(1) - - print("") - print("Windows Install") - print("===============") - print("") - - numVers, hexVers, _ = extractVersion() - nwTesting = not hexVers[-2] == "f" - wShell = win32com.client.Dispatch("WScript.Shell") - - if nwTesting: - linkName = "novelWriter Testing %s.lnk" % numVers - else: - linkName = "novelWriter %s.lnk" % numVers - - desktopDir = wShell.SpecialFolders("Desktop") - desktopIcon = os.path.join(desktopDir, linkName) - - startMenuDir = wShell.SpecialFolders("StartMenu") - startMenuProg = os.path.join(startMenuDir, "Programs", "novelWriter") - startMenuIcon = os.path.join(startMenuProg, linkName) - - pythonDir = os.path.dirname(sys.executable) - pythonExe = os.path.join(pythonDir, "pythonw.exe") - - targetDir = os.path.abspath(os.path.dirname(__file__)) - targetPy = os.path.join(targetDir, "novelWriter.pyw") - targetIcon = os.path.join(targetDir, "novelwriter", "assets", "icons", "novelwriter.ico") - - if not os.path.isfile(targetPy): - shutil.copy2(os.path.join(targetDir, "novelWriter.py"), targetPy) - - print("") - print("Collecting Info ...") - print("Desktop Folder: %s" % desktopDir) - print("Start Menu Folder: %s" % startMenuDir) - print("Python Executable: %s" % pythonExe) - print("Target Executable: %s" % targetPy) - print("Target Icon: %s" % targetIcon) - print("") - - print("Creating Links ...") - if os.path.isfile(desktopIcon): - os.unlink(desktopIcon) - print("Deleted: %s" % desktopIcon) - - if os.path.isdir(startMenuProg): - for oldIcon in os.listdir(startMenuProg): - oldPath = os.path.join(startMenuProg, oldIcon) - if not oldIcon.startswith("novelWriter"): - continue - - isTesting = oldIcon.startswith("novelWriter Testing") - if isTesting and nwTesting: - os.unlink(oldPath) - print("Deleted: %s" % oldPath) - if not isTesting and not nwTesting: - os.unlink(oldPath) - print("Deleted: %s" % oldPath) - - else: - os.mkdir(startMenuProg) - print("Created: %s" % startMenuProg) - - wShortcut = wShell.CreateShortCut(desktopIcon) - wShortcut.TargetPath = targetPy - wShortcut.WorkingDirectory = targetDir - wShortcut.IconLocation = targetIcon - wShortcut.WindowStyle = 1 - wShortcut.save() - print("Created: %s" % desktopIcon) - - wShortcut = wShell.CreateShortCut(startMenuIcon) - wShortcut.TargetPath = targetPy - wShortcut.WorkingDirectory = targetDir - wShortcut.IconLocation = targetIcon - wShortcut.WindowStyle = 1 - wShortcut.save() - print("Created: %s" % startMenuIcon) - - print("") - print("Creating registry keys ...") - - def setKey(kPath: str, kName: str, kVal: str) -> None: - winreg.CreateKey(winreg.HKEY_CURRENT_USER, kPath) - regKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, kPath, 0, winreg.KEY_WRITE) - winreg.SetValueEx(regKey, kName, 0, winreg.REG_SZ, kVal) - winreg.CloseKey(regKey) - - mimeIcon = os.path.join( - targetDir, "novelwriter", "assets", "icons", "x-novelwriter-project.ico" - ) - mimeExec = '"%s" "%s" "%%1"' % (pythonExe, targetPy) - - try: - setKey(r"Software\Classes\.nwx\OpenWithProgids", "novelWriterProject.nwx", "") - setKey(r"Software\Classes\novelWriterProject.nwx", "", "novelWriter Project File") - setKey(r"Software\Classes\novelWriterProject.nwx\DefaultIcon", "", mimeIcon) - setKey(r"Software\Classes\novelWriterProject.nwx\shell\open\command", "", mimeExec) - setKey(r"Software\Classes\Applications\novelWriter.pyw\SupportedTypes", ".nwx", "") - except WindowsError: - print("ERROR: Failed to set registry keys.") - print("") - - print("") - print("Done!") - print("") - - return - - -## -# WIN Uninstallation (win-uninstall) -## - -def winUninstall() -> None: - """Will attempt to uninstall icons previously installed.""" - if sys.platform != "win32": - raise Exception("This method only runs on Windows") - - import winreg - try: - import win32com.client - except ImportError: - print( - "ERROR: Package 'pywin32' is missing on this system.\n" - " Please run 'pip install --user pywin32' to install it." - ) - sys.exit(1) - - print("") - print("Windows Uninstall") - print("=================") - print("") - - numVers, hexVers, _ = extractVersion() - nwTesting = not hexVers[-2] == "f" - wShell = win32com.client.Dispatch("WScript.Shell") - - if nwTesting: - linkName = "novelWriter Testing %s.lnk" % numVers - else: - linkName = "novelWriter %s.lnk" % numVers - - desktopDir = wShell.SpecialFolders("Desktop") - desktopIcon = os.path.join(desktopDir, linkName) - - startMenuDir = wShell.SpecialFolders("StartMenu") - startMenuProg = os.path.join(startMenuDir, "Programs", "novelWriter") - startMenuIcon = os.path.join(startMenuProg, linkName) - - print("") - print("Deleting Links ...") - if os.path.isfile(desktopIcon): - os.unlink(desktopIcon) - print("Deleted: %s" % desktopIcon) - else: - print("Not Found: %s" % desktopIcon) - - if os.path.isfile(startMenuIcon): - os.unlink(startMenuIcon) - print("Deleted: %s" % startMenuIcon) - else: - print("Not Found: %s" % startMenuIcon) - - if os.path.isdir(startMenuProg): - if not os.listdir(startMenuProg): - os.rmdir(startMenuProg) - print("Deleted: %s" % startMenuProg) - else: - print("Not Empty: %s" % startMenuProg) - - print("") - print("Removing registry keys ...") - - keys = [ - r"Software\Classes\novelWriterProject.nwx\shell\open\command", - r"Software\Classes\novelWriterProject.nwx\shell\open", - r"Software\Classes\novelWriterProject.nwx\shell", - r"Software\Classes\novelWriterProject.nwx\DefaultIcon", - r"Software\Classes\novelWriterProject.nwx", - r"Software\Classes\.nwx\OpenWithProgids", - r"Software\Classes\.nwx", - r"Software\Classes\Applications\novelWriter.pyw\SupportedTypes", - r"Software\Classes\Applications\novelWriter.pyw", - ] - - for aKey in keys: - try: - winreg.DeleteKey(winreg.HKEY_CURRENT_USER, aKey) - print("Deleted: HKEY_CURRENT_USER\\%s" % aKey) - except WindowsError: - print("Not Found: HKEY_CURRENT_USER\\%s" % aKey) - - print("") - print("Done!") - print("") - - return - - # =============================================================================================== # # Process Command Line # =============================================================================================== # @@ -1805,19 +1549,6 @@ def winUninstall() -> None: sysArgs = sys.argv.copy() - # Set Target OS - if "--target-linux" in sysArgs: - sysArgs.remove("--target-linux") - targetOS = OS_LINUX - elif "--target-darwin" in sysArgs: - sysArgs.remove("--target-darwin") - targetOS = OS_DARWIN - elif "--target-win" in sysArgs: - sysArgs.remove("--target-win") - targetOS = OS_WIN - else: - targetOS = hostOS - # Sign package if "--sign" in sysArgs: sysArgs.remove("--sign") @@ -1841,10 +1572,6 @@ def winUninstall() -> None: "novelWriter as a package on Linux, Mac and Windows. The available options", "are as follows:", "", - "Some of the commands can be targeted towards a different OS than the host OS.", - "To target the command, add one of '--target-linux', '--target-darwin' or", - "'--target-win'.", - "", "General:", "", " help Print the help message.", @@ -1865,10 +1592,8 @@ def winUninstall() -> None: "Python Packaging:", "", " import-i18n Import updated i18n files from a zip file.", - " minimal-zip Creates a minimal zip file of the core application without", - " all the other source files. Defaults to tailor the zip file", - " for the current OS, but accepts a target OS flag to build", - " for another OS.", + " windows-zip Creates a minimal zip file of the core application without", + " all the other source files. Used for Windows builds.", " build-deb Build a .deb package for Debian and Ubuntu. Add --sign to ", " sign package.", " build-ubuntu Build a .deb packages Launchpad. Add --sign to ", @@ -1880,17 +1605,11 @@ def winUninstall() -> None: "", "System Install:", "", - " install Deprecated. Use pip or build.", " xdg-install Install launcher and icons for freedesktop systems. Run as", " root or with sudo for system-wide install, or as user for", " single user install.", " xdg-uninstall Remove the launcher and icons for the current system as", " installed by the 'xdg-install' command.", - " win-install Install desktop icon, start menu icon, and registry entries", - " for file association with .nwx files for Windows systems.", - " win-uninstall Remove desktop icon, start menu icon, and registry keys,", - " for the current system. Note that it only removes icons for", - " the version number of the package the command is run from.", "", ] @@ -1951,9 +1670,9 @@ def winUninstall() -> None: importI18nUpdates(sysArgs) sys.exit(0) # Don't continue execution - if "minimal-zip" in sysArgs: - sysArgs.remove("minimal-zip") - makeMinimalPackage(targetOS) + if "windows-zip" in sysArgs: + sysArgs.remove("windows-zip") + makeWindowsZip() if "build-deb" in sysArgs: sysArgs.remove("build-deb") @@ -2007,20 +1726,4 @@ def winUninstall() -> None: else: xdgUninstall() - if "win-install" in sysArgs: - sysArgs.remove("win-install") - if hostOS == OS_WIN: - winInstall() - else: - print("ERROR: Command 'win-install' can only be used on Windows") - sys.exit(1) - - if "win-uninstall" in sysArgs: - sysArgs.remove("win-uninstall") - if hostOS == OS_WIN: - winUninstall() - else: - print("ERROR: Command 'win-uninstall' can only be used on Windows") - sys.exit(1) - # END Main diff --git a/pyproject.toml b/pyproject.toml index b6d40d9d8..de8d28ef9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "pyqt5>=5.10", + "pyqt5>=5.15", "pyenchant>=3.0.0", ] dynamic = ["version"] diff --git a/requirements.txt b/requirements.txt index af56101a3..b6d303108 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -pyqt5>=5.10 +pyqt5>=5.15 pyenchant>=3.0.0 diff --git a/setup/debian/control b/setup/debian/control index 556a50517..10481e32d 100644 --- a/setup/debian/control +++ b/setup/debian/control @@ -2,14 +2,14 @@ Source: novelwriter Maintainer: Veronica Berglyd Olsen Section: text Priority: optional -Build-Depends: dh-python, python3-setuptools, python3-all, debhelper (>= 9), python3 (>=3.8), python3-pyqt5 (>= 5.10), python3-enchant (>= 2.0) +Build-Depends: dh-python, python3-setuptools, python3-all, debhelper (>= 9), python3 (>=3.8), python3-pyqt5 (>= 5.15), python3-enchant (>= 2.0) Standards-Version: 4.5.1 Homepage: https://novelwriter.io X-Python3-Version: >= 3.8 Package: novelwriter Architecture: all -Depends: ${misc:Depends}, ${python3:Depends}, python3 (>=3.8), python3-pyqt5 (>= 5.10), python3-enchant (>= 2.0) +Depends: ${misc:Depends}, ${python3:Depends}, python3 (>=3.8), python3-pyqt5 (>= 5.15), python3-enchant (>= 2.0) Description: A markdown-like text editor for planning and writing novels novelWriter is a plain text editor designed for writing novels assembled from many smaller text documents. It uses a minimal formatting syntax inspired by diff --git a/setup/launchpad_setup.cfg b/setup/launchpad_setup.cfg index df0af65a3..a2757b712 100644 --- a/setup/launchpad_setup.cfg +++ b/setup/launchpad_setup.cfg @@ -34,7 +34,7 @@ python_requires = >=3.8 include_package_data = True packages = find_namespace: install_requires = - pyqt5>=5.10 + pyqt5>=5.15 pyenchant>=3.0.0 [options.packages.find] diff --git a/setup/make_release.sh b/setup/make_release.sh index ae4659095..0904000f4 100755 --- a/setup/make_release.sh +++ b/setup/make_release.sh @@ -22,12 +22,10 @@ python3 pkgutils.py qtlrelease manual sample deactivate echo "" -echo " Building Minimal Packages" +echo " Building Windows Source Zip" echo "================================================================================" echo "" -python3 pkgutils.py minimal-zip --target-win -# python3 pkgutils.py minimal-zip --target-linux -# python3 pkgutils.py minimal-zip --target-darwin +python3 pkgutils.py windows-zip echo "" echo " Building Linux Packages" diff --git a/setup/windows_install.bat b/setup/windows_install.bat deleted file mode 100644 index b27d6640d..000000000 --- a/setup/windows_install.bat +++ /dev/null @@ -1,49 +0,0 @@ -@echo off - -:: Check for Python Installation -echo. -echo Looking for Python -python --version 2>NUL -if errorlevel 1 goto errorNoPython -echo Python found. OK. - -if exist pkgutils.py ( - echo pkgutils.py found. OK. -) else ( - cd .. - if exist pkgutils.py ( - echo pkgutils.py found. OK. - ) else ( - goto errorNoSetup - ) -) -echo. - -:: Install the PyQt5 and pyenchant dependencies -pip install --user pywin32 -r requirements.txt - -:: Create the desktop and start menu icons -python pkgutils.py win-install - -pause -goto:eof - -:errorNoPython -echo. -echo ERROR^: Python is not installed on your system, or cannot be found. -echo. -echo Please download and install it from https://www.python.org/downloads/ -echo Also make sure the "Add Python to PATH" option is selected during installation. -echo. - -pause -goto:eof - -:errorNoSetup -echo. -echo ERROR^: Could not find the pkgutils.py script. -echo. -echo Make sure you run pkgutils.py from the novelWriter root folder. -echo. - -pause diff --git a/setup/windows_uninstall.bat b/setup/windows_uninstall.bat deleted file mode 100644 index d96b7863d..000000000 --- a/setup/windows_uninstall.bat +++ /dev/null @@ -1,59 +0,0 @@ -@echo off - -:: Check for Python Installation -echo. -echo Looking for Python -python --version 2>NUL -if errorlevel 1 goto errorNoPython -echo Python found. OK. - -if exist pkgutils.py ( - echo pkgutils.py found. OK. -) else ( - cd .. - if exist pkgutils.py ( - echo pkgutils.py found. OK. - ) else ( - goto errorNoSetup - ) -) -echo. - -echo Ready to remove the novelWriter icons, registry keys, and package dependencies. -set /P c=Are you sure you want to continue [y/n]? -if /I "%c%" EQU "y" goto doUninst -goto afterUninst - -:doUninst - -:: Remove the desktop and start menu icons -python pkgutils.py win-uninstall - -:: Remove the PyQt5 and pyenchant dependencies -pip uninstall --yes -r requirements.txt - -:afterUninst - -echo. -pause -goto:eof - -:errorNoPython -echo. -echo ERROR^: Python is not installed on your system, or cannot be found. -echo. -echo Please download and install it from https://www.python.org/downloads/ -echo Also make sure the "Add Python to PATH" option is selected during installation. -echo. - -pause -goto:eof - -:errorNoSetup -echo. -echo ERROR^: Could not find the pkgutils.py script. -echo. -echo Make sure you run pkgutils.py from the novelWriter root folder. -echo. - -pause diff --git a/tests/test_core/test_core_coretools.py b/tests/test_core/test_core_coretools.py index 7c68bb7d9..0dd0f1136 100644 --- a/tests/test_core/test_core_coretools.py +++ b/tests/test_core/test_core_coretools.py @@ -427,14 +427,7 @@ def testCoreTools_DocSearch(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText): # ======== # Escape Using QRegularExpression - with monkeypatch.context() as mp: - mp.setattr(CONFIG, "verQtValue", 0x050f00) - assert search._buildPattern("[A-Za-z0-9_]+") == r"\[A\-Za\-z0\-9_\]\+" - - # Escape Using Custom Implementation - with monkeypatch.context() as mp: - mp.setattr(CONFIG, "verQtValue", 0x050d00) - assert search._buildPattern("[A-Za-z0-9_]+") == r"\[A\-Za\-z0\-9_\]\+" + assert search._buildPattern("[A-Za-z0-9_]+") == r"\[A\-Za\-z0\-9_\]\+" # Whole Words search.setWholeWords(True) diff --git a/tests/test_gui/test_gui_docviewerpanel.py b/tests/test_gui/test_gui_docviewerpanel.py index b194a52ff..9b35bfe43 100644 --- a/tests/test_gui/test_gui_docviewerpanel.py +++ b/tests/test_gui/test_gui_docviewerpanel.py @@ -26,7 +26,7 @@ from PyQt5.QtGui import QIcon -from novelwriter import CONFIG, SHARED +from novelwriter import SHARED from novelwriter.constants import nwLists from novelwriter.core.item import NWItem from novelwriter.dialogs.editlabel import GuiEditLabel @@ -151,14 +151,13 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd nwGUI.docEditor._processTag(cursor, create=True) # Check Panel Tab Visibility - if CONFIG.verQtValue >= 0x050f00: - assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["CHARACTER"]) is True - assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["PLOT"]) is False - assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["WORLD"]) is False - assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["TIMELINE"]) is False - assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["OBJECT"]) is False - assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["ENTITY"]) is False - assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["CUSTOM"]) is False + assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["CHARACTER"]) is True + assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["PLOT"]) is False + assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["WORLD"]) is False + assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["TIMELINE"]) is False + assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["OBJECT"]) is False + assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["ENTITY"]) is False + assert viewPanel.mainTabs.isTabVisible(viewPanel.idTabs["CUSTOM"]) is False # Check Character Tab charTab = viewPanel.kwTabs["CHARACTER"]