Compare commits

..

No commits in common. "c3daa9228084a6508f6e3315095eacdf8a5612e3" and "4a5756f24ddb777b7736f63dc15d1a733b33ee37" have entirely different histories.

7 changed files with 38 additions and 261 deletions

View file

@ -15,6 +15,6 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from LogarithmPlotter.logarithmplotter import create_qapp if __name__ == "__main__":
from .logarithmplotter import run
app = create_qapp() run()

View file

@ -48,31 +48,16 @@ from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex from LogarithmPlotter.util.latex import Latex
from LogarithmPlotter.util.js import PyJSValue from LogarithmPlotter.util.js import PyJSValue
LINUX_THEMES = { # See https://specifications.freedesktop.org/menu-spec/latest/onlyshowin-registry.html
"COSMIC": "Basic",
"GNOME": "Basic",
"GNOME-Classic": "Basic",
"GNOME-Flashback": "Basic",
"KDE": "Fusion",
"LXDE": "Basic",
"LXQt": "Fusion",
"MATE": "Fusion",
"TDE": "Fusion",
"Unity": "Basic",
"XFCE": "Basic",
"Cinnamon": "Fusion",
"Pantheon": "Basic",
"DDE": "Basic",
"EDE": "Fusion",
"Endless": "Basic",
"Old": "Fusion",
}
def get_linux_theme() -> str: def get_linux_theme() -> str:
des = {
"KDE": "Fusion",
"gnome": "Basic",
"lxqt": "Fusion",
"mate": "Fusion",
}
if "XDG_SESSION_DESKTOP" in environ: if "XDG_SESSION_DESKTOP" in environ:
if environ["XDG_SESSION_DESKTOP"] in LINUX_THEMES: if environ["XDG_SESSION_DESKTOP"] in des:
return LINUX_THEMES[environ["XDG_SESSION_DESKTOP"]] return des[environ["XDG_SESSION_DESKTOP"]]
return "Fusion" return "Fusion"
else: else:
# Android # Android
@ -92,18 +77,18 @@ def get_platform_qt_style(os) -> str:
def register_icon_directories() -> None: def register_icon_directories() -> None:
icon_fallbacks = QIcon.fallbackSearchPaths() icon_fallbacks = QIcon.fallbackSearchPaths()
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons") base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
paths = [["common"], ["objects"], ["history"], ["settings"], ["settings", "custom"]] icon_fallbacks.append(path.realpath(path.join(base_icon_path, "common")))
for p in paths: icon_fallbacks.append(path.realpath(path.join(base_icon_path, "objects")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, *p))) icon_fallbacks.append(path.realpath(path.join(base_icon_path, "history")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
QIcon.setFallbackSearchPaths(icon_fallbacks) QIcon.setFallbackSearchPaths(icon_fallbacks)
def create_qapp() -> QApplication: def create_qapp() -> QApplication:
app = QApplication(argv) app = QApplication(argv)
app.setApplicationName("LogarithmPlotter") app.setApplicationName("LogarithmPlotter")
app.setApplicationDisplayName("LogarithmPlotter") app.setDesktopFileName("eu.ad5001.LogarithmPlotter.desktop")
app.setApplicationVersion(f"v{__VERSION__}")
app.setDesktopFileName("eu.ad5001.LogarithmPlotter")
app.setOrganizationName("Ad5001") app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True) app.styleHints().setShowShortcutsInContextMenus(True)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg")))) app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
@ -116,9 +101,7 @@ def install_translation(app: QApplication) -> QTranslator:
# Check if lang is forced. # Check if lang is forced.
forcedlang = [p for p in argv if p[:7] == "--lang="] forcedlang = [p for p in argv if p[:7] == "--lang="]
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale() locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
if not translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n"))): if translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n"))):
# Load default translation
translator.load(QLocale("en"), "lp", "_", path.realpath(path.join(getcwd(), "i18n")))
app.installTranslator(translator) app.installTranslator(translator)
return translator return translator

View file

