From f8d57539d4ab15f1aa7c7e3e96d560ef3993ffce Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 14 Feb 2024 18:32:09 +0100
Subject: [PATCH] Add a version info widget
---
novelwriter/dialogs/about.py | 42 ++------
novelwriter/extensions/configlayout.py | 17 ++--
novelwriter/extensions/versioninfo.py | 131 +++++++++++++++++++++++++
novelwriter/tools/welcome.py | 12 +--
4 files changed, 152 insertions(+), 50 deletions(-)
create mode 100644 novelwriter/extensions/versioninfo.py
diff --git a/novelwriter/dialogs/about.py b/novelwriter/dialogs/about.py
index 4b205441..3cd6abc3 100644
--- a/novelwriter/dialogs/about.py
+++ b/novelwriter/dialogs/about.py
@@ -25,8 +25,6 @@ from __future__ import annotations
import logging
-from datetime import datetime
-
from PyQt5.QtGui import QCloseEvent, QColor
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
@@ -34,10 +32,10 @@ from PyQt5.QtWidgets import (
QWidget
)
-from novelwriter import CONFIG, SHARED, __version__, __date__
-from novelwriter.common import formatVersion, readTextFile
-from novelwriter.constants import nwConst, nwUnicode
+from novelwriter import CONFIG, SHARED
+from novelwriter.common import readTextFile
from novelwriter.extensions.configlayout import NColourLabel
+from novelwriter.extensions.versioninfo import VersionInfoWidget
logger = logging.getLogger(__name__)
@@ -68,12 +66,7 @@ class GuiAbout(QDialog):
self.nwLabel = QLabel(self)
self.nwLabel.setPixmap(self.nwImage)
- self.nwInfo = QLabel(self.tr("Version {0} {1} Released on {2} {1} {3}").format(
- formatVersion(__version__), nwUnicode.U_ENDASH,
- datetime.strptime(__date__, "%Y-%m-%d").strftime("%x"),
- "{1}".format(nwConst.URL_RELEASES, self.tr("Release Notes"))
- ))
- self.nwInfo.setOpenExternalLinks(True)
+ self.nwInfo = VersionInfoWidget(self)
self.nwLicence = QLabel(self.tr("This application is licenced under {0}".format(
"GPL v3.0"
@@ -82,8 +75,7 @@ class GuiAbout(QDialog):
# Credits
self.lblCredits = NColourLabel(
- self.tr("Credits"), SHARED.theme.helpText,
- scale=NColourLabel.HEADER_SCALE, parent=self
+ self.tr("Credits"), scale=1.6, parent=self, bold=True
)
self.txtCredits = QTextBrowser(self)
@@ -100,6 +92,7 @@ class GuiAbout(QDialog):
self.innerBox.addSpacing(hB)
self.innerBox.addWidget(self.nwLabel)
self.innerBox.addWidget(self.nwInfo)
+ self.innerBox.addSpacing(hA)
self.innerBox.addWidget(self.nwLicence)
self.innerBox.addSpacing(hA)
self.innerBox.addWidget(self.lblCredits)
@@ -156,33 +149,10 @@ class GuiAbout(QDialog):
def _setStyleSheet(self) -> None:
"""Set stylesheet for all browser tabs."""
- colHead = SHARED.theme.colHead
- colKey = SHARED.theme.colKey
- styleSheet = (
- "h1, h2, h3, h4 {{"
- " color: rgb({hColR}, {hColG}, {hColB});"
- "}}\n"
- "a {{"
- " color: rgb({hColR}, {hColG}, {hColB});"
- "}}\n"
- ".alt {{"
- " color: rgb({kColR}, {kColG}, {kColB});"
- "}}\n"
- ).format(
- hColR=colHead.red(),
- hColG=colHead.green(),
- hColB=colHead.blue(),
- kColR=colKey.red(),
- kColG=colKey.green(),
- kColB=colKey.blue(),
- )
- self.txtCredits.document().setDefaultStyleSheet(styleSheet)
-
baseCol = self.palette().window().color()
self.txtCredits.setStyleSheet((
"QTextBrowser {{border: none; background: rgb({r},{g},{b});}} "
).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue()))
-
return
# END Class GuiAbout
diff --git a/novelwriter/extensions/configlayout.py b/novelwriter/extensions/configlayout.py
index fb4cd37d..aa168778 100644
--- a/novelwriter/extensions/configlayout.py
+++ b/novelwriter/extensions/configlayout.py
@@ -26,7 +26,7 @@ along with this program. If not, see .
"""
from __future__ import annotations
-from PyQt5.QtGui import QColor, QPalette
+from PyQt5.QtGui import QColor, QFont, QPalette
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QAbstractButton, QFrame, QHBoxLayout, QLabel, QLayout, QScrollArea,
@@ -198,7 +198,7 @@ class NScrollableForm(QScrollArea):
if helpText:
qHelp = NColourLabel(
- str(helpText), self._helpCol, parent=self,
+ str(helpText), color=self._helpCol, parent=self,
scale=self._fontScale, wrap=True, indent=self._indent
)
labelBox = QVBoxLayout()
@@ -249,16 +249,19 @@ class NColourLabel(QLabel):
HELP_SCALE = DEFAULT_SCALE
HEADER_SCALE = 1.25
- def __init__(self, text: str, color: QColor, parent: QWidget | None = None,
- scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0) -> None:
+ def __init__(self, text: str, color: QColor | None = None, parent: QWidget | None = None,
+ scale: float = HELP_SCALE, wrap: bool = False, indent: int = 0,
+ bold: bool = False) -> None:
super().__init__(text, parent=parent)
font = self.font()
font.setPointSizeF(scale*font.pointSizeF())
- colour = self.palette()
- colour.setColor(QPalette.WindowText, color)
+ font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal)
+ if color:
+ colour = self.palette()
+ colour.setColor(QPalette.WindowText, color)
+ self.setPalette(colour)
- self.setPalette(colour)
self.setFont(font)
self.setIndent(indent)
self.setWordWrap(wrap)
diff --git a/novelwriter/extensions/versioninfo.py b/novelwriter/extensions/versioninfo.py
new file mode 100644
index 00000000..09321e3e
--- /dev/null
+++ b/novelwriter/extensions/versioninfo.py
@@ -0,0 +1,131 @@
+"""
+novelWriter – Custom Widget: Version Info
+=========================================
+
+File History:
+Created: 2024-02-14 [2.3b1] VersionInfoWidget
+
+This file is a part of novelWriter
+Copyright 2018–2024, Veronica Berglyd Olsen
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+from __future__ import annotations
+
+import json
+import logging
+
+from random import shuffle
+from datetime import datetime
+from urllib.request import Request, urlopen
+
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtCore import QUrl, pyqtSlot
+from PyQt5.QtWidgets import QLabel, QVBoxLayout, QWidget
+
+from novelwriter import CONFIG, __version__, __date__, __domain__
+from novelwriter.common import formatVersion
+from novelwriter.constants import nwConst
+
+logger = logging.getLogger(__name__)
+
+LOOKUPS = [
+ "https://novelwriter.io/release-latest.json",
+ "https://vkbo.github.io/novelWriter.io/release-latest.json",
+ "https://raw.githubusercontent.com/vkbo/novelWriter.io/main/source/_extra/release-latest.json",
+]
+
+
+class VersionInfoWidget(QWidget):
+
+ def __init__(self, parent: QWidget) -> None:
+ super().__init__(parent=parent)
+
+ # Labels
+ self._lblInfo = QLabel("{0} {1} \u2013 {2} {3} \u2013 {4}".format(
+ self.tr("Version"), formatVersion(__version__),
+ self.tr("Released on"), datetime.strptime(__date__, "%Y-%m-%d").strftime("%x"),
+ "{0}".format(self.tr("Release Notes")),
+ ), self)
+ self._lblInfo.linkActivated.connect(self._processLink)
+ self._lblRelease = QLabel(self.tr("Latest Version: {0}").format(
+ "{0}".format(self.tr("Check Now"))
+ ), self)
+ self._lblRelease.linkActivated.connect(self._processLink)
+
+ # New Release
+ self._trRelease = self.tr("Latest Version: {0} {1} Download from {2}")
+ self._trFail = self.tr("Could not retrieve version information.")
+
+ # Assemble
+ self._layout = QVBoxLayout()
+ self._layout.addWidget(self._lblInfo)
+ self._layout.addWidget(self._lblRelease)
+ self._layout.setSpacing(CONFIG.pxInt(2))
+ self._layout.setContentsMargins(0, 0, 0, 0)
+
+ self.setLayout(self._layout)
+
+ return
+
+ ##
+ # Private Slots
+ ##
+
+ @pyqtSlot(str)
+ def _processLink(self, link: str) -> None:
+ """Process an activated link."""
+ if link == "#notes":
+ QDesktopServices.openUrl(QUrl(nwConst.URL_RELEASES))
+ elif link == "#website":
+ QDesktopServices.openUrl(QUrl(nwConst.URL_WEB))
+ elif link == "#update":
+ shuffle(LOOKUPS)
+ for url in LOOKUPS:
+ if result := self._pullJson(url):
+ self._updateReleaseInfo(result)
+ break
+ else:
+ self._updateReleaseInfo({})
+ return
+
+ ##
+ # Internal Functions
+ ##
+
+ def _updateReleaseInfo(self, result: dict) -> None:
+ """Update the widget release info."""
+ if version := result.get("version"):
+ download = f"{__domain__}"
+ self._lblRelease.setText(self._trRelease.format(version, "\u2013", download))
+ else:
+ self._lblRelease.setText(self._trFail)
+ return
+
+ def _pullJson(self, url: str) -> dict | None:
+ """Pull a JSON file from a URL."""
+ urlReq = Request(url)
+ urlReq.add_header("User-Agent", nwConst.USER_AGENT)
+ urlReq.add_header("Accept", "application/json")
+
+ try:
+ logger.info("Contacting: %s", url)
+ urlData = urlopen(urlReq, timeout=10)
+ return json.loads(urlData.read().decode()).get("release")
+ except Exception:
+ logger.error("Failed to retrieve data")
+
+ return None
+
+# END Class VersionInfoWidget
diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py
index 25386771..d0085f38 100644
--- a/novelwriter/tools/welcome.py
+++ b/novelwriter/tools/welcome.py
@@ -40,13 +40,14 @@ from PyQt5.QtWidgets import (
QToolButton, QVBoxLayout, QWidget, qApp
)
-from novelwriter import CONFIG, SHARED, __version__, __date__
+from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemClass
-from novelwriter.common import formatInt, formatVersion, makeFileNameSafe
-from novelwriter.constants import nwFiles, nwUnicode
+from novelwriter.common import formatInt, makeFileNameSafe
+from novelwriter.constants import nwFiles
from novelwriter.core.coretools import ProjectBuilder
from novelwriter.extensions.switch import NSwitch
from novelwriter.extensions.modified import NComboBox, NSpinBox
+from novelwriter.extensions.versioninfo import VersionInfoWidget
logger = logging.getLogger(__name__)
@@ -90,10 +91,7 @@ class GuiWelcome(QDialog):
self.nwLabel = QLabel(self)
self.nwLabel.setPixmap(self.nwImage)
- self.nwInfo = QLabel(self.tr("Version {0} {1} Released on {2}").format(
- formatVersion(__version__), nwUnicode.U_ENDASH,
- datetime.strptime(__date__, "%Y-%m-%d").strftime("%x")
- ))
+ self.nwInfo = VersionInfoWidget(self)
self.tabOpen = _OpenProjectPage(self)
self.tabOpen.openProjectRequest.connect(self._openProjectPath)