diff --git a/LogarithmPlotter/__init__.py b/LogarithmPlotter/__init__.py index 2bd4967..b3140ad 100644 --- a/LogarithmPlotter/__init__.py +++ b/LogarithmPlotter/__init__.py @@ -39,3 +39,7 @@ if not is_release and which('git') is not None: # Date cannot be parsed, not git root? pass +if __name__ == "__main__": + from .logarithmplotter import run + + run() diff --git a/LogarithmPlotter/logarithmplotter.py b/LogarithmPlotter/logarithmplotter.py index bc4d895..d528c02 100644 --- a/LogarithmPlotter/logarithmplotter.py +++ b/LogarithmPlotter/logarithmplotter.py @@ -36,8 +36,7 @@ tempdir = TemporaryDirectory() tmpfile = path.join(tempdir.name, 'graph.png') pwd = getcwd() -logarithmplotter_path = path.dirname(path.realpath(__file__)) -chdir(logarithmplotter_path) +chdir(path.dirname(path.realpath(__file__))) if path.realpath(path.join(getcwd(), "..")) not in sys_path: sys_path.append(path.realpath(path.join(getcwd(), ".."))) @@ -92,7 +91,7 @@ def get_platform_qt_style(os) -> str: def register_icon_directories() -> None: icon_fallbacks = QIcon.fallbackSearchPaths() - base_icon_path = path.join(logarithmplotter_path, "qml", "eu", "ad5001", "LogarithmPlotter", "icons") + base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons") paths = [["common"], ["objects"], ["history"], ["settings"], ["settings", "custom"]] for p in paths: icon_fallbacks.append(path.realpath(path.join(base_icon_path, *p))) @@ -107,7 +106,7 @@ def create_qapp() -> QApplication: app.setDesktopFileName("eu.ad5001.LogarithmPlotter") app.setOrganizationName("Ad5001") app.styleHints().setShowShortcutsInContextMenus(True) - app.setWindowIcon(QIcon(path.realpath(path.join(logarithmplotter_path, "logarithmplotter.svg")))) + app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg")))) return app @@ -116,12 +115,10 @@ def install_translation(app: QApplication) -> QTranslator: translator = QTranslator() # Check if lang is forced. forcedlang = [p for p in argv if p[:7] == "--lang="] - i18n_path = path.realpath(path.join(logarithmplotter_path, "i18n")) locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale() - if not translator.load(locale, "lp", "_", i18n_path): + if not translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n"))): # Load default translation - print("Loading default language en...") - translator.load(QLocale("en"), "lp", "_", i18n_path) + translator.load(QLocale("en"), "lp", "_", path.realpath(path.join(getcwd(), "i18n"))) app.installTranslator(translator) return translator @@ -136,9 +133,8 @@ def create_engine(helper: Helper, latex: Latex, dep_time: float) -> tuple[QQmlAp engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv) engine.rootContext().setContextProperty("StartTime", dep_time) - qml_path = path.realpath(path.join(logarithmplotter_path, "qml")) - engine.addImportPath(qml_path) - engine.load(path.join(qml_path, "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"))) return engine, js_globals diff --git a/LogarithmPlotter/util/helper.py b/LogarithmPlotter/util/helper.py index d2fe631..c7c92d8 100644 --- a/LogarithmPlotter/util/helper.py +++ b/LogarithmPlotter/util/helper.py @@ -24,29 +24,16 @@ from PySide6 import __version__ as PySide6_version from os import chdir, path from json import loads -from sys import version as sys_version, argv +from sys import version as sys_version from urllib.request import urlopen from urllib.error import HTTPError, URLError from LogarithmPlotter import __VERSION__ from LogarithmPlotter.util import config -SHOW_GUI_MESSAGES = "--test-build" not in argv -CHANGELOG_VERSION = __VERSION__ - class InvalidFileException(Exception): pass -def show_message(msg: str) -> None: - """ - Shows a GUI message if GUI messages are enabled - """ - if SHOW_GUI_MESSAGES: - QMessageBox.warning(None, "LogarithmPlotter", msg, QMessageBox.OK) - else: - raise InvalidFileException(msg) - - class ChangelogFetcher(QRunnable): def __init__(self, helper): @@ -57,7 +44,7 @@ class ChangelogFetcher(QRunnable): msg_text = "Unknown changelog error." try: # Fetching version - r = urlopen("https://api.ad5001.eu/changelog/logarithmplotter/?version=" + CHANGELOG_VERSION) + r = urlopen("https://api.ad5001.eu/changelog/logarithmplotter/?version=" + __VERSION__) lines = r.readlines() r.close() msg_text = "".join(map(lambda x: x.decode('utf-8'), lines)).strip() @@ -66,16 +53,21 @@ class ChangelogFetcher(QRunnable): str(e.code)) except URLError as e: msg_text = QCoreApplication.translate("changelog", "Could not fetch update: {}.").format(str(e.reason)) - self.helper.changelogFetched.emit(msg_text) + self.helper.gotChangelog.emit(msg_text) class Helper(QObject): changelogFetched = Signal(str) + gotChangelog = Signal(str) def __init__(self, cwd: str, tmpfile: str): QObject.__init__(self) self.cwd = cwd self.tmpfile = tmpfile + self.gotChangelog.connect(self.fetched) + + def fetched(self, changelog: str): + self.changelogFetched.emit(changelog) @Slot(str, str) def write(self, filename, filedata): @@ -101,19 +93,20 @@ class Helper(QObject): if data[:5] == "LPFv1": # V1 version of the file data = data[5:] + elif data[0] == "{" and "type" in loads(data) and loads(data)["type"] == "logplotv1": + pass elif data[:3] == "LPF": # More recent version of LogarithmPlotter file, but incompatible with the current format - msg = QCoreApplication.translate('main', - "This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}.\nPlease update LogarithmPlotter to open this file.") + msg = QCoreApplication.translate("This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}.\nPlease update LogarithmPlotter to open this file.") raise InvalidFileException(msg.format(__VERSION__)) else: - raise InvalidFileException("Invalid LogarithmPlotter file.") - except InvalidFileException as e: # If file can't be loaded + raise Exception("Invalid LogarithmPlotter file.") + except Exception as e: # If file can't be loaded msg = QCoreApplication.translate('main', 'Could not open file "{}":\n{}') - show_message(msg.format(filename, e)) # Cannot parse file + QMessageBox.warning(None, 'LogarithmPlotter', msg.format(filename, e), QMessageBox.Ok) # Cannot parse file else: msg = QCoreApplication.translate('main', 'Could not open file: "{}"\nFile does not exist.') - show_message(msg.format(filename)) # Cannot parse file + QMessageBox.warning(None, 'LogarithmPlotter', msg.format(filename), QMessageBox.Ok) # Cannot parse file try: chdir(path.dirname(path.realpath(__file__))) except NotADirectoryError as e: @@ -137,27 +130,31 @@ class Helper(QObject): @Slot(str, result=str) def getSetting(self, namespace): - return str(config.getSetting(namespace)) + return config.getSetting(namespace) @Slot(str, result=float) def getSettingInt(self, namespace): - return float(config.getSetting(namespace)) + return config.getSetting(namespace) @Slot(str, result=bool) def getSettingBool(self, namespace): - return bool(config.getSetting(namespace)) + return config.getSetting(namespace) @Slot(str, str) def setSetting(self, namespace, value): - return config.setSetting(namespace, str(value)) + return config.setSetting(namespace, value) @Slot(str, bool) def setSettingBool(self, namespace, value): - return config.setSetting(namespace, bool(value)) + return config.setSetting(namespace, value) @Slot(str, float) def setSettingInt(self, namespace, value): - return config.setSetting(namespace, float(value)) + return config.setSetting(namespace, value) + + @Slot(str) + def setLanguage(self, new_lang): + config.setSetting("language", new_lang) @Slot(result=str) def getDebugInfos(self): @@ -170,6 +167,7 @@ class Helper(QObject): @Slot() def fetchChangelog(self): changelog_cache_path = path.join(path.dirname(path.realpath(__file__)), "CHANGELOG.md") + print(changelog_cache_path) if path.exists(changelog_cache_path): # We have a cached version of the changelog, for env that don't have access to the internet. f = open(changelog_cache_path); diff --git a/LogarithmPlotter/util/update.py b/LogarithmPlotter/util/update.py index 32017d5..ed2b5ba 100644 --- a/LogarithmPlotter/util/update.py +++ b/LogarithmPlotter/util/update.py @@ -89,4 +89,3 @@ def check_for_updates(current_version, window): runnable = UpdateCheckerRunnable(current_version, update_info) QThreadPool.globalInstance().start(runnable) - return update_info diff --git a/tests/python/test_helper.py b/tests/python/test_helper.py index 412eac3..c4f6ea7 100644 --- a/tests/python/test_helper.py +++ b/tests/python/test_helper.py @@ -17,166 +17,18 @@ """ import pytest -from os import getcwd, remove +from os import getcwd from os.path import join from tempfile import TemporaryDirectory -from json import loads -from shutil import copy2 - -from PySide6.QtCore import QObject, Signal, QThreadPool -from PySide6.QtGui import QImage -from PySide6.QtWidgets import QApplication - -from LogarithmPlotter import __VERSION__ as version -from LogarithmPlotter.util import config, helper -from LogarithmPlotter.util.helper import ChangelogFetcher, Helper, InvalidFileException +from LogarithmPlotter.util import config pwd = getcwd() -helper.SHOW_GUI_MESSAGES = False + @pytest.fixture() def temporary(): directory = TemporaryDirectory() config.CONFIG_PATH = join(directory.name, "config.json") tmpfile = join(directory.name, "graph.png") - yield tmpfile, directory + yield tmpfile directory.cleanup() - - -class MockHelperSignals(QObject): - changelogFetched = Signal(str) - - def __init__(self, expect_404): - QObject.__init__(self) - self.expect_404 = expect_404 - self.changelogFetched.connect(self.changelog_fetched) - self.changelog = None - - def changelog_fetched(self, changelog): - self.changelog = changelog - - -class TestChangelog: - - def test_exists(self, qtbot): - helper.CHANGELOG_VERSION = '0.5.0' - mock_helper = MockHelperSignals(False) - fetcher = ChangelogFetcher(mock_helper) - fetcher.run() # Does not raise an exception - qtbot.waitSignal(mock_helper.changelogFetched, timeout=10000) - assert type(mock_helper.changelog) == str - assert '404' not in mock_helper.changelog - - def tests_no_exist(self, qtbot): - mock_helper = MockHelperSignals(True) - helper.CHANGELOG_VERSION = '1.0.0' - fetcher = ChangelogFetcher(mock_helper) - fetcher.run() - qtbot.waitSignal(mock_helper.changelogFetched, timeout=10000) - assert type(mock_helper.changelog) == str - assert '404' in mock_helper.changelog - - -class TestHelper: - def test_read(self, temporary): - # Test file reading and information loading. - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - data = obj.load("ci/test1.lpf") - assert type(data) == str - data = loads(data) - assert data['type'] == "logplotv1" - # Checking data['types'] of valid file. - # See https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/LogarithmPlotter-file-format-v1.0 - assert type(data['width']) == int - assert type(data['height']) == int - assert type(data['xzoom']) in (int, float) - assert type(data['yzoom']) in (int, float) - assert type(data['xmin']) in (int, float) - assert type(data['ymax']) in (int, float) - assert type(data['xaxisstep']) == str - assert type(data['yaxisstep']) == str - assert type(data['xaxislabel']) == str - assert type(data['yaxislabel']) == str - assert type(data['logscalex']) == bool - assert type(data['linewidth']) in (int, float) - assert type(data['showxgrad']) == bool - assert type(data['showygrad']) == bool - assert type(data['textsize']) in (int, float) - assert type(data['history']) == list and len(data['history']) == 2 - assert type(data['history'][0]) == list - assert type(data['history'][1]) == list - for action_list in data['history']: - for action in action_list: - assert type(action[0]) == str - assert type(action[1]) == list - assert type(data['objects']) == dict - for obj_type, objects in data['objects'].items(): - assert type(obj_type) == str - assert type(objects) == list - for obj in objects: - assert type(obj) == list - - def test_read_newer(self, temporary): - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - newer_file_path = join(directory.name, "newer.lpf") - with open(newer_file_path, "w") as f: - f.write("LPFv2[other invalid data]") - with pytest.raises(InvalidFileException): - obj.load(newer_file_path) - - def test_read_invalid_file(self, temporary): - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - with pytest.raises(InvalidFileException): - obj.load("./inexistant.lpf") - with pytest.raises(InvalidFileException): - obj.load("./pyproject.toml") - - def test_write(self, temporary): - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - target = join(directory.name, "target.lpf") - data = "example_data" - obj.write(target, data) - with open(target, "r") as f: - read_data = f.read() - # Ensure data has been written. - assert read_data == "LPFv1" + data - - def test_tmp_graphic(self, temporary): - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - assert obj.gettmpfile() == tmpfile - obj.copyImageToClipboard() - clipboard = QApplication.clipboard() - assert type(clipboard.image()) == QImage - - def test_strings(self, temporary): - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - assert obj.getVersion() == version - assert type(obj.getDebugInfos()) == str - assert type(obj.getSetting("check_for_updates")) == str - assert type(obj.getSettingInt("check_for_updates")) == float - assert type(obj.getSettingBool("check_for_updates")) == bool - - def test_set_config(self, temporary): - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - obj.setSetting("last_install_greet", obj.getSetting("last_install_greet")) - obj.setSettingBool("check_for_updates", obj.getSettingBool("check_for_updates")) - obj.setSettingInt("default_graph.xzoom", obj.getSettingInt("default_graph.xzoom")) - - def test_fetch_changelog(self, temporary, qtbot): - tmpfile, directory = temporary - obj = Helper(pwd, tmpfile) - copy2("../../CHANGELOG.md", "../../LogarithmPlotter/util/CHANGELOG.md") - obj.fetchChangelog() - assert QThreadPool.globalInstance().activeThreadCount() == 0 - qtbot.waitSignal(obj.changelogFetched, timeout=10000) - remove("../../LogarithmPlotter/util/CHANGELOG.md") - obj.fetchChangelog() - assert QThreadPool.globalInstance().activeThreadCount() > 0 - qtbot.waitSignal(obj.changelogFetched, timeout=10000) \ No newline at end of file diff --git a/tests/python/test_native.py b/tests/python/test_native.py deleted file mode 100644 index 6fc2d65..0000000 --- a/tests/python/test_native.py +++ /dev/null @@ -1,57 +0,0 @@ -""" - * 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 . -""" - -import pytest -from os.path import exists - -from PySide6.QtCore import QEvent, QObject, QUrl -from PySide6.QtGui import QActionEvent, QFileOpenEvent - -from LogarithmPlotter.util.native import MacOSFileOpenHandler - - -class LoadDiagramCalledSuccessfully(Exception): pass - - -class MockIO: - def loadDiagram(self, file_name): - assert type(file_name) == str - raise LoadDiagramCalledSuccessfully() - - -class MockFileOpenEvent(QEvent): - def __init__(self, file): - QEvent.__init__(self, QEvent.FileOpen) - self._file = file - - def file(self): - return self._file - - -def test_native(): - event_filter = MacOSFileOpenHandler() - # Nothing should happen here. The module hasn't been initialized - event_filter.eventFilter(None, QFileOpenEvent(QUrl.fromLocalFile("ci/test1.lpf"))) - with pytest.raises(LoadDiagramCalledSuccessfully): - event_filter.init_io(MockIO()) # Now that we've initialized, the loadDiagram function should be called. - with pytest.raises(LoadDiagramCalledSuccessfully): - # And now it will do so every time an event is loaded. - event_filter.eventFilter(None, QFileOpenEvent(QUrl.fromLocalFile("ci/test1.lpf"))) - # Check what happens when a non file open qevent is launched against it. - event_filter.eventFilter(QObject(), QEvent(QEvent.ActionAdded)) - diff --git a/tests/python/test_update.py b/tests/python/test_update.py deleted file mode 100644 index 9f65597..0000000 --- a/tests/python/test_update.py +++ /dev/null @@ -1,67 +0,0 @@ -""" - * 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 . -""" -from sys import argv - -import pytest -from PySide6.QtCore import QThreadPool - -from LogarithmPlotter import __VERSION__ as version -from LogarithmPlotter.util.update import UpdateInformation, UpdateCheckerRunnable, check_for_updates - - -class MockWindow: - def showAlert(self, msg): raise Exception(msg) - def showUpdateMenu(self, msg): pass - -def check_update_callback_type(show_alert, msg_text, update_available): - assert type(show_alert) == bool - assert type(msg_text) == str - assert type(update_available) == bool - -def test_update(qtbot): - def check_older(show_alert, msg_text, update_available): - check_update_callback_type(show_alert, msg_text, update_available) - assert update_available - assert show_alert - - def check_newer(show_alert, msg_text, update_available): - check_update_callback_type(show_alert, msg_text, update_available) - assert not update_available - assert not show_alert - - update_info_older = UpdateInformation() - update_info_older.got_update_info.connect(check_older) - update_info_newer = UpdateInformation() - update_info_newer.got_update_info.connect(check_newer) - runnable = UpdateCheckerRunnable('1.0.0', update_info_newer) - runnable.run() - qtbot.waitSignal(update_info_newer.got_update_info, timeout=10000) - runnable = UpdateCheckerRunnable('0.1.0', update_info_older) - runnable.run() - qtbot.waitSignal(update_info_older.got_update_info, timeout=10000) - runnable = UpdateCheckerRunnable('0.5.0+dev0+git20240101', update_info_older) - runnable.run() - qtbot.waitSignal(update_info_older.got_update_info, timeout=10000) - -def test_update_checker(qtbot): - update_info = check_for_updates('0.6.0', MockWindow()) - assert QThreadPool.globalInstance().activeThreadCount() == 1 - qtbot.waitSignal(update_info.got_update_info, timeout=10000) - argv.append("--no-check-for-updates") - update_info = check_for_updates('0.6.0', MockWindow()) - assert QThreadPool.globalInstance().activeThreadCount() < 2 # No new update checks where added