@ -15,15 +15,10 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from re import Pattern
from PySide6.QtCore import QMetaObject, QObject, QDateTime
from PySide6.QtQml import QJSValue from PySide6.QtQml import QJSValue
class InvalidAttributeValueException(Exception): pass class InvalidAttributeValueException(Exception): pass
class NotAPrimitiveException(Exception): pass
class Function: pass
class URL: pass
class PyJSValue: class PyJSValue:
""" """
@ -53,11 +48,11 @@ class PyJSValue:
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, PyJSValue): if isinstance(other, PyJSValue):
return self.qjs_value.strictlyEquals(other.qjs_value) return self.qjs_value.equals(other.qjs_value)
elif isinstance(other, QJSValue): elif isinstance(other, QJSValue):
return self.qjs_value.strictlyEquals(other) return self.qjs_value.equals(other)
elif type(other) in (int, float, str, bool): elif type(other) in (int, float, str, bool):
return self.qjs_value.strictlyEquals(QJSValue(other)) return self.qjs_value.equals(QJSValue(other))
else: else:
return False return False
@ -73,34 +68,3 @@ class PyJSValue:
if isinstance(value, QJSValue): if isinstance(value, QJSValue):
value = PyJSValue(value) value = PyJSValue(value)
return value return value
def type(self) -> any:
matcher = [
(lambda: self.qjs_value.isArray(), list),
(lambda: self.qjs_value.isBool(), bool),
(lambda: self.qjs_value.isCallable(), Function),
(lambda: self.qjs_value.isDate(), QDateTime),
(lambda: self.qjs_value.isError(), Exception),
(lambda: self.qjs_value.isNull(), None),
(lambda: self.qjs_value.isNumber(), float),
(lambda: self.qjs_value.isQMetaObject(), QMetaObject),
(lambda: self.qjs_value.isQObject(), QObject),
(lambda: self.qjs_value.isRegExp(), Pattern),
(lambda: self.qjs_value.isUndefined(), None),
(lambda: self.qjs_value.isUrl(), URL),
(lambda: self.qjs_value.isString(), str),
(lambda: self.qjs_value.isObject(), object),
]
for (test, value) in matcher:
if test():
return value
return None
def primitive(self):
"""
Returns the pythonic value of the given primitive data.
Raises a NotAPrimitiveException() if this JS value is not a primitive.
"""
if self.type() not in [bool, float, str, None]:
raise NotAPrimitiveException()
return self.qjs_value.toPrimitive().toVariant()

View file

@ -1,21 +1,3 @@
"""
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 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 <https://www.gnu.org/licenses/>.
"""
import pytest import pytest
from os import getcwd from os import getcwd
from os.path import join from os.path import join

View file

@ -1,21 +1,3 @@
"""
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 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 <https://www.gnu.org/licenses/>.
"""
import pytest import pytest
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from shutil import which from shutil import which

View file

