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)