diff --git a/LogarithmPlotter/config.py b/LogarithmPlotter/config.py
new file mode 100644
index 0000000..0e0a2bf
--- /dev/null
+++ b/LogarithmPlotter/config.py
@@ -0,0 +1,89 @@
+"""
+ * LogarithmPlotter - Create graphs with logarithm scales.
+ * Copyright (C) 2021 Ad5001
+ *
+ * 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 os import path, environ, makedirs
+from platform import system
+from json import load, dumps
+
+DEFAULT_SETTINGS = {
+ "check_for_updates": True,
+ "last_install_greet": "0",
+ "lang": "en"
+}
+
+# Create config directory
+CONFIG_PATH = {
+ "Linux": path.join(environ["XDG_CONFIG_HOME"], "LogarithmPlotter") if "XDG_CONFIG_HOME" in environ else path.join(path.expanduser("~"), ".config", "LogarithmPlotter"),
+ "Windows": path.join(path.expandvars('%APPDATA%'), "LogarithmPlotter", "config"),
+ "Darwin": path.join(path.expanduser("~"), "Library", "Application Support", "LogarithmPlotter"),
+}[system()]
+
+CONFIG_FILE = path.join(CONFIG_PATH, "config.json")
+CONFIG = DEFAULT_SETTINGS
+
+def init():
+ """
+ Initializes the config and loads all possible settings from the file if needs be.
+ """
+ makedirs(CONFIG_PATH, exist_ok=True)
+
+ if path.exists(CONFIG_FILE):
+ cfg_data = load(open(CONFIG_FILE, 'r', -1, 'utf8'))
+ for setting_name in cfg_data:
+ setSetting(setting_name, cfg_data[setting_name])
+
+def save():
+ """
+ Saves the config to the path.
+ """
+ write_file = open(CONFIG_FILE, 'w', -1, 'utf8')
+ write_file.write(dumps(CONFIG))
+ write_file.close()
+
+def getSetting(namespace):
+ """
+ Returns a setting from a namespace.
+ E.g: if the config is {"test": {"foo": 1}}, you can access the "foo" setting
+ by using the "test.foo" namespace.
+ """
+ names = namespace.split(".")
+ setting = CONFIG
+ for name in names:
+ if name in setting:
+ setting = setting[name]
+ else:
+ # return namespace # Return original name
+ raise ValueError('Setting ' + namespace + ' doesn\'t exist. Debug: ', setting, name)
+ return setting
+
+def setSetting(namespace, data):
+ """
+ Sets a setting at a namespace with data.
+ E.g: if the config is {"test": {"foo": 1}}, you can access the "foo" setting
+ by using the "test.foo" namespace.
+ """
+ names = namespace.split(".")
+ setting = CONFIG
+ for name in names:
+ if name != names[-1]:
+ if name in setting:
+ setting = setting[name]
+ else:
+ raise ValueError('Setting {} doesn\'t exist. Debug: {}, {}'.format(namespace, setting, name))
+ else:
+ setting[name] = data
diff --git a/LogarithmPlotter/logarithmplotter.py b/LogarithmPlotter/logarithmplotter.py
index 687ffb8..f1354bd 100644
--- a/LogarithmPlotter/logarithmplotter.py
+++ b/LogarithmPlotter/logarithmplotter.py
@@ -26,15 +26,21 @@ from PySide2.QtCore import Qt, QObject, Signal, Slot, Property
from PySide2.QtGui import QIcon, QImage, QKeySequence
from PySide2 import __version__ as PySide2_version
-import os
-import tempfile
+from tempfile import mkstemp
+from os import getcwd, chdir, environ, path, remove
from platform import release as os_release
from json import dumps, loads
from sys import platform, argv, version as sys_version
-import webbrowser
+from webbrowser import open as openWeb
+
+# Create the temporary file for saving copied screenshots
+tmpfile = mkstemp(suffix='.png')[1]
+pwd = getcwd()
+
+from . import config, __VERSION__
+from .update import check_for_updates
+config.init()
-tempfile = tempfile.mkstemp(suffix='.png')[1]
-pwd = os.getcwd()
def get_linux_theme():
des = {
@@ -43,8 +49,8 @@ def get_linux_theme():
"lxqt": "fusion",
"mate": "fusion",
}
- if "XDG_SESSION_DESKTOP" in os.environ:
- return des[os.environ["XDG_SESSION_DESKTOP"]] if os.environ["XDG_SESSION_DESKTOP"] in des else "fusion"
+ if "XDG_SESSION_DESKTOP" in environ:
+ return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "fusion"
else:
# Android
return "Material"
@@ -53,22 +59,22 @@ class Helper(QObject):
@Slot(str, str)
def write(self, filename, filedata):
- os.chdir(pwd)
- if os.path.exists(os.path.dirname(os.path.realpath(filename))):
+ chdir(pwd)
+ if path.exists(path.dirname(path.realpath(filename))):
if filename.split(".")[-1] == "lpf":
# Add header to file
filedata = "LPFv1" + filedata
- f = open(os.path.realpath(filename), 'w', -1, 'utf8')
+ f = open(path.realpath(filename), 'w', -1, 'utf8')
f.write(filedata)
f.close()
- os.chdir(os.path.dirname(os.path.realpath(__file__)))
+ chdir(path.dirname(path.realpath(__file__)))
@Slot(str, result=str)
def load(self, filename):
- os.chdir(pwd)
+ chdir(pwd)
data = '{}'
- if os.path.exists(os.path.realpath(filename)):
- f = open(os.path.realpath(filename), 'r', -1, 'utf8')
+ if path.exists(path.realpath(filename)):
+ f = open(path.realpath(filename), 'r', -1, 'utf8')
data = f.read()
f.close()
try:
@@ -84,25 +90,40 @@ class Helper(QObject):
QMessageBox.warning(None, 'LogarithmPlotter', 'Could not open file "{}":\n{}'.format(filename, e), QMessageBox.Ok) # Cannot parse file
else:
QMessageBox.warning(None, 'LogarithmPlotter', 'Could not open file: "{}"\nFile does not exist.'.format(filename), QMessageBox.Ok) # Cannot parse file
- os.chdir(os.path.dirname(os.path.realpath(__file__)))
+ chdir(path.dirname(path.realpath(__file__)))
return data
@Slot(result=str)
def gettmpfile(self):
- global tempfile
- return tempfile
+ global tmpfile
+ return tmpfile
@Slot()
def copyImageToClipboard(self):
- global tempfile
+ global tmpfile
clipboard = QApplication.clipboard()
- clipboard.setImage(QImage(tempfile))
+ clipboard.setImage(QImage(tmpfile))
@Slot(result=str)
def getVersion(self):
- from . import __VERSION__
return __VERSION__
+ @Slot(str, result=str)
+ def getSetting(self, namespace):
+ return config.getSetting(namespace)
+
+ @Slot(str, result=bool)
+ def getSettingBool(self, namespace):
+ return config.getSetting(namespace)
+
+ @Slot(str, str)
+ def setSetting(self, namespace, value):
+ return config.setSetting(namespace, value)
+
+ @Slot(str, bool)
+ def setSettingBool(self, namespace, value):
+ return config.setSetting(namespace, value)
+
@Slot(result=str)
def getDebugInfos(self):
"""
@@ -112,12 +133,12 @@ class Helper(QObject):
@Slot(str)
def openUrl(self, url):
- webbrowser.open(url)
+ openWeb(url)
def run():
- os.chdir(os.path.dirname(os.path.realpath(__file__)))
+ chdir(path.dirname(path.realpath(__file__)))
- os.environ["QT_QUICK_CONTROLS_STYLE"] = {
+ environ["QT_QUICK_CONTROLS_STYLE"] = {
"linux": get_linux_theme(),
"freebsd": get_linux_theme(),
"win32": "universal" if os_release == "10" else "fusion",
@@ -129,35 +150,41 @@ def run():
print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.")
icon_fallbacks = QIcon.fallbackSearchPaths();
- icon_fallbacks.append(os.path.realpath(os.path.join(os.getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")))
- icon_fallbacks.append(os.path.realpath(os.path.join(os.getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons", "settings")))
- icon_fallbacks.append(os.path.realpath(os.path.join(os.getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons", "settings", "custom")))
+ icon_fallbacks.append(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")))
+ icon_fallbacks.append(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons", "settings")))
+ icon_fallbacks.append(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons", "settings", "custom")))
QIcon.setFallbackSearchPaths(icon_fallbacks);
app = QApplication(argv)
app.setApplicationName("LogarithmPlotter")
app.setOrganizationName("Ad5001")
- app.setWindowIcon(QIcon(os.path.realpath(os.path.join(os.getcwd(), "logarithmplotter.svg"))))
+ app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
engine = QQmlApplicationEngine()
helper = Helper()
engine.rootContext().setContextProperty("Helper", helper)
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
engine.rootContext().setContextProperty("StartTime", dep_time)
- engine.addImportPath(os.path.realpath(os.path.join(os.getcwd(), "qml")))
- engine.load(os.path.realpath(os.path.join(os.getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
+ engine.addImportPath(path.realpath(path.join(getcwd(), "qml")))
+ engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
- os.chdir(pwd)
- if len(argv) > 0 and os.path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['json', 'lgg', 'lpf']:
+ chdir(pwd)
+ if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['json', 'lgg', 'lpf']:
engine.rootObjects()[0].loadDiagram(argv[-1])
- os.chdir(os.path.dirname(os.path.realpath(__file__)))
+ chdir(path.dirname(path.realpath(__file__)))
if not engine.rootObjects():
print("No root object")
exit(-1)
+
+ # Check for updates
+ if config.getSetting("check_for_updates"):
+ check_for_updates(__VERSION__, engine.rootObjects()[0])
+
exit_code = app.exec_()
- os.remove(tempfile)
+ remove(tmpfile)
+ config.save()
exit(exit_code)
if __name__ == "__main__":
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml
index 33718bd..9cdb42f 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml
@@ -17,7 +17,6 @@
*/
import QtQuick 2.12
-//import QtQuick.Controls 2.12
import eu.ad5001.MixedMenu 1.1
import "js/objects.js" as Objects
import "js/historylib.js" as HistoryLib
@@ -54,6 +53,7 @@ MenuBar {
icon.name: 'application-exit'
}
}
+
Menu {
title: qsTr("&Edit")
Action {
@@ -80,6 +80,7 @@ MenuBar {
icon.name: 'edit-copy'
}
}
+
Menu {
title: qsTr("&Create")
// Services repeater
@@ -100,6 +101,19 @@ MenuBar {
}
}
}
+
+ Menu {
+ title: qsTr("&Settings")
+ Action {
+ id: checkForUpdatesMenuSetting
+ text: qsTr("Check for updates on startup")
+ checkable: true
+ checked: Helper.getSettingBool("check_for_updates")
+ onTriggered: Helper.setSettingBool("check_for_updates", checked)
+ icon.name: 'update'
+ }
+ }
+
Menu {
title: qsTr("&Help")
Action {
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/GreetScreen.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/GreetScreen.qml
new file mode 100644
index 0000000..22cdb20
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/GreetScreen.qml
@@ -0,0 +1,117 @@
+
+/**
+ * LogarithmPlotter - Create graphs with logarithm scales.
+ * Copyright (C) 2021 Ad5001
+ *
+ * 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 .
+ */
+
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+
+Popup {
+ id: greetingPopup
+ x: (parent.width-width)/2
+ y: Math.max(20, (parent.height-height)/2)
+ width: 600
+ height: Math.min(parent.height-40, 500)
+ modal: true
+ focus: true
+ closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
+
+ Item {
+ id: welcome
+ height: logo.height
+ width: logo.width + 10 + welcomeText.width
+ anchors.top: parent.top
+ anchors.topMargin: 50
+ anchors.horizontalCenter: parent.horizontalCenter
+
+ Image {
+ id: logo
+ source: "icons/logarithmplotter.svg"
+ sourceSize.width: 48
+ sourceSize.height: 48
+ width: 48
+ height: 48
+ }
+
+ Label {
+ id: welcomeText
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: logo.right
+ anchors.leftMargin: 10
+ //width: parent.width
+ wrapMode: Text.WordWrap
+ font.pixelSize: 32
+ text: "Welcome to LogarithmPlotter"
+ }
+ }
+
+ Label {
+ id: versionText
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: welcome.bottom
+ anchors.topMargin: 10
+ //width: parent.width
+ wrapMode: Text.WordWrap
+ width: implicitWidth
+ font.pixelSize: 18
+ font.italic: true
+ text: "Version " + Helper.getVersion()
+ }
+
+ Label {
+ id: helpText
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: versionText.bottom
+ anchors.topMargin: 40
+ //width: parent.width
+ wrapMode: Text.WordWrap
+ font.pixelSize: 14
+ width: parent.width - 50
+ text: "Take a few seconds to configure LogarithmPlotter.\nThese settings can always be changed at any time from the \"Settings\" menu."
+ }
+
+ CheckBox {
+ id: checkForUpdatesSetting
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: helpText.bottom
+ anchors.topMargin: 10
+ checked: Helper.getSettingBool("check_for_updates")
+ text: 'Check for updates on startup (requires online connectivity)'
+ onClicked: {
+ Helper.setSettingBool("check_for_updates", checked)
+ checkForUpdatesMenuSetting.checked = checked
+ }
+ }
+
+ Button {
+ text: "Done"
+ font.pixelSize: 20
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 10
+ anchors.horizontalCenter: parent.horizontalCenter
+ onClicked: greetingPopup.close()
+ }
+
+ Timer {
+ running: Helper.getSetting("last_install_greet") != Helper.getVersion()
+ repeat: false
+ interval: 50
+ onTriggered: greetingPopup.open()
+ }
+
+ onClosed: Helper.setSetting("last_install_greet", Helper.getVersion())
+}
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml
index 123973e..2b27a0a 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml
@@ -18,6 +18,7 @@
import QtQml 2.12
import QtQuick.Controls 2.12
+import eu.ad5001.MixedMenu 1.1
import QtQuick.Layouts 1.12
import QtQuick 2.12
// Auto loading all objects.
@@ -37,12 +38,15 @@ ApplicationWindow {
SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active }
SystemPalette { id: sysPaletteIn; colorGroup: SystemPalette.Disabled }
- History { id: history }
menuBar: appMenu.trueItem
+ GreetScreen {}
+
AppMenuBar {id: appMenu}
+ History { id: history }
+
About {id: about}
Alert {
@@ -271,4 +275,24 @@ ApplicationWindow {
Helper.copyImageToClipboard()
alert.show("Copied plot screenshot to clipboard!")
}
+
+ function showAlert(alertText) {
+ // This function is called from the backend and is used to show alerts from there.
+ alert.show(alertText)
+ }
+
+
+ Menu {
+ id: updateMenu
+ title: qsTr("&Update")
+ Action {
+ text: qsTr("&Update LogarithmPlotter")
+ icon.name: 'update'
+ onTriggered: Helper.openUrl("https://dev.apps.ad5001.eu/logarithmplotter")
+ }
+ }
+
+ function showUpdateMenu() {
+ appMenu.addMenu(updateMenu)
+ }
}
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml
index eee9cdf..8c81bde 100644
--- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml
@@ -43,8 +43,6 @@ ScrollView {
property bool showygrad: true
Column {
- //height: 30*12 //30*Math.max(1, Math.ceil(7 / columns))
- //columns: Math.floor(width / settingWidth)
spacing: 10
width: parent.width
bottomPadding: 20
diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/update.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/update.svg
new file mode 100644
index 0000000..6dd9014
--- /dev/null
+++ b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/update.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/LogarithmPlotter/update.py b/LogarithmPlotter/update.py
new file mode 100644
index 0000000..5e6748b
--- /dev/null
+++ b/LogarithmPlotter/update.py
@@ -0,0 +1,81 @@
+"""
+ * LogarithmPlotter - Create graphs with logarithm scales.
+ * Copyright (C) 2021 Ad5001
+ *
+ * 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 PySide2.QtCore import Qt, QRunnable, QThreadPool, QThread, QObject, Signal
+from urllib.request import urlopen
+from urllib.error import HTTPError, URLError
+
+class UpdateInformation(QObject):
+ got_update_info = Signal(bool, str, bool)
+
+class UpdateCheckerRunnable(QRunnable):
+ def __init__(self, current_version, callback):
+ QRunnable.__init__(self)
+ self.current_version = current_version
+ self.callback = callback
+
+ def run(self):
+ msg_text = "Unknown update error."
+ show_alert = True
+ update_available = False
+ try:
+ # Fetching version
+ r = urlopen("https://api.ad5001.eu/update/v1/LogarithmPlotter")
+ lines = r.readlines()
+ r.close()
+ # Parsing version
+ version = "".join(map(chr, lines[0])).strip() # Converts byte to string.
+ version_tuple = version.split(".")
+ is_version_newer = False
+ if "dev" in self.current_version:
+ # We're on a dev version
+ current_version_tuple = self.current_version.split(".")[:-1] # Removing the dev0+git bit.
+ is_version_newer = version_tuple >= current_version_tuple # If equals, that means we got out of testing phase.
+ else:
+ current_version_tuple = self.current_version.split(".")
+ is_version_newer = version_tuple > current_version_tuple
+ if is_version_newer:
+ msg_text = "An update for LogarithPlotter (v" + version + ") is available."
+ update_available = True
+ else:
+ show_alert = False
+
+ except HTTPError as e:
+ msg_text = "Could not fetch update information: Server error " + str(e.code) + "."
+ except URLError as e:
+ msg_text = "Could not fetch update information: Could not connect to the update server. " + str(e.reason) + "."
+ print(msg_text)
+ self.callback.got_update_info.emit(show_alert, msg_text,update_available)
+
+def check_for_updates(current_version, window):
+ """
+ Checks for updates in the background, and sends an alert with information.
+ """
+
+ def cb(show_alert, msg_text, update_available):
+ pass
+ if show_alert:
+ window.showAlert(msg_text)
+ if update_available:
+ window.showUpdateMenu()
+
+ update_info = UpdateInformation()
+ update_info.got_update_info.connect(cb)
+
+ runnable = UpdateCheckerRunnable(current_version, update_info)
+ QThreadPool.globalInstance().start(runnable)