@ -1,33 +1,6 @@
"""
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 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 <https://www.gnu.org/licenses/>.
"""
import pytest import pytest
from os import environ
from os.path import exists, join
from PySide6.QtGui import QIcon
from tempfile import TemporaryDirectory
from LogarithmPlotter.logarithmplotter import get_linux_theme, LINUX_THEMES, get_platform_qt_style, \ from LogarithmPlotter.logarithmplotter import get_linux_theme
register_icon_directories, install_translation, create_engine
from LogarithmPlotter.util import config
from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex
from globals import app
THEMES = [ THEMES = [
"Basic", "Basic",
@ -38,72 +11,6 @@ THEMES = [
"macOS" "macOS"
] ]
OS_PLATFORMS = [
"linux",
"freebsd",
"win32",
"cygwin",
"darwin"
]
@pytest.fixture()
def temporary():
directory = TemporaryDirectory()
config.CONFIG_PATH = join(directory.name, "config.json")
tmpfile = join(directory.name, "graph.png")
yield tmpfile, directory
directory.cleanup()
class TestMain: class TestMain:
def test_linux_themes(self): def test_themes(self):
# Check without a desktop get_linux_theme()
if "XDG_SESSION_DESKTOP" in environ:
del environ["XDG_SESSION_DESKTOP"]
assert get_linux_theme() in THEMES
# Test various environments.
environ["XDG_SESSION_DESKTOP"] = "GNOME"
assert get_linux_theme() in THEMES
# Test various environments.
environ["XDG_SESSION_DESKTOP"] = "NON-EXISTENT"
assert get_linux_theme() in THEMES
# Check all linux themes are in list
for desktop, theme in LINUX_THEMES.items():
assert theme in THEMES
def test_os_themes(self):
for platform in OS_PLATFORMS:
assert get_platform_qt_style(platform) in THEMES
def test_icon_directories(self):
base_paths = QIcon.fallbackSearchPaths()
register_icon_directories()
# Check if registered
assert len(base_paths) < len(QIcon.fallbackSearchPaths())
# Check if all exists
for p in QIcon.fallbackSearchPaths():
assert exists(p)
def test_app(self, temporary):
assert not app.windowIcon().isNull()
# Translations
translator = install_translation(app)
assert not translator.isEmpty()
# Engine
tmpfile, tempdir = temporary
helper = Helper(".", tmpfile)
latex = Latex(tempdir)
engine, js_globals = create_engine(helper, latex, 0)
assert len(engine.rootObjects()) > 0 # QML File loaded.
assert type(engine.rootContext().contextProperty("TestBuild")) is bool
assert engine.rootContext().contextProperty("StartTime") == 0
assert js_globals.Latex.type() is not None
assert js_globals.Helper.type() is not None
assert js_globals.Modules.type() is not None
# Check if modules have loaded
assert js_globals.Modules.History.type() is not None
assert js_globals.Modules.Latex.type() is not None
assert js_globals.Modules.ObjectsCommon.type() is not None
assert js_globals.Modules.Canvas.type() is not None
assert js_globals.Modules.IO.type() is not None
assert js_globals.Modules.Objects.type() is not None
assert js_globals.Modules.Preferences.type() is not None

View file

@ -1,53 +1,30 @@
"""
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 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 <https://www.gnu.org/licenses/>.
"""
import pytest import pytest
from re import Pattern
from PySide6.QtQml import QJSEngine, QJSValue from PySide6.QtQml import QJSEngine, QJSValue
from PySide6.QtWidgets import QApplication
from LogarithmPlotter.util.js import PyJSValue, InvalidAttributeValueException, NotAPrimitiveException from LogarithmPlotter.util.js import PyJSValue, InvalidAttributeValueException
from globals import app
@pytest.fixture() app = QApplication()
def data():
engine = QJSEngine() engine = QJSEngine()
obj = PyJSValue(engine.globalObject()) obj = PyJSValue(engine.globalObject())
yield engine, obj
class TestPyJS: class TestPyJS:
def test_set(self, data): def test_set(self):
engine, obj = data
obj.num1 = 2 obj.num1 = 2
obj.num2 = QJSValue(2) obj.num2 = QJSValue(2)
obj.num3 = PyJSValue(QJSValue(2)) obj.num3 = PyJSValue(QJSValue(2))
with pytest.raises(InvalidAttributeValueException): with pytest.raises(InvalidAttributeValueException):
obj.num3 = object() obj.num3 = object()
def test_eq(self, data): def test_eq(self):
engine, obj = data
obj.num = QJSValue(2) obj.num = QJSValue(2)
assert obj.num == 2 assert obj.num == 2
assert obj.num == QJSValue(2) assert obj.num == QJSValue(2)
assert obj.num == PyJSValue(QJSValue(2)) assert obj.num == PyJSValue(QJSValue(2))
assert obj.num != object() assert obj.num != object()
def test_function(self, data): def test_function(self):
engine, obj = data
function = PyJSValue(engine.evaluate("(function(argument) {return argument*2})")) function = PyJSValue(engine.evaluate("(function(argument) {return argument*2})"))
assert function(3) == 6 assert function(3) == 6
assert function(10) == 20 assert function(10) == 20
@ -57,21 +34,3 @@ class TestPyJS:
function3 = PyJSValue(engine.evaluate("2+2")) function3 = PyJSValue(engine.evaluate("2+2"))
with pytest.raises(InvalidAttributeValueException): with pytest.raises(InvalidAttributeValueException):
function3() function3()
def test_type(self, data):
engine, obj = data
assert PyJSValue(engine.evaluate("[]")).type() == list
assert PyJSValue(engine.evaluate("undefined")).type() is None
assert PyJSValue(engine.evaluate("/[a-z]/g")).type() == Pattern
assert PyJSValue(QJSValue(2)).type() == float
assert PyJSValue(QJSValue("3")).type() == str
assert PyJSValue(QJSValue(True)).type() == bool
def test_primitive(self, data):
engine, obj = data
assert PyJSValue(QJSValue(2)).primitive() == 2
assert PyJSValue(QJSValue("string")).primitive() == "string"
assert PyJSValue(QJSValue(True)).primitive() == True
assert PyJSValue(engine.evaluate("undefined")).primitive() is None
with pytest.raises(NotAPrimitiveException):
assert PyJSValue(engine.evaluate("[]")).primitive() == []