Compare commits

..

No commits in common. "master" and "v0.5.0" have entirely different histories.

353 changed files with 20477 additions and 36724 deletions

34
.gitignore vendored
View file

@ -1,19 +1,13 @@
# Building
build/ build/
dist/ dist/
deb_dist/ deb_dist/
assets/linux/flatpak/AppDir linux/flatpak/AppDir
assets/linux/flatpak/repo linux/flatpak/repo
assets/linux/flatpak/build-dir linux/flatpak/build-dir
assets/linux/flatpak/.flatpak-builder linux/flatpak/.flatpak-builder
*.snap *.snap
*.spec *.spec
*.zip *.zip
*.tar.gz
*.spec
*.egg-info/
# Runtime data
**/**.qmlc **/**.qmlc
**/**.jsc **/**.jsc
**/**.pyc **/**.pyc
@ -26,21 +20,17 @@ assets/linux/flatpak/.flatpak-builder
.DS_Store .DS_Store
**/.DS_Store **/.DS_Store
**/__pycache__/ **/__pycache__/
# IDE Data
.ropeproject .ropeproject
.vscode .vscode
*.kdev4 build
.kdev4
docs/html docs/html
.directory .directory
*.kdev4
*.lpf *.lpf
*.lgg *.lgg
*.spec
# Tests .kdev4
common/coverage/ AccountFree.pro
**/.coverage AccountFree.pro.user
*.egg-info/
# npm *.tar.gz
common/node_modules
runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs*

4
.gitmodules vendored
View file

@ -1,3 +1,3 @@
[submodule "runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/MixedMenu"] [submodule "LogarithmPlotter/qml/eu/ad5001/MixedMenu"]
path = runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/MixedMenu path = LogarithmPlotter/qml/eu/ad5001/MixedMenu
url = https://git.ad5001.eu/Ad5001/MixedMenu url = https://git.ad5001.eu/Ad5001/MixedMenu

View file

@ -1,6 +1,6 @@
# Changelog # Changelog
## v0.5.0 (11 Jan 2024) ## v0.5.0 (11 Jan 2023)
**New** **New**

View file

@ -1,6 +1,6 @@
""" """
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,8 +17,9 @@
""" """
from shutil import which from shutil import which
__VERSION__ = "0.6.0" __VERSION__ = "0.5.0"
is_release = False is_release = True
# Check if development version, if so get the date of the latest git patch # Check if development version, if so get the date of the latest git patch
# and append it to the version string. # and append it to the version string.
@ -26,10 +27,9 @@ if not is_release and which('git') is not None:
from os.path import realpath, join, dirname, exists from os.path import realpath, join, dirname, exists
from subprocess import check_output from subprocess import check_output
from datetime import datetime from datetime import datetime
# Command to check date of latest git commit # Command to check date of latest git commit
cmd = ['git', 'log', '--format=%ci', '-n 1'] cmd = ['git', 'log', '--format=%ci', '-n 1']
cwd = realpath(join(dirname(__file__), '..', '..', '..')) # Root LogarithmPlotter directory. cwd = realpath(join(dirname(__file__), '..')) # Root AccountFree directory.
if exists(join(cwd, '.git')): if exists(join(cwd, '.git')):
date_str = check_output(cmd, cwd=cwd).decode('utf-8').split(' ')[0] date_str = check_output(cmd, cwd=cwd).decode('utf-8').split(' ')[0]
try: try:
@ -39,3 +39,6 @@ if not is_release and which('git') is not None:
# Date cannot be parsed, not git root? # Date cannot be parsed, not git root?
pass pass
if __name__ == "__main__":
from .logarithmplotter import run
run()

View file

@ -1,4 +1,4 @@
/** """
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
@ -14,12 +14,7 @@
* *
* 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/>.
*/ """
if __name__ == "__main__":
{ from .logarithmplotter import run
"recursive": true, run()
"require": [
"esm",
"./test/hooks.mjs"
]
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,2 @@
#!/bin/bash
lrelease *.ts

View file

@ -0,0 +1,2 @@
#!/bin/bash
lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts

View file

@ -0,0 +1,155 @@
"""
* 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/>.
"""
from time import time
from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import Qt, QTranslator, QLocale
from PySide6.QtGui import QIcon
from tempfile import TemporaryDirectory
from os import getcwd, chdir, environ, path, remove, close
from platform import release as os_release
from sys import platform, argv, version as sys_version, exit
from sys import path as sys_path
start_time = time()
# Create the temporary directory for saving copied screenshots and latex files
tempdir = TemporaryDirectory()
tmpfile = path.join(tempdir.name, 'graph.png')
pwd = getcwd()
chdir(path.dirname(path.realpath(__file__)))
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
sys_path.append(path.realpath(path.join(getcwd(), "..")))
from LogarithmPlotter import __VERSION__
from LogarithmPlotter.util import config, native
from LogarithmPlotter.util.update import check_for_updates
from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex
config.init()
def get_linux_theme():
des = {
"KDE": "Fusion",
"gnome": "Basic",
"lxqt": "Fusion",
"mate": "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"
def run():
if not 'QT_QUICK_CONTROLS_STYLE' in environ:
environ["QT_QUICK_CONTROLS_STYLE"] = {
"linux": get_linux_theme(),
"freebsd": get_linux_theme(),
"win32": "Universal" if os_release == "10" else "Fusion",
"cygwin": "Fusion",
"darwin": "macOS"
}[platform]
dep_time = time()
print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.")
icon_fallbacks = QIcon.fallbackSearchPaths();
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "common")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "objects")))
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);
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(argv)
app.setApplicationName("LogarithmPlotter")
app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
# Installing translators
translator = QTranslator()
# Check if lang is forced.
forcedlang = [p for p in argv if p[:7]=="--lang="]
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))):
app.installTranslator(translator);
# Installing macOS file handler.
macOSFileOpenHandler = None
if platform == "darwin":
macOSFileOpenHandler = native.MacOSFileOpenHandler()
app.installEventFilter(macOSFileOpenHandler)
engine = QQmlApplicationEngine()
global tmpfile
helper = Helper(pwd, tmpfile)
latex = Latex(tempdir)
engine.rootContext().setContextProperty("Helper", helper)
engine.rootContext().setContextProperty("Latex", latex)
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
engine.rootContext().setContextProperty("StartTime", dep_time)
app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
engine.addImportPath(path.realpath(path.join(getcwd(), "qml")))
engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
if not engine.rootObjects():
print("No root object", path.realpath(path.join(getcwd(), "qml")))
print(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
exit(-1)
# Open the current diagram
chdir(pwd)
if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']:
engine.rootObjects()[0].loadDiagram(argv[-1])
chdir(path.dirname(path.realpath(__file__)))
if platform == "darwin":
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
# Check for LaTeX installation if LaTeX support is enabled
if config.getSetting("enable_latex"):
latex.check_latex_install()
# Check for updates
if config.getSetting("check_for_updates"):
check_for_updates(__VERSION__, engine.rootObjects()[0])
exit_code = app.exec()
tempdir.cleanup()
config.save()
exit(exit_code)
if __name__ == "__main__":
run()

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,7 +20,9 @@ import QtQuick
import Qt.labs.platform as Native import Qt.labs.platform as Native
//import QtQuick.Controls 2.15 //import QtQuick.Controls 2.15
import eu.ad5001.MixedMenu 1.1 import eu.ad5001.MixedMenu 1.1
import eu.ad5001.LogarithmPlotter.Common import "js/objects.js" as Objects
import "js/historylib.js" as HistoryLib
import "js/math/latex.js" as LatexJS
/*! /*!
@ -33,6 +35,7 @@ import eu.ad5001.LogarithmPlotter.Common
\sa LogarithmPlotter \sa LogarithmPlotter
*/ */
MenuBar { MenuBar {
property var settingsMenu: settingsSubMenu
Menu { Menu {
title: qsTr("&File") title: qsTr("&File")
@ -41,7 +44,6 @@ MenuBar {
shortcut: StandardKey.Open shortcut: StandardKey.Open
onTriggered: settings.load() onTriggered: settings.load()
icon.name: 'document-open' icon.name: 'document-open'
icon.color: sysPalette.windowText
} }
Action { Action {
@ -49,14 +51,13 @@ MenuBar {
shortcut: StandardKey.Save shortcut: StandardKey.Save
onTriggered: settings.save() onTriggered: settings.save()
icon.name: 'document-save' icon.name: 'document-save'
icon.color: sysPalette.windowText
} }
Action { Action {
text: qsTr("Save &As...") text: qsTr("Save &As...")
shortcut: StandardKey.SaveAs shortcut: StandardKey.SaveAs
onTriggered: settings.saveAs() onTriggered: settings.saveAs()
icon.color: sysPalette.windowText
icon.name: 'document-save-as' icon.name: 'document-save-as'
} }
MenuSeparator { } MenuSeparator { }
Action { Action {
@ -70,7 +71,6 @@ MenuBar {
} }
icon.name: 'application-exit' icon.name: 'application-exit'
icon.color: sysPalette.windowText
} }
} }
@ -79,31 +79,25 @@ MenuBar {
Action { Action {
text: qsTr("&Undo") text: qsTr("&Undo")
shortcut: StandardKey.Undo shortcut: StandardKey.Undo
onTriggered: Modules.History.undo() onTriggered: history.undo()
icon.name: 'edit-undo' icon.name: 'edit-undo'
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
enabled: history.undoCount > 0
} }
Action { Action {
text: qsTr("&Redo") text: qsTr("&Redo")
shortcut: StandardKey.Redo shortcut: StandardKey.Redo
onTriggered: Modules.History.redo() onTriggered: history.redo()
icon.name: 'edit-redo' icon.name: 'edit-redo'
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
enabled: history.redoCount > 0
} }
MenuSeparator { }
Action { Action {
text: qsTr("&Copy plot") text: qsTr("&Copy plot")
shortcut: StandardKey.Copy shortcut: StandardKey.Copy
onTriggered: root.copyDiagramToClipboard() onTriggered: root.copyDiagramToClipboard()
icon.name: 'edit-copy' icon.name: 'edit-copy'
icon.color: sysPalette.windowText
}
MenuSeparator { }
Action {
text: qsTr("&Preferences")
shortcut: StandardKey.Copy
onTriggered: preferences.open()
icon.name: 'settings'
icon.color: sysPalette.windowText
} }
} }
@ -111,68 +105,160 @@ MenuBar {
title: qsTr("&Create") title: qsTr("&Create")
// Services repeater // Services repeater
Repeater { Repeater {
model: Object.keys(Modules.Objects.types) model: Object.keys(Objects.types)
MenuItem { MenuItem {
text: Modules.Objects.types[modelData].displayType() text: Objects.types[modelData].displayType()
visible: Modules.Objects.types[modelData].createable() visible: Objects.types[modelData].createable()
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
icon.name: modelData icon.name: modelData
icon.source: './icons/objects/' + modelData + '.svg' icon.source: './icons/objects/' + modelData + '.svg'
icon.color: sysPalette.buttonText icon.color: sysPalette.buttonText
onTriggered: { onTriggered: {
var newObj = Modules.Objects.createNewRegisteredObject(modelData) var newObj = Objects.createNewRegisteredObject(modelData)
Modules.History.addToHistory(new JS.HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export())) history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
objectLists.update() objectLists.update()
} }
} }
} }
} }
Menu {
id: settingsSubMenu
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'
icon.color: sysPalette.buttonText
}
Action {
id: resetRedoStackMenuSetting
text: qsTr("Reset redo stack automaticly")
checkable: true
checked: Helper.getSettingBool("reset_redo_stack")
onTriggered: Helper.setSettingBool("reset_redo_stack", checked)
icon.name: 'timeline'
icon.color: sysPalette.buttonText
}
Action {
id: enableLatexJSSetting
text: qsTr("Enable LaTeX rendering")
checkable: true
checked: Helper.getSettingBool("enable_latex")
onTriggered: {
Helper.setSettingBool("enable_latex", checked)
LatexJS.enabled = checked
drawCanvas.requestPaint()
}
icon.name: 'Expression'
icon.color: sysPalette.buttonText
}
Menu {
title: qsTr("Expression editor")
Action {
id: autocloseFormulaSetting
text: qsTr("Automatically close parenthesises and brackets")
checkable: true
checked: Helper.getSettingBool("expression_editor.autoclose")
onTriggered: {
Helper.setSettingBool("expression_editor.autoclose", checked)
}
icon.name: 'Text'
icon.color: sysPalette.buttonText
}
Action {
id: colorizeFormulaSetting
text: qsTr("Enable syntax highlighting")
checkable: true
checked: Helper.getSettingBool("expression_editor.colorize")
onTriggered: {
Helper.setSettingBool("expression_editor.colorize", checked)
}
icon.name: 'appearance'
icon.color: sysPalette.buttonText
}
Action {
id: autocompleteFormulaSetting
text: qsTr("Enable autocompletion")
checkable: true
checked: Helper.getSettingBool("autocompletion.enabled")
onTriggered: {
Helper.setSettingBool("autocompletion.enabled", checked)
}
icon.name: 'label'
icon.color: sysPalette.buttonText
}
Menu {
id: colorSchemeSetting
title: qsTr("Color Scheme")
property var schemes: ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"]
Repeater {
model: colorSchemeSetting.schemes
MenuItem {
text: modelData
checkable: true
checked: Helper.getSettingInt("expression_editor.color_scheme") == index
onTriggered: {
parent.children[Helper.getSettingInt("expression_editor.color_scheme")].checked = false
checked = true
Helper.setSettingInt("expression_editor.color_scheme", index)
}
}
}
}
}
}
Menu { Menu {
title: qsTr("&Help") title: qsTr("&Help")
Action { Action {
text: qsTr("&Source code") text: qsTr("&Source code")
icon.name: 'software-sources' icon.name: 'software-sources'
icon.color: sysPalette.windowText
onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter") onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter")
} }
Action { Action {
text: qsTr("&Report a bug") text: qsTr("&Report a bug")
icon.name: 'tools-report-bug' icon.name: 'tools-report-bug'
icon.color: sysPalette.windowText
onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues") onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues")
} }
Action { Action {
text: qsTr("&User manual") text: qsTr("&User manual")
icon.name: 'documentation' icon.name: 'documentation'
icon.color: sysPalette.windowText
onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar") onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar")
} }
Action { Action {
text: qsTr("&Changelog") text: qsTr("&Changelog")
icon.name: 'state-information' icon.name: 'state-information'
icon.color: sysPalette.windowText
onTriggered: changelog.open() onTriggered: changelog.open()
} }
Action { Action {
text: qsTr("&Help translating!") text: qsTr("&Help translating!")
icon.name: 'translate' icon.name: 'translator'
icon.color: sysPalette.windowText
onTriggered: Qt.openUrlExternally("https://hosted.weblate.org/engage/logarithmplotter/") onTriggered: Qt.openUrlExternally("https://hosted.weblate.org/engage/logarithmplotter/")
} }
MenuSeparator { } MenuSeparator { }
Action { Action {
text: qsTr("&Thanks") text: qsTr("&Thanks")
icon.name: 'help-about' icon.name: 'about'
icon.color: sysPalette.windowText
onTriggered: thanksTo.open() onTriggered: thanksTo.open()
} }
Action { Action {
text: qsTr("&About") text: qsTr("&About")
shortcut: StandardKey.HelpContents shortcut: StandardKey.HelpContents
icon.name: 'help-about' icon.name: 'about'
icon.color: sysPalette.windowText
onTriggered: about.open() onTriggered: about.open()
} }
} }

View file

@ -0,0 +1,221 @@
/**
* 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 QtQuick
import QtQml
import QtQuick.Window
import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import "../js/history/common.js" as HistoryCommon
/*!
\qmltype History
\inqmlmodule eu.ad5001.LogarithmPlotter.History
\brief QObject holding persistantly for undo & redo stacks.
\sa HistoryBrowser, historylib
*/
Item {
// Using a QtObject is necessary in order to have proper property propagation in QML
id: historyObj
/*!
\qmlproperty int History::undoCount
Count of undo actions.
*/
property int undoCount: 0
/*!
\qmlproperty int History::redoCount
Count of redo actions.
*/
property int redoCount: 0
/*!
\qmlproperty var History::undoStack
Stack of undo actions.
*/
property var undoStack: []
/*!
\qmlproperty var History::redoStack
Stack of redo actions.
*/
property var redoStack: []
/*!
\qmlproperty bool History::saved
true when no modification was done to the current working file, false otherwise.
*/
property bool saved: true
/*!
\qmlmethod void History::clear()
Clears both undo and redo stacks completly.
*/
function clear() {
undoCount = 0
redoCount = 0
undoStack = []
redoStack = []
}
/*!
\qmlmethod var History::serialize()
Serializes history into JSON-able content.
*/
function serialize() {
let undoSt = [], redoSt = [];
for(let i = 0; i < undoCount; i++)
undoSt.push([
undoStack[i].type(),
undoStack[i].export()
]);
for(let i = 0; i < redoCount; i++)
redoSt.push([
redoStack[i].type(),
redoStack[i].export()
]);
return [undoSt, redoSt]
}
/*!
\qmlmethod void History::unserialize(var undoSt, var redoSt)
Unserializes both \c undoSt stack and \c redoSt stack from serialized content.
*/
function unserialize(undoSt, redoSt) {
clear();
for(let i = 0; i < undoSt.length; i++)
undoStack.push(new HistoryLib.Actions[undoSt[i][0]](...undoSt[i][1]))
for(let i = 0; i < redoSt.length; i++)
redoStack.push(new HistoryLib.Actions[redoSt[i][0]](...redoSt[i][1]))
undoCount = undoSt.length;
redoCount = redoSt.length;
objectLists.update()
}
/*!
\qmlmethod void History::addToHistory(var action)
Adds an instance of historylib.Action to history.
*/
function addToHistory(action) {
if(action instanceof HistoryLib.Action) {
console.log("Added new entry to history: " + action.getReadableString())
undoStack.push(action)
undoCount++;
if(Helper.getSettingBool("reset_redo_stack")) {
redoStack = []
redoCount = 0
}
saved = false
}
}
/*!
\qmlmethod void History::undo(bool updateObjectList = true)
Undoes the historylib.Action at the top of the undo stack and pushes it to the top of the redo stack.
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
*/
function undo(updateObjectList = true) {
if(undoStack.length > 0) {
var action = undoStack.pop()
action.undo()
if(updateObjectList)
objectLists.update()
redoStack.push(action)
undoCount--;
redoCount++;
saved = false
}
}
/*!
\qmlmethod void History::redo(bool updateObjectList = true)
Redoes the historylib.Action at the top of the redo stack and pushes it to the top of the undo stack.
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
*/
function redo(updateObjectList = true) {
if(redoStack.length > 0) {
var action = redoStack.pop()
action.redo()
if(updateObjectList)
objectLists.update()
undoStack.push(action)
undoCount++;
redoCount--;
saved = false
}
}
/*!
\qmlmethod void History::undoMultipleDefered(int toUndoCount)
Undoes several historylib.Action at the top of the undo stack and pushes them to the top of the redo stack.
It undoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
*/
function undoMultipleDefered(toUndoCount) {
undoTimer.toUndoCount = toUndoCount;
undoTimer.start()
if(toUndoCount > 0)
saved = false
}
/*!
\qmlmethod void History::redoMultipleDefered(int toRedoCount)
Redoes several historylib.Action at the top of the redo stack and pushes them to the top of the undo stack.
It redoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
*/
function redoMultipleDefered(toRedoCount) {
redoTimer.toRedoCount = toRedoCount;
redoTimer.start()
if(toRedoCount > 0)
saved = false
}
Timer {
id: undoTimer
interval: 5; running: false; repeat: true
property int toUndoCount: 0
onTriggered: {
if(toUndoCount > 0) {
historyObj.undo(toUndoCount % 4 == 1) // Only redraw once every 4 changes.
toUndoCount--;
} else {
running = false;
}
}
}
Timer {
id: redoTimer
interval: 5; running: false; repeat: true
property int toRedoCount: 0
onTriggered: {
if(toRedoCount > 0) {
historyObj.redo(toRedoCount % 4 == 1) // Only redraw once every 4 changes.
toRedoCount--;
} else {
running = false;
}
}
}
Component.onCompleted: {
HistoryLib.history = historyObj
HistoryCommon.themeTextColor = sysPalette.windowText
HistoryCommon.imageDepth = Screen.devicePixelRatio
}
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,15 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
pragma ComponentBehavior: Bound
import QtQuick.Controls import QtQuick.Controls
import QtQuick import QtQuick
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/utils.js" as Utils
/*! /*!
\qmltype Browser \qmltype HistoryBrowser
\inqmlmodule eu.ad5001.LogarithmPlotter.History \inqmlmodule eu.ad5001.LogarithmPlotter.History
\brief Tab of the drawer that allows to navigate through the undo and redo history. \brief Tab of the drawer that allows to navigate through the undo and redo history.
@ -49,24 +48,11 @@ Item {
*/ */
property bool darkTheme: isDarkTheme() property bool darkTheme: isDarkTheme()
/*!
\qmlproperty int HistoryBrowser::undoCount
Number of actions in the undo stack.
*/
property int undoCount: 0
/*!
\qmlproperty int HistoryBrowser::redoCount
Number of actions in the redo stack.
*/
property int redoCount: 0
Setting.TextSetting { Setting.TextSetting {
id: filterInput id: filterInput
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.rightMargin: 5
placeholderText: qsTr("Filter...") placeholderText: qsTr("Filter...")
category: "all" category: "all"
} }
@ -90,22 +76,19 @@ Item {
id: redoColumn id: redoColumn
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
width: historyBrowser.actionWidth width: actionWidth
Repeater { Repeater {
model: historyBrowser.redoCount model: history.redoCount
SingleItem { HistoryItem {
id: redoButton id: redoButton
width: historyBrowser.actionWidth width: actionWidth
//height: actionHeight //height: actionHeight
isRedo: true isRedo: true
idx: index
darkTheme: historyBrowser.darkTheme darkTheme: historyBrowser.darkTheme
hidden: !(filterInput.value == "" || content.includes(filterInput.value)) hidden: !(filterInput.value == "" || content.includes(filterInput.value))
onClicked: {
redoTimer.toRedoCount = Modules.History.redoStack.length-index
redoTimer.start()
}
} }
} }
} }
@ -118,14 +101,14 @@ Item {
transform: Rotation { origin.x: 30; origin.y: 30; angle: 270} transform: Rotation { origin.x: 30; origin.y: 30; angle: 270}
height: 70 height: 70
width: 20 width: 20
visible: historyBrowser.redoCount > 0 visible: history.redoCount > 0
} }
Rectangle { Rectangle {
id: nowRect id: nowRect
anchors.right: parent.right anchors.right: parent.right
anchors.top: redoColumn.bottom anchors.top: redoColumn.bottom
width: historyBrowser.actionWidth width: actionWidth
height: 40 height: 40
color: sysPalette.highlight color: sysPalette.highlight
Text { Text {
@ -141,24 +124,20 @@ Item {
id: undoColumn id: undoColumn
anchors.right: parent.right anchors.right: parent.right
anchors.top: nowRect.bottom anchors.top: nowRect.bottom
width: historyBrowser.actionWidth width: actionWidth
Repeater { Repeater {
model: historyBrowser.undoCount model: history.undoCount
SingleItem { HistoryItem {
id: undoButton id: undoButton
width: historyBrowser.actionWidth width: actionWidth
//height: actionHeight //height: actionHeight
isRedo: false isRedo: false
idx: index
darkTheme: historyBrowser.darkTheme darkTheme: historyBrowser.darkTheme
hidden: !(filterInput.value == "" || content.includes(filterInput.value)) hidden: !(filterInput.value == "" || content.includes(filterInput.value))
onClicked: {
undoTimer.toUndoCount = +index+1
undoTimer.start()
}
} }
} }
} }
@ -171,39 +150,7 @@ Item {
transform: Rotation { origin.x: 30; origin.y: 30; angle: 270} transform: Rotation { origin.x: 30; origin.y: 30; angle: 270}
height: 60 height: 60
width: 20 width: 20
visible: historyBrowser.undoCount > 0 visible: history.undoCount > 0
}
}
}
Timer {
id: undoTimer
interval: 5; running: false; repeat: true
property int toUndoCount: 0
onTriggered: {
if(toUndoCount > 0) {
Modules.History.undo()
if(toUndoCount % 3 === 1)
Modules.Canvas.requestPaint()
toUndoCount--;
} else {
running = false;
}
}
}
Timer {
id: redoTimer
interval: 5; running: false; repeat: true
property int toRedoCount: 0
onTriggered: {
if(toRedoCount > 0) {
Modules.History.redo()
if(toRedoCount % 3 === 1)
Modules.Canvas.requestPaint()
toRedoCount--;
} else {
running = false;
} }
} }
} }
@ -216,18 +163,6 @@ Item {
let hex = sysPalette.windowText.toString() let hex = sysPalette.windowText.toString()
// We only check the first parameter, as on all normal OSes, text color is grayscale. // We only check the first parameter, as on all normal OSes, text color is grayscale.
return parseInt(hex.substr(1,2), 16) > 128 return parseInt(hex.substr(1,2), 16) > 128
}
Component.onCompleted: {
Modules.History.initialize({
helper: Helper,
themeTextColor: sysPalette.windowText.toString(),
imageDepth: Screen.devicePixelRatio,
fontSize: 14
})
Modules.History.on("cleared loaded added undone redone", () => {
undoCount = Modules.History.undoStack.length
redoCount = Modules.History.redoStack.length
})
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,13 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick
import Qt5Compat.GraphicalEffects
import "../js/utils.js" as Utils
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
/*! /*!
\qmltype SingleItem \qmltype HistoryItem
\inqmlmodule eu.ad5001.LogarithmPlotter.History \inqmlmodule eu.ad5001.LogarithmPlotter.History
\brief Item representing an history action. \brief Item representing an history action.
@ -40,17 +42,17 @@ Button {
\qmlproperty bool HistoryItem::isRedo \qmlproperty bool HistoryItem::isRedo
true if the action is in the redo stack, false othewise. true if the action is in the redo stack, false othewise.
*/ */
required property bool isRedo property bool isRedo
/*! /*!
\qmlproperty int HistoryItem::index \qmlproperty int HistoryItem::idx
Index of the item within the HistoryBrowser list. Index of the item within the HistoryBrowser list.
*/ */
required property int index property int idx
/*! /*!
\qmlproperty bool HistoryItem::darkTheme \qmlproperty bool HistoryItem::darkTheme
true when the system is running with a dark theme, false otherwise. true when the system is running with a dark theme, false otherwise.
*/ */
required property bool darkTheme property bool darkTheme
/*! /*!
\qmlproperty bool HistoryItem::hidden \qmlproperty bool HistoryItem::hidden
true when the item is filtered out, false otherwise. true when the item is filtered out, false otherwise.
@ -60,7 +62,7 @@ Button {
\qmlproperty int HistoryItem::historyAction \qmlproperty int HistoryItem::historyAction
Associated history action. Associated history action.
*/ */
readonly property var historyAction: isRedo ? Modules.History.redoStack.at(index) : Modules.History.undoStack.at(-index-1) readonly property var historyAction: isRedo ? history.redoStack[idx] : history.undoStack[history.undoCount-idx-1]
/*! /*!
\qmlproperty int HistoryItem::actionHeight \qmlproperty int HistoryItem::actionHeight
@ -81,11 +83,12 @@ Button {
height: hidden ? 8 : Math.max(actionHeight, label.height + 15) height: hidden ? 8 : Math.max(actionHeight, label.height + 15)
Rectangle { LinearGradient {
anchors.fill: parent anchors.fill: parent
//opacity: hidden ? 0.6 : 1 //opacity: hidden ? 0.6 : 1
start: Qt.point(0, 0)
end: Qt.point(parent.width, 0)
gradient: Gradient { gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop { position: 0.1; color: "transparent" } GradientStop { position: 0.1; color: "transparent" }
GradientStop { position: 1.5; color: clr } GradientStop { position: 1.5; color: clr }
} }
@ -113,23 +116,10 @@ Button {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: !hidden visible: !hidden
font.pixelSize: 14 font.pixelSize: 14
text: "" text: historyAction.getHTMLString().replace(/\$\{tag_color\}/g, clr)
textFormat: Text.RichText textFormat: Text.RichText
clip: true clip: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Component.onCompleted: function() {
// Render HTML, might be string, but could also be a promise
const html = historyAction.getHTMLString()
if(typeof html === "string") {
label.text = html.replace(/\$\{tag_color\}/g, clr)
} else {
// Promise! We need to way to wait for it to be completed.
html.then(rendered => {
label.text = rendered.replace(/\$\{tag_color\}/g, clr)
})
}
}
} }
Rectangle { Rectangle {
@ -145,6 +135,13 @@ Button {
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: 200 ToolTip.delay: 200
ToolTip.text: content ToolTip.text: content
onClicked: {
if(isRedo)
history.redoMultipleDefered(history.redoCount-idx)
else
history.undoMultipleDefered(+idx+1)
}
} }

View file

@ -0,0 +1,5 @@
module eu.ad5001.LogarithmPlotter.History
History 1.0 History.qml
HistoryBrowser 1.0 HistoryBrowser.qml
HistoryItem 1.0 HistoryItem.qml

View file

@ -0,0 +1,493 @@
/**
* 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 QtQuick
import Qt.labs.platform as Native
import "js/objects.js" as Objects
import "js/utils.js" as Utils
import "js/mathlib.js" as MathLib
/*!
\qmltype LogGraphCanvas
\inqmlmodule eu.ad5001.LogarithmPlotter
\brief Canvas used to display the diagram.
Provides a customized canvas with several helper methods to be used by objects.
\sa LogarithmPlotter, PickLocationOverlay
*/
Canvas {
id: canvas
anchors.top: separator.bottom
anchors.left: parent.left
height: parent.height - 90
width: parent.width
/*!
\qmlproperty double LogGraphCanvas::xmin
Minimum x of the diagram, provided from settings.
\sa Settings
*/
property double xmin: 0
/*!
\qmlproperty double LogGraphCanvas::ymax
Maximum y of the diagram, provided from settings.
\sa Settings
*/
property double ymax: 0
/*!
\qmlproperty double LogGraphCanvas::xzoom
Zoom on the x axis of the diagram, provided from settings.
\sa Settings
*/
property double xzoom: 10
/*!
\qmlproperty double LogGraphCanvas::yzoom
Zoom on the y axis of the diagram, provided from settings.
\sa Settings
*/
property double yzoom: 10
/*!
\qmlproperty string LogGraphCanvas::xaxisstep
Step of the x axis graduation, provided from settings.
\note: Only available in non-logarithmic mode.
\sa Settings
*/
property string xaxisstep: "4"
/*!
\qmlproperty string LogGraphCanvas::yaxisstep
Step of the y axis graduation, provided from settings.
\sa Settings
*/
property string yaxisstep: "4"
/*!
\qmlproperty string LogGraphCanvas::xlabel
Label used on the x axis, provided from settings.
\sa Settings
*/
property string xlabel: ""
/*!
\qmlproperty string LogGraphCanvas::ylabel
Label used on the y axis, provided from settings.
\sa Settings
*/
property string ylabel: ""
/*!
\qmlproperty double LogGraphCanvas::linewidth
Width of lines that will be drawn into the canvas, provided from settings.
\sa Settings
*/
property double linewidth: 1
/*!
\qmlproperty double LogGraphCanvas::textsize
Font size of the text that will be drawn into the canvas, provided from settings.
\sa Settings
*/
property double textsize: 14
/*!
\qmlproperty bool LogGraphCanvas::logscalex
true if the canvas should be in logarithmic mode, false otherwise.
Provided from settings.
\sa Settings
*/
property bool logscalex: false
/*!
\qmlproperty bool LogGraphCanvas::showxgrad
true if the x graduation should be shown, false otherwise.
Provided from settings.
\sa Settings
*/
property bool showxgrad: false
/*!
\qmlproperty bool LogGraphCanvas::showygrad
true if the y graduation should be shown, false otherwise.
Provided from settings.
\sa Settings
*/
property bool showygrad: false
/*!
\qmlproperty int LogGraphCanvas::maxgradx
Max power of the logarithmic scaled on the x axis in logarithmic mode.
*/
property int maxgradx: 20
/*!
\qmlproperty var LogGraphCanvas::yaxisstepExpr
Expression for the y axis step (used to create labels).
*/
property var yaxisstepExpr: (new MathLib.Expression(`x*(${yaxisstep})`))
/*!
\qmlproperty double LogGraphCanvas::yaxisstep1
Value of the for the y axis step.
*/
property double yaxisstep1: yaxisstepExpr.execute(1)
/*!
\qmlproperty int LogGraphCanvas::drawMaxY
Minimum value of y that should be drawn onto the canvas.
*/
property int drawMaxY: Math.ceil(Math.max(Math.abs(ymax), Math.abs(px2y(canvasSize.height)))/yaxisstep1)
/*!
\qmlproperty var LogGraphCanvas::xaxisstepExpr
Expression for the x axis step (used to create labels).
*/
property var xaxisstepExpr: (new MathLib.Expression(`x*(${xaxisstep})`))
/*!
\qmlproperty double LogGraphCanvas::xaxisstep1
Value of the for the x axis step.
*/
property double xaxisstep1: xaxisstepExpr.execute(1)
/*!
\qmlproperty int LogGraphCanvas::drawMaxX
Maximum value of x that should be drawn onto the canvas.
*/
property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1)
/*!
\qmlproperty var LogGraphCanvas::imageLoaders
Dictionary of format {image: [callback.image data]} containing data for defered image loading.
*/
property var imageLoaders: {}
/*!
\qmlproperty var LogGraphCanvas::ctx
Cache for the 2D context so that it may be used asynchronously.
*/
property var ctx
Component.onCompleted: imageLoaders = {}
Native.MessageDialog {
id: drawingErrorDialog
title: qsTranslate("expression", "LogarithmPlotter - Drawing error")
text: ""
function showDialog(objType, objName, error) {
text = qsTranslate("error", "Error while attempting to draw %1 %2:\n%3\n\nUndoing last change.").arg(objType).arg(objName).arg(error)
open()
}
}
onPaint: function(rect) {
//console.log('Redrawing')
if(rect.width == canvas.width) { // Redraw full canvas
ctx = getContext("2d");
reset(ctx)
drawGrille(ctx)
drawAxises(ctx)
drawLabels(ctx)
ctx.lineWidth = linewidth
for(var objType in Objects.currentObjects) {
for(var obj of Objects.currentObjects[objType]){
ctx.strokeStyle = obj.color
ctx.fillStyle = obj.color
if(obj.visible)
try {
obj.draw(canvas, ctx)
} catch(e) {
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
drawingErrorDialog.showDialog(objType, obj.name, e.message)
history.undo()
}
}
}
ctx.lineWidth = 1
}
}
onImageLoaded: {
Object.keys(imageLoaders).forEach((key) => {
if(isImageLoaded(key)) {
// Calling callback
imageLoaders[key][0](canvas, ctx, imageLoaders[key][1])
delete imageLoaders[key]
}
})
}
/*!
\qmlmethod void LogGraphCanvas::reset(var ctx)
Resets the canvas to a blank one with default setting using 2D \c ctx.
*/
function reset(ctx){
// Reset
ctx.fillStyle = "#FFFFFF"
ctx.strokeStyle = "#000000"
ctx.font = `${canvas.textsize}px sans-serif`
ctx.fillRect(0,0,width,height)
}
// Drawing the log based graph
/*!
\qmlmethod void LogGraphCanvas::drawGrille(var ctx)
Draws the grid using 2D \c ctx.
*/
function drawGrille(ctx) {
ctx.strokeStyle = "#C0C0C0"
if(logscalex) {
for(var xpow = -maxgradx; xpow <= maxgradx; xpow++) {
for(var xmulti = 1; xmulti < 10; xmulti++) {
drawXLine(ctx, Math.pow(10, xpow)*xmulti)
}
}
} else {
for(var x = 0; x < drawMaxX; x+=1) {
drawXLine(ctx, x*xaxisstep1)
drawXLine(ctx, -x*xaxisstep1)
}
}
for(var y = 0; y < drawMaxY; y+=1) {
drawYLine(ctx, y*yaxisstep1)
drawYLine(ctx, -y*yaxisstep1)
}
}
/*!
\qmlmethod void LogGraphCanvas::drawAxises(var ctx)
Draws the graph axises using 2D \c ctx.
*/
function drawAxises(ctx) {
ctx.strokeStyle = "#000000"
var axisypos = logscalex ? 1 : 0
drawXLine(ctx, axisypos)
drawYLine(ctx, 0)
var axisypx = x2px(axisypos) // X coordinate of Y axis
var axisxpx = y2px(0) // Y coordinate of X axis
// Drawing arrows
drawLine(ctx, axisypx, 0, axisypx-10, 10)
drawLine(ctx, axisypx, 0, axisypx+10, 10)
drawLine(ctx, canvasSize.width, axisxpx, canvasSize.width-10, axisxpx-10)
drawLine(ctx, canvasSize.width, axisxpx, canvasSize.width-10, axisxpx+10)
}
/*!
\qmlmethod void LogGraphCanvas::drawLabels(var ctx)
Draws all labels (graduation & axises labels) using 2D \c ctx.
*/
function drawLabels(ctx) {
var axisypx = x2px(logscalex ? 1 : 0) // X coordinate of Y axis
var axisxpx = y2px(0) // Y coordinate of X axis
// Labels
ctx.fillStyle = "#000000"
ctx.font = `${canvas.textsize}px sans-serif`
ctx.fillText(ylabel, axisypx+10, 24)
var textSize = ctx.measureText(xlabel).width
ctx.fillText(xlabel, canvasSize.width-14-textSize, axisxpx-5)
// Axis graduation labels
ctx.font = `${canvas.textsize-4}px sans-serif`
var txtMinus = ctx.measureText('-').width
if(showxgrad) {
if(logscalex) {
for(var xpow = -maxgradx; xpow <= maxgradx; xpow+=1) {
var textSize = ctx.measureText("10"+Utils.textsup(xpow)).width
if(xpow != 0)
drawVisibleText(ctx, "10"+Utils.textsup(xpow), x2px(Math.pow(10,xpow))-textSize/2, axisxpx+16+(6*(xpow==1)))
}
} else {
for(var x = 1; x < drawMaxX; x += 1) {
var drawX = x*xaxisstep1
var txtX = xaxisstepExpr.simplify(x).replace(/^\((.+)\)$/, '$1')
var textSize = measureText(ctx, txtX, 6).height
drawVisibleText(ctx, txtX, x2px(drawX)-4, axisxpx+textsize/2+textSize)
drawVisibleText(ctx, '-'+txtX, x2px(-drawX)-4, axisxpx+textsize/2+textSize)
}
}
}
if(showygrad) {
for(var y = 0; y < drawMaxY; y += 1) {
var drawY = y*yaxisstep1
var txtY = yaxisstepExpr.simplify(y).replace(/^\((.+)\)$/, '$1')
var textSize = ctx.measureText(txtY).width
drawVisibleText(ctx, txtY, axisypx-6-textSize, y2px(drawY)+4+(10*(y==0)))
if(y != 0)
drawVisibleText(ctx, '-'+txtY, axisypx-6-textSize-txtMinus, y2px(-drawY)+4)
}
}
ctx.fillStyle = "#FFFFFF"
}
/*!
\qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x)
Draws an horizontal line at \c x plot coordinate using 2D \c ctx.
*/
function drawXLine(ctx, x) {
if(isVisible(x, ymax)) {
drawLine(ctx, x2px(x), 0, x2px(x), canvasSize.height)
}
}
/*!
\qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x)
Draws an vertical line at \c y plot coordinate using 2D \c ctx.
*/
function drawYLine(ctx, y) {
if(isVisible(xmin, y)) {
drawLine(ctx, 0, y2px(y), canvasSize.width, y2px(y))
}
}
/*!
\qmlmethod void LogGraphCanvas::drawVisibleText(var ctx, string text, double x, double y)
Writes multline \c text onto the canvas using 2D \c ctx.
\note The \c x and \c y properties here are relative to the canvas, not the plot.
*/
function drawVisibleText(ctx, text, x, y) {
if(x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height) {
text.toString().split("\n").forEach(function(txt, i){
ctx.fillText(txt, x, y+(canvas.textsize*i))
})
}
}
/*!
\qmlmethod void LogGraphCanvas::drawVisibleImage(var ctx, var image, double x, double y)
Draws an \c image onto the canvas using 2D \c ctx.
\note The \c x, \c y \c width and \c height properties here are relative to the canvas, not the plot.
*/
function drawVisibleImage(ctx, image, x, y, width, height) {
//console.log("Drawing image", isImageLoaded(image), isImageError(image))
markDirty(Qt.rect(x, y, width, height));
ctx.drawImage(image, x, y, width, height)
/*if(true || (x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height)) {
}*/
}
/*!
\qmlmethod var LogGraphCanvas::measureText(var ctx, string text)
Measures the wicth and height of a multiline \c text that would be drawn onto the canvas using 2D \c ctx.
Return format: dictionary {"width": width, "height": height}
*/
function measureText(ctx, text) {
let theight = 0
let twidth = 0
let defaultHeight = ctx.measureText("M").width // Approximate but good enough!
text.split("\n").forEach(function(txt, i){
theight += defaultHeight
if(ctx.measureText(txt).width > twidth) twidth = ctx.measureText(txt).width
})
return {'width': twidth, 'height': theight}
}
/*!
\qmlmethod double LogGraphCanvas::x2px(double x)
Converts an \c x coordinate to it's relative position on the canvas.
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
*/
function x2px(x) {
if(logscalex) {
var logxmin = Math.log(xmin)
return (Math.log(x)-logxmin)*xzoom
} else return (x - xmin)*xzoom
}
/*!
\qmlmethod double LogGraphCanvas::y2px(double y)
Converts an \c y coordinate to it's relative position on the canvas.
The y axis not supporting logarithmic scale, it only support linear convertion.
*/
function y2px(y) {
return (ymax-y)*yzoom
}
/*!
\qmlmethod double LogGraphCanvas::px2x(double px)
Converts an x \c px position on the canvas to it's corresponding coordinate on the plot.
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
*/
function px2x(px) {
if(logscalex) {
return Math.exp(px/xzoom+Math.log(xmin))
} else return (px/xzoom+xmin)
}
/*!
\qmlmethod double LogGraphCanvas::px2x(double px)
Converts an x \c px position on the canvas to it's corresponding coordinate on the plot.
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
*/
function px2y(px) {
return -(px/yzoom-ymax)
}
/*!
\qmlmethod bool LogGraphCanvas::isVisible(double x, double y)
Checks whether a plot point (\c x, \c y) is visible or not on the canvas.
*/
function isVisible(x, y) {
return (x2px(x) >= 0 && x2px(x) <= canvasSize.width) && (y2px(y) >= 0 && y2px(y) <= canvasSize.height)
}
/*!
\qmlmethod bool LogGraphCanvas::drawLine(var ctx, double x1, double y1, double x2, double y2)
Draws a line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
*/
function drawLine(ctx, x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
/*!
\qmlmethod bool LogGraphCanvas::drawDashedLine2(var ctx, double x1, double y1, double x2, double y2)
Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
*/
function drawDashedLine2(ctx, x1, y1, x2, y2, dashPxSize = 5) {
ctx.setLineDash([dashPxSize, dashPxSize]);
drawLine(ctx, x1, y1, x2, y2)
ctx.setLineDash([]);
}
/*!
\qmlmethod bool LogGraphCanvas::drawDashedLine(var ctx, double x1, double y1, double x2, double y2)
Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
(Legacy slower method)
*/
function drawDashedLine(ctx, x1, y1, x2, y2, dashPxSize = 10) {
var distance = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))
var progPerc = dashPxSize/distance
ctx.beginPath();
ctx.moveTo(x1, y1);
for(var i = 0; i < 1; i += progPerc) {
ctx.lineTo(x1-(x1-x2)*i, y1-(y1-y2)*i)
ctx.moveTo(x1-(x1-x2)*(i+progPerc/2), y1-(y1-y2)*(i+progPerc/2))
}
ctx.stroke();
}
/*!
\qmlmethod var LogGraphCanvas::renderLatexImage(string ltxText, color)
Renders latex markup \c ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
*/
function renderLatexImage(ltxText, color, callback) {
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, textsize, color).split(",")
let imgData = {
"source": ltxSrc,
"width": parseFloat(ltxWidth),
"height": parseFloat(ltxHeight)
};
if(!isImageLoaded(ltxSrc) && !isImageLoading(ltxSrc)){
// Wait until the image is loaded to callback.
loadImage(ltxSrc)
imageLoaders[ltxSrc] = [callback, imgData]
} else {
// Callback directly
callback(canvas, ctx, imgData)
}
}
}

View file

@ -0,0 +1,378 @@
/**
* 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 QtQml
import QtQuick.Controls
import eu.ad5001.MixedMenu 1.1
import QtQuick.Layouts 1.12
import QtQuick
// Auto loading all objects.
import "js/objs/autoload.js" as ALObjects
import "js/objects.js" as Objects
import "js/math/latex.js" as LatexJS
import eu.ad5001.LogarithmPlotter.History 1.0
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
/*!
\qmltype LogarithmPlotter
\inqmlmodule eu.ad5001.LogarithmPlotter
\brief Main window of LogarithmPlotter
\sa AppMenuBar, History, GreetScreen, Changelog, Alert, ObjectLists, Settings, HistoryBrowser, LogGraphCanvas, PickLocationOverlay.
*/
ApplicationWindow {
id: root
visible: true
width: 1000
height: 500
color: sysPalette.window
title: "LogarithmPlotter " + (settings.saveFilename != "" ? " - " + settings.saveFilename.split('/').pop() : "") + (history.saved ? "" : "*")
SystemPalette {
id: sysPalette; colorGroup: SystemPalette.Active
Component.onCompleted: {
// LatexJS initialization.
LatexJS.enabled = Helper.getSettingBool("enable_latex")
LatexJS.Renderer = Latex
LatexJS.defaultColor = sysPalette.windowText
}
}
SystemPalette { id: sysPaletteIn; colorGroup: SystemPalette.Disabled }
menuBar: appMenu.trueItem
AppMenuBar {id: appMenu}
History { id: history }
Popup.GreetScreen {}
Popup.Changelog {id: changelog}
Popup.About {id: about}
Popup.ThanksTo {id: thanksTo}
Popup.Alert {
id: alert
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
z: 3
}
Item {
id: sidebar
width: 300
height: parent.height
//y: root.menuBar.height
readonly property bool inPortrait: root.width < root.height
/*modal: true// inPortrait
interactive: inPortrait
position: inPortrait ? 0 : 1
*/
visible: !inPortrait
TabBar {
id: sidebarSelector
width: parent.width
anchors.top: parent.top
TabButton {
text: qsTr("Objects")
icon.name: 'polygon-add-nodes'
icon.color: sysPalette.windowText
//height: 24
}
TabButton {
text: qsTr("Settings")
icon.name: 'preferences-system-symbolic'
icon.color: sysPalette.windowText
//height: 24
}
TabButton {
text: qsTr("History")
icon.name: 'view-history'
icon.color: sysPalette.windowText
//height: 24
}
}
StackLayout {
id: sidebarContents
anchors.top: sidebarSelector.bottom
anchors.left: parent.left
anchors.topMargin: 5
anchors.leftMargin: 5
anchors.bottom: parent.bottom
//anchors.bottomMargin: sidebarSelector.height
width: parent.width - 5
currentIndex: sidebarSelector.currentIndex
z: -1
clip: true
ObjectLists {
id: objectLists
onChanged: drawCanvas.requestPaint()
}
Settings {
id: settings
canvas: drawCanvas
onChanged: drawCanvas.requestPaint()
}
HistoryBrowser {
id: historyBrowser
}
}
}
LogGraphCanvas {
id: drawCanvas
anchors.top: parent.top
anchors.left: sidebar.inPortrait ? parent.left : sidebar.right
height: parent.height
width: sidebar.inPortrait ? parent.width : parent.width - sidebar.width//*sidebar.position
x: sidebar.width//*sidebar.position
xmin: settings.xmin
ymax: settings.ymax
xzoom: settings.xzoom
yzoom: settings.yzoom
xlabel: settings.xlabel
ylabel: settings.ylabel
yaxisstep: settings.yaxisstep
xaxisstep: settings.xaxisstep
logscalex: settings.logscalex
linewidth: settings.linewidth
textsize: settings.textsize
showxgrad: settings.showxgrad
showygrad: settings.showygrad
property bool firstDrawDone: false
onPainted: if(!firstDrawDone) {
firstDrawDone = true;
console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms")
if(TestBuild == true) {
console.log("Plot drawn in canvas, terminating test of build in 100ms.")
testBuildTimer.start()
}
}
ViewPositionChangeOverlay {
id: viewPositionChanger
anchors.fill: parent
canvas: parent
settingsInstance: settings
}
PickLocationOverlay {
id: positionPicker
anchors.fill: parent
canvas: parent
}
}
/*!
\qmlmethod void LogarithmPlotter::saveDiagram(string filename)
Saves the diagram to a certain \c filename.
*/
function saveDiagram(filename) {
if(['lpf'].indexOf(filename.split('.')[filename.split('.').length-1]) == -1)
filename += '.lpf'
settings.saveFilename = filename
var objs = {}
for(var objType in Objects.currentObjects){
objs[objType] = []
for(var obj of Objects.currentObjects[objType]) {
objs[objType].push(obj.export())
}
}
Helper.write(filename, JSON.stringify({
"xzoom": settings.xzoom,
"yzoom": settings.yzoom,
"xmin": settings.xmin,
"ymax": settings.ymax,
"xaxisstep": settings.xaxisstep,
"yaxisstep": settings.yaxisstep,
"xaxislabel": settings.xlabel,
"yaxislabel": settings.ylabel,
"logscalex": settings.logscalex,
"linewidth": settings.linewidth,
"showxgrad": settings.showxgrad,
"showygrad": settings.showygrad,
"textsize": settings.textsize,
"history": history.serialize(),
"width": root.width,
"height": root.height,
"objects": objs,
"type": "logplotv1"
}))
alert.show(qsTr("Saved plot to '%1'.").arg(filename.split("/").pop()))
history.saved = true
}
/*!
\qmlmethod void LogarithmPlotter::saveDiagram(string filename)
Loads the diagram from a certain \c filename.
*/
function loadDiagram(filename) {
let basename = filename.split("/").pop()
alert.show(qsTr("Loading file '%1'.").arg(basename))
let data = JSON.parse(Helper.load(filename))
let error = "";
if(Object.keys(data).includes("type") && data["type"] == "logplotv1") {
history.clear()
// Importing settings
settings.saveFilename = filename
settings.xzoom = data["xzoom"]
settings.yzoom = data["yzoom"]
settings.xmin = data["xmin"]
settings.ymax = data["ymax"]
settings.xaxisstep = data["xaxisstep"]
settings.yaxisstep = data["yaxisstep"]
settings.xlabel = data["xaxislabel"]
settings.ylabel = data["yaxislabel"]
settings.logscalex = data["logscalex"]
if("showxgrad" in data)
settings.showxgrad = data["showxgrad"]
if("showygrad" in data)
settings.textsize = data["showygrad"]
if("linewidth" in data)
settings.linewidth = data["linewidth"]
if("textsize" in data)
settings.textsize = data["textsize"]
root.height = data["height"]
root.width = data["width"]
// Importing objects
Objects.currentObjects = {}
Object.keys(Objects.currentObjectsByName).forEach(key => {
delete Objects.currentObjectsByName[key];
// Required to keep the same reference for the copy of the object used in expression variable detection.
// Another way would be to change the reference as well, but I feel like the code would be less clean.
})
for(let objType in data['objects']) {
if(Object.keys(Objects.types).indexOf(objType) > -1) {
Objects.currentObjects[objType] = []
for(let objData of data['objects'][objType]) {
let obj = new Objects.types[objType](...objData)
Objects.currentObjects[objType].push(obj)
Objects.currentObjectsByName[obj.name] = obj
}
} else {
error += qsTr("Unknown object type: %1.").arg(objType) + "\n";
}
}
// Updating object dependencies.
for(let objName in Objects.currentObjectsByName)
Objects.currentObjectsByName[objName].update()
// Importing history
if("history" in data)
history.unserialize(...data["history"])
// Refreshing sidebar
if(sidebarSelector.currentIndex == 0) {
// For some reason, if we load a file while the tab is on object,
// we get stuck in a Qt-side loop? Qt bug or side-effect here, I don't know.
sidebarSelector.currentIndex = 1
objectLists.update()
delayRefreshTimer.start()
} else {
objectLists.update()
}
} else {
error = qsTr("Invalid file provided.")
}
if(error != "") {
console.log(error)
alert.show(qsTr("Could not save file: ") + error)
// TODO: Error handling
return
}
drawCanvas.requestPaint()
alert.show(qsTr("Loaded file '%1'.").arg(basename))
history.saved = true
}
Timer {
id: delayRefreshTimer
repeat: false
interval: 1
onTriggered: sidebarSelector.currentIndex = 0
}
Timer {
id: testBuildTimer
repeat: false
interval: 100
onTriggered: Qt.quit() // Quit after paint on test build
}
onClosing: function(close) {
if(!history.saved) {
close.accepted = false
appMenu.openSaveUnsavedChangesDialog()
}
}
/*!
\qmlmethod void LogarithmPlotter::copyDiagramToClipboard()
Copies the current diagram image to the clipboard.
*/
function copyDiagramToClipboard() {
var file = Helper.gettmpfile()
drawCanvas.save(file)
Helper.copyImageToClipboard()
alert.show(qsTr("Copied plot screenshot to clipboard!"))
}
/*!
\qmlmethod void LogarithmPlotter::showAlert(string alertText)
Shows an alert on the diagram.
*/
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: Qt.openUrlExternally("https://dev.apps.ad5001.eu/logarithmplotter")
}
}
/*!
\qmlmethod void LogarithmPlotter::showUpdateMenu()
Shows the update menu in the AppMenuBar.
*/
function showUpdateMenu() {
appMenu.addMenu(updateMenu)
}
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,7 +20,10 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Qt.labs.platform as Native import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Common import "../../js/objects.js" as Objects
import "../../js/historylib.js" as HistoryLib
import "../../js/utils.js" as Utils
import "../../js/mathlib.js" as MathLib
/*! /*!
\qmltype CustomPropertyList \qmltype CustomPropertyList
@ -47,7 +50,7 @@ Repeater {
*/ */
property var positionPicker property var positionPicker
readonly property var textTypes: ['Domain', 'string', 'number', 'int'] readonly property var textTypes: ['Domain', 'string', 'number']
readonly property var comboBoxTypes: ['ObjectType', 'Enum'] readonly property var comboBoxTypes: ['ObjectType', 'Enum']
readonly property var listTypes: ['List', 'Dict'] readonly property var listTypes: ['List', 'Dict']
@ -74,13 +77,13 @@ Repeater {
Setting.ExpressionEditor { Setting.ExpressionEditor {
height: 30 height: 30
label: propertyLabel label: propertyLabel
icon: `properties/${propertyIcon}.svg` icon: `settings/custom/${propertyIcon}.svg`
defValue: JS.Utils.simplifyExpression(obj[propertyName].toEditableString()) defValue: Utils.simplifyExpression(obj[propertyName].toEditableString())
self: obj.name self: obj.name
variables: propertyType.variables variables: propertyType.variables
onChanged: function(newExpr) { onChanged: function(newExpr) {
if(obj[propertyName].toString() != newExpr.toString()) { if(obj[propertyName].toString() != newExpr.toString()) {
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
obj.name, objType, propertyName, obj.name, objType, propertyName,
obj[propertyName], newExpr obj[propertyName], newExpr
)) ))
@ -99,31 +102,27 @@ Repeater {
Setting.TextSetting { Setting.TextSetting {
height: 30 height: 30
label: propertyLabel label: propertyLabel
icon: `properties/${propertyIcon}.svg` icon: `settings/custom/${propertyIcon}.svg`
min: propertyType == "int" ? 0 : -Infinity
isInt: propertyType == "int"
isDouble: propertyType == "number" isDouble: propertyType == "number"
defValue: obj[propertyName] == null ? '' : obj[propertyName].toString() defValue: obj[propertyName] == null ? '' : obj[propertyName].toString()
category: { category: {
return { return {
"Domain": "domain", "Domain": "domain",
"string": "all", "string": "all",
"number": "all", "number": "all"
"int": "all",
}[propertyType] }[propertyType]
} }
onChanged: function(newValue) { onChanged: function(newValue) {
try { try {
var newValueParsed = { var newValueParsed = {
"Domain": () => JS.MathLib.parseDomain(newValue), "Domain": () => MathLib.parseDomain(newValue),
"string": () => newValue, "string": () => newValue,
"number": () => newValue, "number": () => parseFloat(newValue)
"int": () => newValue
}[propertyType]() }[propertyType]()
// Ensuring old and new values are different to prevent useless adding to history. // Ensuring old and new values are different to prevent useless adding to history.
if(obj[propertyName] != newValueParsed) { if(obj[propertyName] != newValueParsed) {
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
obj.name, objType, propertyName, obj.name, objType, propertyName,
obj[propertyName], newValueParsed obj[propertyName], newValueParsed
)) ))
@ -132,7 +131,6 @@ Repeater {
} }
} catch(e) { } catch(e) {
// Error in expression or domain // Error in expression or domain
console.trace()
parsingErrorDialog.showDialog(propertyName, newValue, e.message) parsingErrorDialog.showDialog(propertyName, newValue, e.message)
} }
} }
@ -143,9 +141,7 @@ Repeater {
title: qsTranslate("expression", "LogarithmPlotter - Parsing error") title: qsTranslate("expression", "LogarithmPlotter - Parsing error")
text: "" text: ""
function showDialog(propName, propValue, error) { function showDialog(propName, propValue, error) {
text = qsTranslate("error", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3") text = qsTranslate("error", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3").arg(propName).arg(error).arg(propValue)
.arg(qsTranslate('prop', propName))
.arg(error).arg(propValue)
open() open()
} }
} }
@ -159,7 +155,7 @@ Repeater {
CheckBox { CheckBox {
height: 20 height: 20
text: propertyLabel text: propertyLabel
//icon: `properties/${propertyIcon}.svg` //icon: `settings/custom/${propertyIcon}.svg`
checked: { checked: {
//if(obj[propertyName] == null) { //if(obj[propertyName] == null) {
@ -168,7 +164,7 @@ Repeater {
return obj[propertyName] return obj[propertyName]
} }
onClicked: { onClicked: {
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
obj.name, objType, propertyName, obj.name, objType, propertyName,
obj[propertyName], this.checked obj[propertyName], this.checked
)) ))
@ -185,15 +181,15 @@ Repeater {
Setting.ComboBoxSetting { Setting.ComboBoxSetting {
height: 30 height: 30
label: propertyLabel label: propertyLabel
icon: `properties/${propertyIcon}.svg` icon: `settings/custom/${propertyIcon}.svg`
// True to select an object of type, false for enums. // True to select an object of type, false for enums.
property bool selectObjMode: paramTypeIn(propertyType, ['ObjectType']) property bool selectObjMode: paramTypeIn(propertyType, ['ObjectType'])
property bool isRealObject: !selectObjMode || (propertyType.objType != "ExecutableObject" && propertyType.objType != "DrawableObject") property bool isRealObject: !selectObjMode || (propertyType.objType != "ExecutableObject" && propertyType.objType != "DrawableObject")
// Base, untranslated version of the model. // Base, untranslated version of the model.
property var baseModel: selectObjMode ? property var baseModel: selectObjMode ?
Modules.Objects.getObjectsName(propertyType.objType).concat( Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] : []) isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] : [])
: propertyType.values : propertyType.values
// Translated version of the model. // Translated version of the model.
model: selectObjMode ? baseModel : propertyType.translatedValues model: selectObjMode ? baseModel : propertyType.translatedValues
@ -203,32 +199,30 @@ Repeater {
if(selectObjMode) { if(selectObjMode) {
// This is only done when what we're selecting are Objects. // This is only done when what we're selecting are Objects.
// Setting object property. // Setting object property.
var selectedObj = Modules.Objects.currentObjectsByName[baseModel[newIndex]] var selectedObj = Objects.currentObjectsByName[baseModel[newIndex]]
if(newIndex != 0) { if(newIndex != 0) {
// Make sure we don't set the object to null. // Make sure we don't set the object to null.
if(selectedObj == null) { if(selectedObj == null) {
// Creating new object. // Creating new object.
selectedObj = Modules.Objects.createNewRegisteredObject(propertyType.objType) selectedObj = Objects.createNewRegisteredObject(propertyType.objType)
Modules.History.addToHistory( history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export()))
new JS.HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export()) baseModel = Objects.getObjectsName(propertyType.objType).concat(
) isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] :
baseModel = Modules.Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] :
[]) [])
currentIndex = baseModel.indexOf(selectedObj.name) currentIndex = baseModel.indexOf(selectedObj.name)
} }
selectedObj.requiredBy.push(Modules.Objects.currentObjects[objType][objIndex]) selectedObj.requiredBy.push(Objects.currentObjects[objType][objIndex])
//Modules.Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name) //Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name)
} }
obj.requiredBy = obj.requiredBy.filter((obj) => obj.name != obj.name) obj.requiredBy = obj.requiredBy.filter((obj) => obj.name != obj.name)
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
obj.name, objType, propertyName, obj.name, objType, propertyName,
obj[propertyName], selectedObj obj[propertyName], selectedObj
)) ))
obj[propertyName] = selectedObj obj[propertyName] = selectedObj
} else if(baseModel[newIndex] != obj[propertyName]) { } else if(baseModel[newIndex] != obj[propertyName]) {
// Ensuring new property is different to not add useless history entries. // Ensuring new property is different to not add useless history entries.
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
obj.name, objType, propertyName, obj.name, objType, propertyName,
obj[propertyName], baseModel[newIndex] obj[propertyName], baseModel[newIndex]
)) ))
@ -246,7 +240,7 @@ Repeater {
Setting.ListSetting { Setting.ListSetting {
label: propertyLabel label: propertyLabel
//icon: `properties/${propertyIcon}.svg` //icon: `settings/custom/${propertyIcon}.svg`
dictionaryMode: paramTypeIn(propertyType, ['Dict']) dictionaryMode: paramTypeIn(propertyType, ['Dict'])
keyType: dictionaryMode ? propertyType.keyType : 'string' keyType: dictionaryMode ? propertyType.keyType : 'string'
valueType: propertyType.valueType valueType: propertyType.valueType
@ -258,10 +252,11 @@ Repeater {
onChanged: { onChanged: {
var exported = exportModel() var exported = exportModel()
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
obj.name, objType, propertyName, obj.name, objType, propertyName,
obj[propertyName], exported obj[propertyName], exported
)) ))
//Objects.currentObjects[objType][objIndex][propertyName] = exported
obj[propertyName] = exported obj[propertyName] = exported
root.changed() root.changed()
} }
@ -283,7 +278,7 @@ Repeater {
property string propertyName: modelData[0] property string propertyName: modelData[0]
property var propertyType: modelData[1] property var propertyType: modelData[1]
property string propertyLabel: qsTranslate('prop',propertyName) property string propertyLabel: qsTranslate('prop',propertyName)
property string propertyIcon: propertyName property string propertyIcon: Utils.camelCase2readable(propertyName)
sourceComponent: { sourceComponent: {
if(propertyName.startsWith('comment')) if(propertyName.startsWith('comment'))

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -18,10 +18,15 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Dialogs as D
import Qt.labs.platform as Native import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
import eu.ad5001.LogarithmPlotter.Common import "../../js/objects.js" as Objects
import "../../js/objs/common.js" as ObjectsCommons
import "../../js/historylib.js" as HistoryLib
import "../../js/utils.js" as Utils
import "../../js/mathlib.js" as MathLib
/*! /*!
\qmltype Dialog \qmltype Dialog
@ -49,7 +54,7 @@ Popup.BaseDialog {
\qmlproperty var EditorDialog::obj \qmlproperty var EditorDialog::obj
Instance of the object being edited. Instance of the object being edited.
*/ */
property var obj: Modules.Objects.currentObjects[objType][objIndex] property var obj: Objects.currentObjects[objType][objIndex]
/*! /*!
\qmlproperty var EditorDialog::posPicker \qmlproperty var EditorDialog::posPicker
Reference to the global PositionPicker QML object. Reference to the global PositionPicker QML object.
@ -82,7 +87,7 @@ Popup.BaseDialog {
Label { Label {
id: dlgTitle id: dlgTitle
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: qsTr("Edit properties of %1 %2").arg(Modules.Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name) text: qsTr("Edit properties of %1 %2").arg(Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name)
font.pixelSize: 20 font.pixelSize: 20
color: sysPalette.windowText color: sysPalette.windowText
} }
@ -106,16 +111,16 @@ Popup.BaseDialog {
width: dlgProperties.width width: dlgProperties.width
value: objEditor.obj.name value: objEditor.obj.name
onChanged: function(newValue) { onChanged: function(newValue) {
let newName = JS.Utils.parseName(newValue) let newName = Utils.parseName(newValue)
if(newName != '' && objEditor.obj.name != newName) { if(newName != '' && objEditor.obj.name != newName) {
if(newName in Modules.Objects.currentObjectsByName) { if(newName in Objects.currentObjectsByName) {
invalidNameDialog.showDialog(newName) invalidNameDialog.showDialog(newName)
} else { } else {
Modules.History.addToHistory(new JS.HistoryLib.NameChanged( history.addToHistory(new HistoryLib.NameChanged(
objEditor.obj.name, objEditor.objType, newName objEditor.obj.name, objEditor.objType, newName
)) ))
Modules.Objects.renameObject(obj.name, newName) Objects.renameObject(obj.name, newName)
objEditor.obj = Modules.Objects.currentObjects[objEditor.objType][objEditor.objIndex] objEditor.obj = Objects.currentObjects[objEditor.objType][objEditor.objIndex]
objectListList.update() objectListList.update()
} }
} }
@ -126,17 +131,13 @@ Popup.BaseDialog {
id: labelContentProperty id: labelContentProperty
height: 30 height: 30
width: dlgProperties.width width: dlgProperties.width
label: qsTranslate("prop", "labelContent") label: qsTr("Label content")
model: [qsTr("null"), qsTr("name"), qsTr("name + value")] model: [qsTr("null"), qsTr("name"), qsTr("name + value")]
property var idModel: ["null", "name", "name + value"] property var idModel: ["null", "name", "name + value"]
icon: "common/label.svg" icon: "common/label.svg"
currentIndex: idModel.indexOf(objEditor.obj.labelContent) currentIndex: idModel.indexOf(objEditor.obj.labelContent)
onActivated: function(newIndex) { onActivated: function(newIndex) {
if(idModel[newIndex] != objEditor.obj.labelContent) { if(idModel[newIndex] != objEditor.obj.labelContent) {
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty(
obj.name, objType, "labelContent",
objEditor.obj.labelContent, idModel[newIndex]
))
objEditor.obj.labelContent = idModel[newIndex] objEditor.obj.labelContent = idModel[newIndex]
objEditor.obj.update() objEditor.obj.update()
objectListList.update() objectListList.update()
@ -164,7 +165,7 @@ Popup.BaseDialog {
*/ */
function open() { function open() {
dlgCustomProperties.model = [] // Reset dlgCustomProperties.model = [] // Reset
let objProps = Modules.Objects.types[objEditor.objType].properties() let objProps = Objects.types[objEditor.objType].properties()
dlgCustomProperties.model = Object.keys(objProps).map(prop => [prop, objProps[prop]]) // Converted to 2-dimentional array. dlgCustomProperties.model = Object.keys(objProps).map(prop => [prop, objProps[prop]]) // Converted to 2-dimentional array.
objEditor.show() objEditor.show()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -18,8 +18,9 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Common
/*! /*!
@ -43,7 +44,7 @@ Column {
// Open editor // Open editor
objectEditor.obj = obj objectEditor.obj = obj
objectEditor.objType = obj.type objectEditor.objType = obj.type
objectEditor.objIndex = Modules.Objects.currentObjects[obj.type].indexOf(obj) objectEditor.objIndex = Objects.currentObjects[obj.type].indexOf(obj)
objectEditor.open() objectEditor.open()
// Disconnect potential link // Disconnect potential link
posPicker.picked.disconnect(openEditorDialog) posPicker.picked.disconnect(openEditorDialog)
@ -60,12 +61,12 @@ Column {
width: parent.width width: parent.width
columns: 3 columns: 3
Repeater { Repeater {
model: Object.keys(Modules.Objects.types) model: Object.keys(Objects.types)
Button { Button {
id: createBtn id: createBtn
width: 96 width: 96
visible: Modules.Objects.types[modelData].createable() visible: Objects.types[modelData].createable()
height: visible ? width*0.8 : 0 height: visible ? width*0.8 : 0
// The KDE SDK is kinda buggy, so it respects neither specified color nor display propreties. // The KDE SDK is kinda buggy, so it respects neither specified color nor display propreties.
//display: AbstractButton.TextUnderIcon //display: AbstractButton.TextUnderIcon
@ -93,7 +94,7 @@ Column {
anchors.rightMargin: 4 anchors.rightMargin: 4
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.pixelSize: 14 font.pixelSize: 14
text: Modules.Objects.types[modelData].displayType() text: Objects.types[modelData].displayType()
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
clip: true clip: true
} }
@ -103,10 +104,8 @@ Column {
ToolTip.text: label.text ToolTip.text: label.text
onClicked: { onClicked: {
let newObj = Modules.Objects.createNewRegisteredObject(modelData) let newObj = Objects.createNewRegisteredObject(modelData)
Modules.History.addToHistory(new JS.HistoryLib.CreateNewObject( history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
newObj.name, modelData, newObj.export()
))
objectLists.update() objectLists.update()
let hasXProp = newObj.constructor.properties().hasOwnProperty('x') let hasXProp = newObj.constructor.properties().hasOwnProperty('x')

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -21,6 +21,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.ObjectLists.Editor 1.0 as Editor import eu.ad5001.LogarithmPlotter.ObjectLists.Editor 1.0 as Editor
import "../js/objects.js" as Objects
/*! /*!
\qmltype ObjectLists \qmltype ObjectLists
@ -46,7 +47,7 @@ ScrollView {
ListView { ListView {
id: objectsListView id: objectsListView
model: Object.keys(Modules.Objects.types) model: Object.keys(Objects.types)
//width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0) //width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0)
implicitHeight: contentItem.childrenRect.height + footerItem.height + 10 implicitHeight: contentItem.childrenRect.height + footerItem.height + 10
@ -54,9 +55,9 @@ ScrollView {
id: objTypeList id: objTypeList
property string objType: objectsListView.model[index] property string objType: objectsListView.model[index]
property var editingRows: [] property var editingRows: []
model: Modules.Objects.currentObjects[objType] model: Objects.currentObjects[objType]
width: objectsListView.width width: objectsListView.width
height: contentItem.childrenRect.height + (visible ? 10 : 0) implicitHeight: contentItem.childrenRect.height
visible: model != undefined && model.length > 0 visible: model != undefined && model.length > 0
interactive: false interactive: false
@ -69,23 +70,21 @@ ScrollView {
CheckBox { CheckBox {
id: typeVisibilityCheckBox id: typeVisibilityCheckBox
checked: Modules.Objects.currentObjects[objType] != undefined ? Modules.Objects.currentObjects[objType].every(obj => obj.visible) : true checked: Objects.currentObjects[objType] != undefined ? Objects.currentObjects[objType].every(obj => obj.visible) : true
onClicked: { onClicked: {
for(const obj of Modules.Objects.currentObjects[objType]) obj.visible = this.checked for(var obj of Objects.currentObjects[objType]) obj.visible = this.checked
for(const obj of objTypeList.editingRows) obj.objVisible = this.checked for(var obj of objTypeList.editingRows) obj.objVisible = this.checked
objectListList.changed() objectListList.changed()
} }
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: checked ? ToolTip.text: checked ? qsTr("Hide all %1").arg(Objects.types[objType].displayTypeMultiple()) : qsTr("Show all %1").arg(Objects.types[objType].displayTypeMultiple())
qsTr("Hide all %1").arg(Modules.Objects.types[objType].displayTypeMultiple()) :
qsTr("Show all %1").arg(Modules.Objects.types[objType].displayTypeMultiple())
} }
Label { Label {
id: typeHeaderText id: typeHeaderText
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: qsTranslate("control", "%1: ").arg(Modules.Objects.types[objType].displayTypeMultiple()) text: qsTranslate("control", "%1: ").arg(Objects.types[objType].displayTypeMultiple())
font.pixelSize: 20 font.pixelSize: 20
} }
} }
@ -93,11 +92,11 @@ ScrollView {
delegate: ObjectRow { delegate: ObjectRow {
id: controlRow id: controlRow
width: objTypeList.width width: objTypeList.width
obj: Modules.Objects.currentObjects[objType][index] obj: Objects.currentObjects[objType][index]
posPicker: positionPicker posPicker: positionPicker
onChanged: { onChanged: {
obj = Modules.Objects.currentObjects[objType][index] obj = Objects.currentObjects[objType][index]
objectListList.update() objectListList.update()
} }
@ -129,7 +128,7 @@ ScrollView {
function update() { function update() {
objectListList.changed() objectListList.changed()
for(var objType in objectListList.listViews) { for(var objType in objectListList.listViews) {
objectListList.listViews[objType].model = Modules.Objects.currentObjects[objType] objectListList.listViews[objType].model = Objects.currentObjects[objType]
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -21,7 +21,9 @@ import QtQuick.Dialogs
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Window import QtQuick.Window
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Common import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import "../js/math/latex.js" as LatexJS
/*! /*!
@ -72,7 +74,7 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 5 anchors.leftMargin: 5
onClicked: { onClicked: {
Modules.History.addToHistory(new JS.HistoryLib.EditedVisibility( history.addToHistory(new HistoryLib.EditedVisibility(
obj.name, obj.type, this.checked obj.name, obj.type, this.checked
)) ))
obj.visible = this.checked obj.visible = this.checked
@ -89,43 +91,27 @@ Item {
id: objDescription id: objDescription
anchors.left: objVisibilityCheckBox.right anchors.left: objVisibilityCheckBox.right
anchors.right: deleteButton.left anchors.right: deleteButton.left
height: Modules.Latex.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight height: LatexJS.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: Modules.Latex.enabled ? "" : obj.getReadableString() text: LatexJS.enabled ? "" : obj.getReadableString()
font.pixelSize: 14 font.pixelSize: 14
Image { Image {
id: latexDescription id: latexDescription
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
visible: Modules.Latex.enabled visible: LatexJS.enabled
property double depth: Screen.devicePixelRatio property double depth: Screen.devicePixelRatio
source: "" property var ltxInfo: visible ? Latex.render(obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color).split(",") : ["","0","0"]
width: 0/depth source: visible ? ltxInfo[0] : ""
height: 0/depth width: parseInt(ltxInfo[1])/depth
height: parseInt(ltxInfo[2])/depth
Component.onCompleted: function() {
if(Modules.Latex.enabled) {
const args = [obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color]
const prerendered = Modules.Latex.findPrerendered(...args)
if(prerendered !== null) {
source = prerendered.source
width = prerendered.width/depth
height = prerendered.height/depth
} else
Modules.Latex.requestAsyncRender(...args).then(info => {
source = info.source
width = info.width/depth
height = info.height/depth
})
}
}
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
objEditor.obj = Modules.Objects.currentObjects[obj.type][index] objEditor.obj = Objects.currentObjects[obj.type][index]
objEditor.objType = obj.type objEditor.objType = obj.type
objEditor.objIndex = index objEditor.objIndex = index
//objEditor.editingRow = objectRow //objEditor.editingRow = objectRow
@ -212,7 +198,7 @@ Item {
selectedColor: obj.color selectedColor: obj.color
title: qsTr("Pick new color for %1 %2").arg(obj.constructor.displayType()).arg(obj.name) title: qsTr("Pick new color for %1 %2").arg(obj.constructor.displayType()).arg(obj.name)
onAccepted: { onAccepted: {
Modules.History.addToHistory(new JS.HistoryLib.ColorChanged( history.addToHistory(new HistoryLib.ColorChanged(
obj.name, obj.type, obj.color, selectedColor.toString() obj.name, obj.type, obj.color, selectedColor.toString()
)) ))
obj.color = selectedColor.toString() obj.color = selectedColor.toString()
@ -227,14 +213,10 @@ Item {
function deleteRecursively(object) { function deleteRecursively(object) {
for(let toRemove of object.requiredBy) for(let toRemove of object.requiredBy)
deleteRecursively(toRemove) deleteRecursively(toRemove)
if(Modules.Objects.currentObjectsByName[object.name] !== undefined) {
// Object still exists
// Temporary fix for objects require not being propertly updated.
object.requiredBy = [] object.requiredBy = []
Modules.History.addToHistory(new JS.HistoryLib.DeleteObject( history.addToHistory(new HistoryLib.DeleteObject(
object.name, object.type, object.export() object.name, object.type, object.export()
)) ))
Modules.Objects.deleteObject(object.name) Objects.deleteObject(object.name)
}
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -18,12 +18,14 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Common import "js/objects.js" as Objects
import "js/mathlib.js" as MathLib
import "js/historylib.js" as HistoryLib
/*! /*!
\qmltype PickLocation \qmltype PickLocationOverlay
\inqmlmodule eu.ad5001.LogarithmPlotter.Overlay \inqmlmodule eu.ad5001.LogarithmPlotter
\brief Overlay used to pick a new location for an object. \brief Overlay used to pick a new location for an object.
Provides an overlay over the canvas that can be shown when the user clicks the "Set position" button Provides an overlay over the canvas that can be shown when the user clicks the "Set position" button
@ -97,9 +99,9 @@ Item {
readonly property bool userPickY: pickY && pickYCheckbox.checked readonly property bool userPickY: pickY && pickYCheckbox.checked
Rectangle { Rectangle {
anchors.fill: parent
color: sysPalette.window color: sysPalette.window
opacity: 0.35 opacity: 0.35
anchors.fill: parent
} }
MouseArea { MouseArea {
@ -112,10 +114,10 @@ Item {
if(mouse.button == Qt.LeftButton) { // Validate if(mouse.button == Qt.LeftButton) { // Validate
let newValueX = !parent.userPickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX) let newValueX = !parent.userPickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX)
let newValueY = !parent.userPickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY) let newValueY = !parent.userPickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY)
let obj = Modules.Objects.currentObjectsByName[objName] let obj = Objects.currentObjectsByName[objName]
// Set values // Set values
if(parent.userPickX && parent.userPickY) { if(parent.userPickX && parent.userPickY) {
Modules.History.addToHistory(new JS.HistoryLib.EditedPosition( history.addToHistory(new HistoryLib.EditedPosition(
objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY
)) ))
obj[propertyX] = newValueX obj[propertyX] = newValueX
@ -124,7 +126,7 @@ Item {
objectLists.update() objectLists.update()
pickerRoot.picked(obj) pickerRoot.picked(obj)
} else if(parent.userPickX) { } else if(parent.userPickX) {
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
objName, objType, propertyX, obj[propertyX], newValueX objName, objType, propertyX, obj[propertyX], newValueX
)) ))
obj[propertyX] = newValueX obj[propertyX] = newValueX
@ -132,7 +134,7 @@ Item {
objectLists.update() objectLists.update()
pickerRoot.picked(obj) pickerRoot.picked(obj)
} else if(parent.userPickY) { } else if(parent.userPickY) {
Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
objName, objType, propertyY, obj[propertyY], newValueY objName, objType, propertyY, obj[propertyY], newValueY
)) ))
obj[propertyY] = newValueY obj[propertyY] = newValueY
@ -261,7 +263,7 @@ Item {
color: 'black' color: 'black'
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Modules.Canvas.x2px(picked.mouseX) anchors.leftMargin: canvas.x2px(picked.mouseX)
visible: parent.userPickX visible: parent.userPickX
} }
@ -272,7 +274,7 @@ Item {
color: 'black' color: 'black'
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: Modules.Canvas.y2px(picked.mouseY) anchors.topMargin: canvas.y2px(picked.mouseY)
visible: parent.userPickY visible: parent.userPickY
} }
@ -281,26 +283,25 @@ Item {
x: picker.mouseX - width - 5 x: picker.mouseX - width - 5
y: picker.mouseY - height - 5 y: picker.mouseY - height - 5
color: 'black' color: 'black'
property double axisX: canvas.xaxisstep1
property double mouseX: { property double mouseX: {
const axisX = Modules.Canvas.axesSteps.x.value let xpos = canvas.px2x(picker.mouseX)
const xpos = Modules.Canvas.px2x(picker.mouseX)
if(snapToGridCheckbox.checked) { if(snapToGridCheckbox.checked) {
if(Modules.Settings.logscalex) { if(canvas.logscalex) {
// Calculate the logged power // Calculate the logged power
let pow = Math.pow(10, Math.floor(Math.log10(xpos))) let pow = Math.pow(10, Math.floor(Math.log10(xpos)))
return pow*Math.round(xpos/pow) return pow*Math.round(xpos/pow)
} else { } else {
return axisX*Math.round(xpos/axisX) return canvas.xaxisstep1*Math.round(xpos/canvas.xaxisstep1)
} }
} else { } else {
return xpos.toFixed(parent.precision) return xpos.toFixed(parent.precision)
} }
} }
property double mouseY: { property double mouseY: {
const axisY = Modules.Canvas.axesSteps.y.value let ypos = canvas.px2y(picker.mouseY)
const ypos = Modules.Canvas.px2y(picker.mouseY)
if(snapToGridCheckbox.checked) { if(snapToGridCheckbox.checked) {
return axisY*Math.round(ypos/axisY) return canvas.yaxisstep1*Math.round(ypos/canvas.yaxisstep1)
} else { } else {
return ypos.toFixed(parent.precision) return ypos.toFixed(parent.precision)
} }
@ -323,9 +324,9 @@ Item {
Parses a given \c value as an expression or a number depending on the type of \c propertyName of all \c objType. Parses a given \c value as an expression or a number depending on the type of \c propertyName of all \c objType.
*/ */
function parseValue(value, objType, propertyName) { function parseValue(value, objType, propertyName) {
if(Modules.Objects.types[objType].properties()[propertyName] == 'number') if(Objects.types[objType].properties()[propertyName] == 'number')
return parseFloat(value) return parseFloat(value)
else else
return new JS.MathLib.Expression(value) return new MathLib.Expression(value)
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -102,7 +102,7 @@ BaseDialog {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
textFormat: Text.RichText textFormat: Text.RichText
font.pixelSize: 13 font.pixelSize: 13
text: "Copyright © 2021-2025 Ad5001 &lt;mail@ad5001.eu&gt;<br> text: "Copyright © 2021-2024 Ad5001 &lt;mail@ad5001.eu&gt;<br>
<br> <br>
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.<br> 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.<br>
<br> <br>

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -45,17 +45,17 @@ Popup {
property bool changelogNeedsFetching: true property bool changelogNeedsFetching: true
onAboutToShow: if(changelogNeedsFetching) { onAboutToShow: if(changelogNeedsFetching) {
Helper.fetchChangelog().then((fetchedText) => { Helper.fetchChangelog()
changelogNeedsFetching = false }
changelog.text = fetchedText
Connections {
target: Helper
function onChangelogFetched(chl) {
changelogNeedsFetching = false;
changelog.text = chl
changelogView.contentItem.implicitHeight = changelog.height changelogView.contentItem.implicitHeight = changelog.height
}, (error) => { // console.log(changelog.height, changelogView.contentItem.implicitHeight)
const e = qsTranslate("changelog", "Could not fetch update: {}.").replace('{}', error) }
console.error(e)
changelogNeedsFetching = false
changelog.text = e
changelogView.contentItem.implicitHeight = changelog.height
})
} }
ScrollView { ScrollView {
@ -96,7 +96,7 @@ Popup {
Button { Button {
id: doneBtn id: doneBtn
text: qsTr("Close") text: qsTr("Done")
font.pixelSize: 18 font.pixelSize: 18
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 7 anchors.bottomMargin: 7

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -0,0 +1,236 @@
/**
* 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 QtQuick
import QtQuick.Controls
import "../js/math/latex.js" as Latex
/*!
\qmltype GreetScreen
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
\brief Overlay displayed when LogarithmPlotter is launched for the first time or when it was just updated.
It contains several settings as well as an easy access to the changelog
\sa LogarithmPlotter, Settings, AppMenuBar, Changelog
*/
Popup {
id: greetingPopup
x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2)
width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20
height: Math.min(parent.height-40, 700)
modal: true
focus: true
clip: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
ScrollView {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: bottomButtons.height + 20
clip: true
Column {
width: greetingPopup.width - 25
spacing: 10
clip: true
topPadding: 35
Row {
id: welcome
height: logo.height
spacing: 10
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
wrapMode: Text.WordWrap
font.pixelSize: 32
text: qsTr("Welcome to LogarithmPlotter")
}
}
Label {
id: versionText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: implicitWidth
font.pixelSize: 18
font.italic: true
text: qsTr("Version %1").arg(Helper.getVersion())
}
Label {
id: helpText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
font.pixelSize: 14
width: parent.width - 50
text: qsTr("Take a few seconds to configure LogarithmPlotter.\nThese settings can be changed at any time from the \"Settings\" menu.")
}
CheckBox {
id: checkForUpdatesSetting
anchors.left: parent.left
checked: Helper.getSettingBool("check_for_updates")
text: qsTr('Check for updates on startup (requires online connectivity)')
onClicked: {
Helper.setSettingBool("check_for_updates", checked)
// Set in the menu bar
appMenu.settingsMenu.children[0].checked = checked
}
}
CheckBox {
id: resetRedoStackSetting
anchors.left: parent.left
checked: Helper.getSettingBool("reset_redo_stack")
text: qsTr('Reset redo stack when a new action is added to history')
onClicked: {
Helper.setSettingBool("reset_redo_stack", checked)
appMenu.settingsMenu.children[1].checked = checked
}
}
CheckBox {
id: enableLatexSetting
anchors.left: parent.left
checked: Helper.getSettingBool("enable_latex")
text: qsTr('Enable LaTeX rendering')
onClicked: {
Helper.setSettingBool("enable_latex", checked)
appMenu.settingsMenu.children[2].checked = checked
}
}
CheckBox {
id: autocloseFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("expression_editor.autoclose")
text: qsTr('Automatically close parenthesises and brackets in expressions')
onClicked: {
Helper.setSettingBool("expression_editor.autoclose", checked)
appMenu.settingsMenu.children[3].children[0].checked = checked
}
}
CheckBox {
id: colorizeFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("expression_editor.colorize")
text: qsTr('Enable syntax highlighting for expressions')
onClicked: {
Helper.setSettingBool("expression_editor.colorize", checked)
appMenu.settingsMenu.children[3].children[1].checked = checked
}
}
CheckBox {
id: autocompleteFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("autocompletion.enabled")
text: qsTr('Enable autocompletion interface in expression editor')
onClicked: {
Helper.setSettingBool("autocompletion.enabled", checked)
appMenu.settingsMenu.children[3].children[2].checked = checked
}
}
Row {
anchors.left: parent.left
anchors.leftMargin: 10
spacing: 10
Label {
id: colorSchemeLabel
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
text: qsTr("Color scheme:")
}
ComboBox {
model: ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"]
currentIndex: Helper.getSettingInt("expression_editor.color_scheme")
onActivated: function(index) {
Helper.setSettingInt("expression_editor.color_scheme", index)
}
}
}
}
}
Rectangle {
id: bottomSeparator
opacity: 0.3
color: sysPalette.windowText
width: parent.width * 2 / 3
height: 1
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: bottomButtons.top
anchors.bottomMargin: 9
}
Row {
id: bottomButtons
anchors.bottom: parent.bottom
anchors.bottomMargin: 7
spacing: 10
anchors.horizontalCenter: parent.horizontalCenter
Button {
id: userManualBtn
text: qsTr("User manual")
font.pixelSize: 18
onClicked: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar")
}
Button {
id: changelogBtn
text: qsTr("Changelog")
font.pixelSize: 18
onClicked: changelog.open()
}
Button {
id: doneBtn
text: qsTr("Done")
font.pixelSize: 18
onClicked: greetingPopup.close()
}
}
Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()) {
greetingPopup.open()
}
onClosed: Helper.setSetting("last_install_greet", Helper.getVersion())
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,9 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQml.Models import QtQuick
/*! /*!
\qmltype InsertCharacter \qmltype InsertCharacter
@ -43,18 +42,15 @@ Popup {
*/ */
property string category: 'all' property string category: 'all'
width: insertGrid.width + 10 width: 280
height: insertGrid.height + 10 height: Math.ceil(insertGrid.insertChars.length/insertGrid.columns)*(width/insertGrid.columns)+5
modal: true modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
GridView { Grid {
id: insertGrid id: insertGrid
width: 280 width: parent.width
height: Math.ceil(model.count/columns)*cellHeight columns: 7
property int columns: 7
cellWidth: width/columns
cellHeight: cellWidth
property var insertCharsExpression: [ property var insertCharsExpression: [
"∞","π","¹","²","³","⁴","⁵", "∞","π","¹","²","³","⁴","⁵",
@ -90,34 +86,21 @@ Popup {
}[insertPopup.category] }[insertPopup.category]
} }
model: ListModel {} Repeater {
model: parent.insertChars.length
delegate: Button { Button {
id: insertBtn id: insertBtn
width: insertGrid.cellWidth width: insertGrid.width/insertGrid.columns
height: insertGrid.cellHeight height: width
text: chr text: insertGrid.insertChars[modelData]
flat: text == " " flat: text == " "
font.pixelSize: 18 font.pixelSize: 18
onClicked: { onClicked: {
insertPopup.selected(text) selected(text)
insertPopup.close()
} }
} }
Component.onCompleted: function() {
for(const chr of insertChars) {
model.append({ 'chr': chr })
} }
} }
Keys.onEscapePressed: parent.close()
}
function setFocus() {
insertGrid.currentIndex = 0
insertGrid.forceActiveFocus()
}
} }

View file

@ -0,0 +1,334 @@
/**
* 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 QtQuick
import QtQuick.Dialogs
import QtQuick.Controls
/*!
\qmltype ThanksTo
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
\brief Thanks to popup of LogarithmPlotter.
\sa LogarithmPlotter
*/
BaseDialog {
id: about
title: qsTr("Thanks and Contributions - LogarithmPlotter")
width: 450
minimumHeight: 710
Column {
anchors {
top: parent.top;
left: parent.left;
bottom: parent.bottom;
right: parent.right;
topMargin: margin;
leftMargin: margin;
bottomMargin: margin;
rightMargin: margin;
}
spacing: 10
ListView {
id: librariesListView
anchors.left: parent.left
width: parent.width
//height: parent.height
implicitHeight: contentItem.childrenRect.height
interactive: false
model: ListModel {
Component.onCompleted: {
append({
libName: 'expr-eval',
license: 'MIT',
licenseLink: 'https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt',
linkName: qsTr('Source code'),
link: 'https://github.com/silentmatt/expr-eval',
authors: [{
authorLine: qsTr('Original library by Raphael Graf'),
email: 'r@undefined.ch',
website: 'https://web.archive.org/web/20111023001618/http://www.undefined.ch/mparser/index.html',
websiteName: qsTr('Source')
}, {
authorLine: qsTr('Ported to Javascript by Matthew Crumley'),
email: 'email@matthewcrumley.com',
website: 'https://silentmatt.com/',
websiteName: qsTr('Website')
}, {
authorLine: qsTr('Ported to QMLJS by Ad5001'),
email: 'mail@ad5001.eu',
website: 'https://ad5001.eu/',
websiteName: qsTr('Website')
}]
})
}
}
header: Label {
id: librariesUsedHeader
wrapMode: Text.WordWrap
font.pixelSize: 25
text: qsTr("Libraries included")
height: implicitHeight + 10
}
delegate: Column {
id: libClmn
width: librariesListView.width
spacing: 10
Item {
height: libraryHeader.height
width: parent.width
Label {
id: libraryHeader
anchors.left: parent.left
wrapMode: Text.WordWrap
font.pixelSize: 18
text: libName
}
Row {
anchors.right: parent.right
height: parent.height
spacing: 10
Button {
height: parent.height
text: license
icon.name: 'license'
onClicked: Qt.openUrlExternally(licenseLink)
}
Button {
height: parent.height
text: linkName
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally(link)
}
}
}
ListView {
id: libAuthors
anchors.left: parent.left
anchors.leftMargin: 10
model: authors
width: parent.width - 10
implicitHeight: contentItem.childrenRect.height
interactive: false
delegate: Item {
id: libAuthor
width: librariesListView.width - 10
height: 50
Label {
id: libAuthorName
anchors.left: parent.left
anchors.right: buttons.left
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 14
text: authorLine
}
Row {
id: buttons
anchors.right: parent.right
height: parent.height
spacing: 10
Button {
anchors.verticalCenter: parent.verticalCenter
text: websiteName
icon.name: 'web-browser'
height: parent.height - 10
onClicked: Qt.openUrlExternally(website)
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr('Email')
icon.name: 'email'
height: parent.height - 10
onClicked: Qt.openUrlExternally('mailto:' + email)
}
}
}
}
Rectangle {
id: libSeparator
opacity: 0.3
color: sysPalette.windowText
width: parent.width
height: 1
}
}
}
ListView {
id: translationsListView
anchors.left: parent.left
width: parent.width
implicitHeight: contentItem.childrenRect.height
interactive: false
spacing: 3
model: ListModel {
Component.onCompleted: {
append({
tranName: '🇬🇧 ' + qsTr('English'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/',
authors: [{
authorLine: 'Ad5001',
email: 'mail@ad5001.eu',
website: 'https://ad5001.eu',
websiteName: qsTr('Website')
}]
})
append({
tranName: '🇫🇷 ' + qsTr('French'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/',
authors: [{
authorLine: 'Ad5001',
website: 'https://ad5001.eu',
websiteName: qsTr('Website')
}]
})
append({
tranName: '🇩🇪 ' + qsTr('German'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/',
authors: [{
authorLine: 'Ad5001',
website: 'https://ad5001.eu',
websiteName: qsTr('Website')
}]
})
append({
tranName: '🇭🇺 ' + qsTr('Hungarian'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/',
authors: [{
authorLine: 'Óvári',
website: 'https://github.com/ovari',
websiteName: qsTr('Github')
}]
})
append({
tranName: '🇳🇴 ' + qsTr('Norwegian'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/no/',
authors: [{
authorLine: 'Allan Nordhøy',
website: 'https://github.com/comradekingu',
websiteName: qsTr('Github')
}]
})
}
}
header: Label {
id: translationsHeader
wrapMode: Text.WordWrap
font.pixelSize: 25
text: qsTr("Translations included")
height: implicitHeight + 10
}
delegate: Column {
id: tranClmn
width: translationsListView.width
Item {
width: parent.width
height: translationHeader.height + 10
Label {
id: translationHeader
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 18
text: tranName
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: 30
spacing: 10
Button {
height: parent.height
text: qsTr('Improve')
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally(link)
}
}
}
ListView {
id: tranAuthors
anchors.left: parent.left
anchors.leftMargin: 10
model: authors
width: parent.width - 10
implicitHeight: contentItem.childrenRect.height
interactive: false
delegate: Item {
id: tranAuthor
width: tranAuthors.width
height: 40
Label {
id: tranAuthorName
anchors.left: parent.left
anchors.right: buttons.left
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 14
text: authorLine
}
Row {
id: buttons
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: 30
spacing: 10
Button {
text: websiteName
icon.name: 'web-browser'
height: parent.height
onClicked: Qt.openUrlExternally(website)
}
}
}
}
}
}
}
}

View file

@ -1,11 +1,10 @@
module eu.ad5001.LogarithmPlotter.Popup module eu.ad5001.LogarithmPlotter.Popup
Alert 1.0 Alert.qml
About 1.0 About.qml
BaseDialog 1.0 BaseDialog.qml BaseDialog 1.0 BaseDialog.qml
Changelog 1.0 Changelog.qml About 1.0 About.qml
Alert 1.0 Alert.qml
FileDialog 1.0 FileDialog.qml FileDialog 1.0 FileDialog.qml
GreetScreen 1.0 GreetScreen.qml GreetScreen 1.0 GreetScreen.qml
InsertCharacter 1.0 InsertCharacter.qml Changelog 1.0 Changelog.qml
Preferences 1.0 Preferences.qml
ThanksTo 1.0 ThanksTo.qml ThanksTo 1.0 ThanksTo.qml
InsertCharacter 1.0 InsertCharacter.qml

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -114,7 +114,6 @@ Item {
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
height: 30 height: 30
width: Math.max(85, implicitWidth)
anchors.top: parent.top anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: qsTranslate("control", "%1: ").arg(control.label) text: qsTranslate("control", "%1: ").arg(control.label)

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -20,7 +20,10 @@ import QtQuick.Controls
import QtQuick import QtQuick
import Qt.labs.platform as Native import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Popup 1.0 as P import eu.ad5001.LogarithmPlotter.Popup 1.0 as P
import eu.ad5001.LogarithmPlotter.Common import "../js/mathlib.js" as MathLib
import "../js/utils.js" as Utils
import "../js/objects.js" as Objects
import "../js/parsing/parsing.js" as Parsing
/*! /*!
@ -78,17 +81,6 @@ Item {
Icon path of the editor. Icon path of the editor.
*/ */
property string icon: "" property string icon: ""
/*!
\qmlproperty bool ExpressionEditor::allowGraphObjects
If true, allows graph objects to be used as part of the expression.
*/
property bool allowGraphObjects: true
/*!
\qmlproperty var ExpressionEditor::errorDialog
Allows to summon the error dialog when using additional external parsing.
*/
readonly property alias errorDialog: parsingErrorDialog
/*! /*!
\qmlproperty string ExpressionEditor::openAndCloseMatches \qmlproperty string ExpressionEditor::openAndCloseMatches
@ -175,20 +167,19 @@ Item {
Icon { Icon {
id: iconLabel id: iconLabel
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: parent.icon == "" ? 0 : 3 anchors.topMargin: icon == "" ? 0 : 3
source: control.visible && parent.icon != "" ? "../icons/" + control.icon : "" source: control.visible && icon != "" ? "../icons/" + control.icon : ""
width: height width: height
height: parent.icon == "" || !visible ? 0 : 24 height: icon == "" || !visible ? 0 : 24
color: sysPalette.windowText color: sysPalette.windowText
} }
Label { Label {
id: labelItem id: labelItem
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: parent.icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
anchors.top: parent.top
height: parent.height height: parent.height
width: Math.max(85, implicitWidth) anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText //color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : "" text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
@ -200,9 +191,7 @@ Item {
title: qsTranslate("expression", "LogarithmPlotter - Parsing error") title: qsTranslate("expression", "LogarithmPlotter - Parsing error")
text: "" text: ""
function showDialog(propName, propValue, error) { function showDialog(propName, propValue, error) {
text = qsTranslate("expression", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3") text = qsTranslate("expression", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3").arg(propName).arg(error).arg(propValue)
.arg(qsTranslate('prop', propName))
.arg(error).arg(propValue)
open() open()
} }
} }
@ -221,9 +210,9 @@ Item {
focus: true focus: true
selectByMouse: true selectByMouse: true
property bool autocompleteEnabled: Helper.getSetting("autocompletion.enabled") property bool autocompleteEnabled: Helper.getSettingBool("autocompletion.enabled")
property bool syntaxHighlightingEnabled: Helper.getSetting("expression_editor.colorize") property bool syntaxHighlightingEnabled: Helper.getSettingBool("expression_editor.colorize")
property bool autoClosing: Helper.getSetting("expression_editor.autoclose") property bool autoClosing: Helper.getSettingBool("expression_editor.autoclose")
property var tokens: autocompleteEnabled || syntaxHighlightingEnabled ? parent.tokens(text) : [] property var tokens: autocompleteEnabled || syntaxHighlightingEnabled ? parent.tokens(text) : []
Keys.priority: Keys.BeforeItem // Required for knowing which key the user presses. Keys.priority: Keys.BeforeItem // Required for knowing which key the user presses.
@ -231,8 +220,8 @@ Item {
onEditingFinished: { onEditingFinished: {
if(insertButton.focus || insertPopup.focus) return if(insertButton.focus || insertPopup.focus) return
let value = text let value = text
if(value != "" && value.toString() != parent.defValue) { if(value != "" && value.toString() != defValue) {
let expr = parent.parse(value) let expr = parse(value)
if(expr != null) { if(expr != null) {
control.changed(expr) control.changed(expr)
defValue = expr.toEditableString() defValue = expr.toEditableString()
@ -280,10 +269,10 @@ Item {
acPopupContent.itemSelected = 0 acPopupContent.itemSelected = 0
if(event.text in parent.openAndCloseMatches && autoClosing) { if(event.text in openAndCloseMatches && autoClosing) {
let start = selectionStart let start = selectionStart
insert(selectionStart, event.text) insert(selectionStart, event.text)
insert(selectionEnd, parent.openAndCloseMatches[event.text]) insert(selectionEnd, openAndCloseMatches[event.text])
cursorPosition = start+1 cursorPosition = start+1
event.accepted = true event.accepted = true
} }
@ -317,9 +306,9 @@ Item {
width: parent.width width: parent.width
readonly property var identifierTokenTypes: [ readonly property var identifierTokenTypes: [
JS.Parsing.TokenType.VARIABLE, Parsing.TokenType.VARIABLE,
JS.Parsing.TokenType.FUNCTION, Parsing.TokenType.FUNCTION,
JS.Parsing.TokenType.CONSTANT Parsing.TokenType.CONSTANT
] ]
property var currentToken: generateTokenInformation(getTokenAt(editor.tokens, editor.cursorPosition)) property var currentToken: generateTokenInformation(getTokenAt(editor.tokens, editor.cursorPosition))
property var previousToken: generateTokenInformation(getPreviousToken(currentToken.token)) property var previousToken: generateTokenInformation(getPreviousToken(currentToken.token))
@ -344,7 +333,7 @@ Item {
'value': exists ? token.value : null, 'value': exists ? token.value : null,
'type': exists ? token.type : null, 'type': exists ? token.type : null,
'startPosition': exists ? token.startPosition : 0, 'startPosition': exists ? token.startPosition : 0,
'dot': exists ? (token.type == JS.Parsing.TokenType.PUNCT && token.value == ".") : false, 'dot': exists ? (token.type == Parsing.TokenType.PUNCT && token.value == ".") : false,
'identifier': exists ? identifierTokenTypes.includes(token.type) : false 'identifier': exists ? identifierTokenTypes.includes(token.type) : false
} }
} }
@ -383,7 +372,7 @@ Item {
*/ */
function getPreviousToken(token) { function getPreviousToken(token) {
let newToken = getTokenAt(editor.tokens, token.startPosition) let newToken = getTokenAt(editor.tokens, token.startPosition)
if(newToken != null && newToken.type == JS.Parsing.TokenType.WHITESPACE) if(newToken != null && newToken.type == Parsing.TokenType.WHITESPACE)
return getPreviousToken(newToken) return getPreviousToken(newToken)
return newToken return newToken
} }
@ -392,7 +381,7 @@ Item {
id: objectPropertiesList id: objectPropertiesList
category: qsTr("Object Properties") category: qsTr("Object Properties")
visbilityCondition: control.allowGraphObjects && doesObjectExist visbilityCondition: doesObjectExist
itemStartIndex: 0 itemStartIndex: 0
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
property bool isEnteringProperty: ( property bool isEnteringProperty: (
@ -403,9 +392,9 @@ Item {
property string objectName: isEnteringProperty ? property string objectName: isEnteringProperty ?
(parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value) (parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value)
: "" : ""
property bool doesObjectExist: isEnteringProperty && (objectName in Modules.Objects.currentObjectsByName) property bool doesObjectExist: isEnteringProperty && (objectName in Objects.currentObjectsByName)
property var objectProperties: doesObjectExist ? property var objectProperties: doesObjectExist ?
Modules.Objects.currentObjectsByName[objectName].constructor.properties() : Objects.currentObjectsByName[objectName].constructor.properties() :
{} {}
categoryItems: Object.keys(objectProperties) categoryItems: Object.keys(objectProperties)
autocompleteGenerator: (item) => { autocompleteGenerator: (item) => {
@ -442,9 +431,9 @@ Item {
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: variablesList.itemStartIndex + variablesList.model.length itemStartIndex: variablesList.itemStartIndex + variablesList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: JS.Parsing.CONSTANTS_LIST categoryItems: Parsing.CONSTANTS_LIST
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': JS.Parsing.CONSTANTS[item], 'text': item, 'annotation': Parsing.CONSTANTS[item],
'autocomplete': item + " ", 'cursorFinalOffset': 0 'autocomplete': item + " ", 'cursorFinalOffset': 0
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -457,9 +446,9 @@ Item {
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: constantsList.itemStartIndex + constantsList.model.length itemStartIndex: constantsList.itemStartIndex + constantsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: JS.Parsing.FUNCTIONS_LIST categoryItems: Parsing.FUNCTIONS_LIST
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': JS.Parsing.FUNCTIONS_USAGE[item].join(', '), 'text': item, 'annotation': Parsing.FUNCTIONS_USAGE[item].join(', '),
'autocomplete': item+'()', 'cursorFinalOffset': -1 'autocomplete': item+'()', 'cursorFinalOffset': -1
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -469,12 +458,12 @@ Item {
id: executableObjectsList id: executableObjectsList
category: qsTr("Executable Objects") category: qsTr("Executable Objects")
visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: functionsList.itemStartIndex + functionsList.model.length itemStartIndex: functionsList.itemStartIndex + functionsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Modules.Objects.getObjectsName("ExecutableObject").filter(obj => obj != self) categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': Modules.Objects.currentObjectsByName[item] == null ? '' : Modules.Objects.currentObjectsByName[item].constructor.displayType(), 'text': item, 'annotation': Objects.currentObjectsByName[item] == null ? '' : Objects.currentObjectsByName[item].constructor.displayType(),
'autocomplete': item+'()', 'cursorFinalOffset': -1 'autocomplete': item+'()', 'cursorFinalOffset': -1
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -484,12 +473,12 @@ Item {
id: objectsList id: objectsList
category: qsTr("Objects") category: qsTr("Objects")
visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Object.keys(Modules.Objects.currentObjectsByName).filter(obj => obj != self) categoryItems: Object.keys(Objects.currentObjectsByName).filter(obj => obj != self)
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': `${Modules.Objects.currentObjectsByName[item].constructor.displayType()}`, 'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`,
'autocomplete': item+'.', 'cursorFinalOffset': 0 'autocomplete': item+'.', 'cursorFinalOffset': 0
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -500,41 +489,29 @@ Item {
Button { Button {
id: insertButton id: insertButton
text: "α"
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 5 anchors.rightMargin: 5
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 20 width: 20
height: width height: width
Icon {
id: icon
width: 12
height: 12
anchors.centerIn: parent
color: sysPalette.windowText
source: '../icons/properties/expression.svg'
}
onClicked: { onClicked: {
insertPopup.open() insertPopup.open()
insertPopup.setFocus() insertPopup.focus = true
} }
} }
P.InsertCharacter { P.InsertCharacter {
id: insertPopup id: insertPopup
x: parent.width - width x: Math.round((parent.width - width) / 2)
y: parent.height y: Math.round((parent.height - height) / 2)
category: "expression" category: "expression"
onSelected: function(c) { onSelected: function(c) {
editor.insert(editor.cursorPosition, c) editor.insert(editor.cursorPosition, c)
} insertPopup.close()
onClosed: function() {
focus = false focus = false
editor.focus = true editor.focus = true
} }
@ -548,9 +525,9 @@ Item {
function parse(newExpression) { function parse(newExpression) {
let expr = null let expr = null
try { try {
expr = new JS.MathLib.Expression(value.toString()) expr = new MathLib.Expression(value.toString())
// Check if the expression is valid, throws error otherwise. // Check if the expression is valid, throws error otherwise.
if(!expr.allRequirementsFulfilled()) { if(!expr.allRequirementsFullfilled()) {
let undefVars = expr.undefinedVariables() let undefVars = expr.undefinedVariables()
if(undefVars.length > 1) if(undefVars.length > 1)
throw new Error(qsTranslate('error', 'No object found with names %1.').arg(undefVars.join(', '))) throw new Error(qsTranslate('error', 'No object found with names %1.').arg(undefVars.join(', ')))
@ -561,8 +538,8 @@ Item {
throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.')) throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.'))
// Recursive dependencies // Recursive dependencies
let dependentOnSelfObjects = expr.requiredObjects().filter( let dependentOnSelfObjects = expr.requiredObjects().filter(
(obj) => Modules.Objects.currentObjectsByName[obj].getDependenciesList() (obj) => Objects.currentObjectsByName[obj].getDependenciesList()
.includes(Modules.Objects.currentObjectsByName[control.self]) .includes(Objects.currentObjectsByName[control.self])
) )
if(dependentOnSelfObjects.length == 1) if(dependentOnSelfObjects.length == 1)
throw new Error(qsTranslate('error', 'Circular dependency detected. Object %1 depends on %2.').arg(dependentOnSelfObjects[0].toString()).arg(control.self)) throw new Error(qsTranslate('error', 'Circular dependency detected. Object %1 depends on %2.').arg(dependentOnSelfObjects[0].toString()).arg(control.self))
@ -582,7 +559,7 @@ Item {
Generates a list of tokens from the given. Generates a list of tokens from the given.
*/ */
function tokens(text) { function tokens(text) {
let tokenizer = new JS.Parsing.Tokenizer(new JS.Parsing.Input(text), true, false) let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false)
let tokenList = [] let tokenList = []
let token let token
while((token = tokenizer.next()) != null) while((token = tokenizer.next()) != null)
@ -612,31 +589,31 @@ Item {
*/ */
function colorize(tokenList) { function colorize(tokenList) {
let parsedText = "" let parsedText = ""
let scheme = colorSchemes[Helper.getSetting("expression_editor.color_scheme")] let scheme = colorSchemes[Helper.getSettingInt("expression_editor.color_scheme")]
for(let token of tokenList) { for(let token of tokenList) {
switch(token.type) { switch(token.type) {
case JS.Parsing.TokenType.VARIABLE: case Parsing.TokenType.VARIABLE:
parsedText += `<font color="${scheme.VARIABLE}">${token.value}</font>` parsedText += `<font color="${scheme.VARIABLE}">${token.value}</font>`
break; break;
case JS.Parsing.TokenType.CONSTANT: case Parsing.TokenType.CONSTANT:
parsedText += `<font color="${scheme.CONSTANT}">${token.value}</font>` parsedText += `<font color="${scheme.CONSTANT}">${token.value}</font>`
break; break;
case JS.Parsing.TokenType.FUNCTION: case Parsing.TokenType.FUNCTION:
parsedText += `<font color="${scheme.FUNCTION}">${JS.Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${scheme.FUNCTION}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case JS.Parsing.TokenType.OPERATOR: case Parsing.TokenType.OPERATOR:
parsedText += `<font color="${scheme.OPERATOR}">${JS.Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${scheme.OPERATOR}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case JS.Parsing.TokenType.NUMBER: case Parsing.TokenType.NUMBER:
parsedText += `<font color="${scheme.NUMBER}">${JS.Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${scheme.NUMBER}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case JS.Parsing.TokenType.STRING: case Parsing.TokenType.STRING:
parsedText += `<font color="${scheme.STRING}">${token.limitator}${JS.Utils.escapeHTML(token.value)}${token.limitator}</font>` parsedText += `<font color="${scheme.STRING}">${token.limitator}${Utils.escapeHTML(token.value)}${token.limitator}</font>`
break; break;
case JS.Parsing.TokenType.WHITESPACE: case Parsing.TokenType.WHITESPACE:
case JS.Parsing.TokenType.PUNCT: case Parsing.TokenType.PUNCT:
default: default:
parsedText += JS.Utils.escapeHTML(token.value).replace(/ /g, '&nbsp;') parsedText += Utils.escapeHTML(token.value).replace(/ /g, '&nbsp;')
break; break;
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,7 +17,7 @@
*/ */
import QtQuick import QtQuick
import QtQuick.Window import QtQuick.Window
import QtQuick.Controls.impl import Qt5Compat.GraphicalEffects
/*! /*!
\qmltype Icon \qmltype Icon
@ -41,16 +41,20 @@ Item {
\qmlproperty string Icon::source \qmlproperty string Icon::source
Path of the icon image source. Path of the icon image source.
*/ */
property alias sourceSize: img.sourceS property alias sourceSize: img.sourceSize.width
ColorImage { Image {
id: img id: img
height: parent.height height: parent.height
width: parent.width width: parent.width
// visible: false visible: false
property int sourceS: width*Screen.devicePixelRatio sourceSize.width: width*Screen.devicePixelRatio
sourceSize.width: sourceS sourceSize.height: width*Screen.devicePixelRatio
sourceSize.height: sourceS }
ColorOverlay {
anchors.fill: img
source: img
color: parent.color color: parent.color
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -37,7 +37,7 @@ Item {
Emitted when the value of the text has been changed. Emitted when the value of the text has been changed.
The corresponding handler is \c onChanged. The corresponding handler is \c onChanged.
*/ */
signal changed(var newValue) signal changed(string newValue)
/*! /*!
\qmlproperty bool TextSetting::isInt \qmlproperty bool TextSetting::isInt
@ -100,15 +100,15 @@ Item {
id: labelItem id: labelItem
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
anchors.top: parent.top
height: parent.height height: parent.height
width: visible ? Math.max(85, implicitWidth) : 0 anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText //color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : "" text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
visible: control.label != "" visible: control.label != ""
} }
TextField { TextField {
id: input id: input
anchors.top: parent.top anchors.top: parent.top
@ -127,15 +127,10 @@ Item {
selectByMouse: true selectByMouse: true
onEditingFinished: function() { onEditingFinished: function() {
if(insertButton.focus || insertPopup.focus) return if(insertButton.focus || insertPopup.focus) return
let value = text var value = text
if(control.isInt) { if(control.isInt) value = Math.max(control.min,parseInt(value).toString()=="NaN"?control.min:parseInt(value))
let parsed = parseInt(value) if(control.isDouble) value = Math.max(control.min,parseFloat(value).toString()=="NaN"?control.min:parseFloat(value))
value = isNaN(parsed) ? control.min : Math.max(control.min,parsed) if(value != "" && value.toString() != defValue) {
} else if(control.isDouble) {
let parsed = parseFloat(value)
value = isNaN(parsed) ? control.min : Math.max(control.min,parsed)
}
if(value !== "" && value.toString() != defValue) {
control.changed(value) control.changed(value)
defValue = value.toString() defValue = value.toString()
} }
@ -144,40 +139,28 @@ Item {
Button { Button {
id: insertButton id: insertButton
text: "α"
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 5 anchors.rightMargin: 5
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 20 width: 20
height: width height: width
visible: !isInt && !isDouble visible: !isInt && !isDouble
Icon {
id: icon
width: 12
height: 12
anchors.centerIn: parent
color: sysPalette.windowText
source: '../icons/properties/expression.svg'
}
onClicked: { onClicked: {
insertPopup.open() insertPopup.open()
insertPopup.setFocus() insertPopup.focus = true
} }
} }
Popup.InsertCharacter { Popup.InsertCharacter {
id: insertPopup id: insertPopup
x: parent.width - width x: Math.round((parent.width - width) / 2)
y: parent.height y: Math.round((parent.height - height) / 2)
onSelected: function(c) { onSelected: function(c) {
input.insert(input.cursorPosition, c) input.insert(input.cursorPosition, c)
} insertPopup.close()
onClosed: function() {
focus = false focus = false
input.focus = true input.focus = true
} }

View file

@ -1,8 +1,8 @@
module eu.ad5001.LogarithmPlotter.Setting module eu.ad5001.LogarithmPlotter.Setting
AutocompletionCategory 1.0 AutocompletionCategory.qml
ComboBoxSetting 1.0 ComboBoxSetting.qml ComboBoxSetting 1.0 ComboBoxSetting.qml
ExpressionEditor 1.0 ExpressionEditor.qml
Icon 1.0 Icon.qml Icon 1.0 Icon.qml
ListSetting 1.0 ListSetting.qml ListSetting 1.0 ListSetting.qml
TextSetting 1.0 TextSetting.qml TextSetting 1.0 TextSetting.qml
ExpressionEditor 1.0 ExpressionEditor.qml
AutocompletionCategory 1.0 AutocompletionCategory.qml

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
import eu.ad5001.LogarithmPlotter.Common import "js/utils.js" as Utils
/*! /*!
\qmltype Settings \qmltype Settings
@ -44,83 +44,88 @@ ScrollView {
Zoom on the x axis of the diagram, provided from settings. Zoom on the x axis of the diagram, provided from settings.
\sa Settings \sa Settings
*/ */
property double xzoom: Helper.getSetting('default_graph.xzoom') property double xzoom: 100
/*! /*!
\qmlproperty double Settings::yzoom \qmlproperty double Settings::yzoom
Zoom on the y axis of the diagram, provided from settings. Zoom on the y axis of the diagram, provided from settings.
\sa Settings \sa Settings
*/ */
property double yzoom: Helper.getSetting('default_graph.yzoom') property double yzoom: 10
/*! /*!
\qmlproperty double Settings::xmin \qmlproperty double Settings::xmin
Minimum x of the diagram, provided from settings. Minimum x of the diagram, provided from settings.
\sa Settings \sa Settings
*/ */
property double xmin: Helper.getSetting('default_graph.xmin') property double xmin: 5/10
/*! /*!
\qmlproperty double Settings::ymax \qmlproperty double Settings::ymax
Maximum y of the diagram, provided from settings. Maximum y of the diagram, provided from settings.
\sa Settings \sa Settings
*/ */
property double ymax: Helper.getSetting('default_graph.ymax') property double ymax: 25
/*! /*!
\qmlproperty string Settings::xaxisstep \qmlproperty string Settings::xaxisstep
Step of the x axis graduation, provided from settings. Step of the x axis graduation, provided from settings.
\note: Only available in non-logarithmic mode. \note: Only available in non-logarithmic mode.
\sa Settings \sa Settings
*/ */
property string xaxisstep: Helper.getSetting('default_graph.xaxisstep') property string xaxisstep: "4"
/*! /*!
\qmlproperty string Settings::yaxisstep \qmlproperty string Settings::yaxisstep
Step of the y axis graduation, provided from settings. Step of the y axis graduation, provided from settings.
\sa Settings \sa Settings
*/ */
property string yaxisstep: Helper.getSetting('default_graph.yaxisstep') property string yaxisstep: "4"
/*! /*!
\qmlproperty string Settings::xlabel \qmlproperty string Settings::xlabel
Label used on the x axis, provided from settings. Label used on the x axis, provided from settings.
\sa Settings \sa Settings
*/ */
property string xlabel: Helper.getSetting('default_graph.xlabel') property string xlabel: ""
/*! /*!
\qmlproperty string Settings::ylabel \qmlproperty string Settings::ylabel
Label used on the y axis, provided from settings. Label used on the y axis, provided from settings.
\sa Settings \sa Settings
*/ */
property string ylabel: Helper.getSetting('default_graph.ylabel') property string ylabel: ""
/*! /*!
\qmlproperty double Settings::linewidth \qmlproperty double Settings::linewidth
Width of lines that will be drawn into the canvas, provided from settings. Width of lines that will be drawn into the canvas, provided from settings.
\sa Settings \sa Settings
*/ */
property double linewidth: Helper.getSetting('default_graph.linewidth') property double linewidth: 1
/*! /*!
\qmlproperty double Settings::textsize \qmlproperty double Settings::textsize
Font size of the text that will be drawn into the canvas, provided from settings. Font size of the text that will be drawn into the canvas, provided from settings.
\sa Settings \sa Settings
*/ */
property double textsize: Helper.getSetting('default_graph.textsize') property double textsize: 18
/*! /*!
\qmlproperty bool Settings::logscalex \qmlproperty bool Settings::logscalex
true if the canvas should be in logarithmic mode, false otherwise. true if the canvas should be in logarithmic mode, false otherwise.
Provided from settings. Provided from settings.
\sa Settings \sa Settings
*/ */
property bool logscalex: Helper.getSetting('default_graph.logscalex') property bool logscalex: true
/*! /*!
\qmlproperty bool Settings::showxgrad \qmlproperty bool Settings::showxgrad
true if the x graduation should be shown, false otherwise. true if the x graduation should be shown, false otherwise.
Provided from settings. Provided from settings.
\sa Settings \sa Settings
*/ */
property bool showxgrad: Helper.getSetting('default_graph.showxgrad') property bool showxgrad: true
/*! /*!
\qmlproperty bool Settings::showygrad \qmlproperty bool Settings::showygrad
true if the y graduation should be shown, false otherwise. true if the y graduation should be shown, false otherwise.
Provided from settings. Provided from settings.
\sa Settings \sa Settings
*/ */
property bool showygrad: Helper.getSetting('default_graph.showygrad') property bool showygrad: true
/*!
\qmlproperty bool Settings::saveFilename
Path of the currently opened file. Empty if no file is opened.
*/
property string saveFilename: ""
Column { Column {
spacing: 10 spacing: 10
@ -131,18 +136,15 @@ ScrollView {
id: fdiag id: fdiag
onAccepted: { onAccepted: {
var filePath = fdiag.currentFile.toString().substr(7) var filePath = fdiag.currentFile.toString().substr(7)
Modules.Settings.set("saveFilename", filePath) settings.saveFilename = filePath
if(exportMode) { if(exportMode) {
Modules.IO.saveDiagram(filePath) root.saveDiagram(filePath)
} else { } else {
Modules.IO.loadDiagram(filePath) root.loadDiagram(filePath)
// Adding labels. if(xAxisLabel.find(settings.xlabel) == -1) xAxisLabel.model.append({text: settings.xlabel})
if(xAxisLabel.find(Modules.Settings.xlabel) === -1) xAxisLabel.editText = settings.xlabel
xAxisLabel.model.append({text: Modules.Settings.xlabel}) if(yAxisLabel.find(settings.ylabel) == -1) yAxisLabel.model.append({text: settings.ylabel})
xAxisLabel.editText = Modules.Settings.xlabel yAxisLabel.editText = settings.ylabel
if(yAxisLabel.find(Modules.Settings.ylabel) === -1)
yAxisLabel.model.append({text: Modules.Settings.ylabel})
yAxisLabel.editText = Modules.Settings.ylabel
} }
} }
} }
@ -153,39 +155,28 @@ ScrollView {
height: 30 height: 30
isDouble: true isDouble: true
label: qsTr("X Zoom") label: qsTr("X Zoom")
min: 0.1 min: 1
icon: "settings/xzoom.svg" icon: "settings/xzoom.svg"
width: settings.settingWidth width: settings.settingWidth
value: settings.xzoom.toFixed(2)
onChanged: function(newValue) { onChanged: function(newValue) {
Modules.Settings.set("xzoom", newValue, true) settings.xzoom = newValue
settings.changed() settings.changed()
} }
function update(newValue) {
value = Modules.Settings.xzoom.toFixed(2)
maxX.update()
}
} }
Setting.TextSetting { Setting.TextSetting {
id: zoomY id: zoomY
height: 30 height: 30
isDouble: true isDouble: true
min: 0.1
label: qsTr("Y Zoom") label: qsTr("Y Zoom")
icon: "settings/yzoom.svg" icon: "settings/yzoom.svg"
width: settings.settingWidth width: settings.settingWidth
value: settings.yzoom.toFixed(2)
onChanged: function(newValue) { onChanged: function(newValue) {
Modules.Settings.set("yzoom", newValue, true) settings.yzoom = newValue
settings.changed() settings.changed()
} }
function update(newValue) {
value = Modules.Settings.yzoom.toFixed(2)
minY.update()
}
} }
// Positioning the graph // Positioning the graph
@ -197,18 +188,14 @@ ScrollView {
label: qsTr("Min X") label: qsTr("Min X")
icon: "settings/xmin.svg" icon: "settings/xmin.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: settings.xmin
onChanged: function(newValue) { onChanged: function(newValue) {
Modules.Settings.set("xmin", newValue, true) if(parseFloat(maxX.value) > newValue) {
settings.xmin = newValue
settings.changed() settings.changed()
} else {
alert.show("Minimum x value must be inferior to maximum.")
} }
function update(newValue) {
let newVal = Modules.Settings.xmin
if(newVal > 1e-5)
newVal = newVal.toDecimalPrecision(8)
value = newVal
maxX.update()
} }
} }
@ -220,16 +207,11 @@ ScrollView {
label: qsTr("Max Y") label: qsTr("Max Y")
icon: "settings/ymax.svg" icon: "settings/ymax.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: settings.ymax
onChanged: function(newValue) { onChanged: function(newValue) {
Modules.Settings.set("ymax", newValue, true) settings.ymax = newValue
settings.changed() settings.changed()
} }
function update() {
value = Modules.Settings.ymax
minY.update()
}
} }
Setting.TextSetting { Setting.TextSetting {
@ -240,24 +222,15 @@ ScrollView {
label: qsTr("Max X") label: qsTr("Max X")
icon: "settings/xmax.svg" icon: "settings/xmax.svg"
width: settings.settingWidth width: settings.settingWidth
value: canvas.px2x(canvas.canvasSize.width).toFixed(2)
onChanged: function(xvaluemax) { onChanged: function(xvaluemax) {
if(xvaluemax > Modules.Settings.xmin) { if(xvaluemax > settings.xmin) {
const newXZoom = Modules.Settings.xzoom * canvas.width/(Modules.Canvas.x2px(xvaluemax)) // Adjusting zoom to fit. = (end)/(px of current point) settings.xzoom = settings.xzoom * canvas.canvasSize.width/(canvas.x2px(xvaluemax)) // Adjusting zoom to fit. = (end)/(px of current point)
Modules.Settings.set("xzoom", newXZoom, true)
zoomX.update()
settings.changed() settings.changed()
} else { } else {
alert.show("Maximum x value must be superior to minimum.") alert.show("Maximum x value must be superior to minimum.")
} }
} }
function update() {
let newVal = Modules.Canvas.px2x(canvas.width)
if(newVal > 1e-5)
newVal = newVal.toDecimalPrecision(8)
value = newVal
}
} }
Setting.TextSetting { Setting.TextSetting {
@ -268,21 +241,15 @@ ScrollView {
label: qsTr("Min Y") label: qsTr("Min Y")
icon: "settings/ymin.svg" icon: "settings/ymin.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: canvas.px2y(canvas.canvasSize.height).toFixed(2)
onChanged: function(yvaluemin) { onChanged: function(yvaluemin) {
if(yvaluemin < settings.ymax) { if(yvaluemin < settings.ymax) {
const newYZoom = Modules.Settings.yzoom * canvas.height/(Modules.Canvas.y2px(yvaluemin)) // Adjusting zoom to fit. = (end)/(px of current point) settings.yzoom = settings.yzoom * canvas.canvasSize.height/(canvas.y2px(yvaluemin)) // Adjusting zoom to fit. = (end)/(px of current point)
Modules.Settings.set("yzoom", newYZoom, true)
zoomY.update()
settings.changed() settings.changed()
} else { } else {
alert.show("Minimum y value must be inferior to maximum.") alert.show("Minimum y value must be inferior to maximum.")
} }
} }
function update() {
value = Modules.Canvas.px2y(canvas.height).toDecimalPrecision(8)
}
} }
Setting.TextSetting { Setting.TextSetting {
@ -292,16 +259,12 @@ ScrollView {
label: qsTr("X Axis Step") label: qsTr("X Axis Step")
icon: "settings/xaxisstep.svg" icon: "settings/xaxisstep.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: settings.xaxisstep
visible: !settings.logscalex
onChanged: function(newValue) { onChanged: function(newValue) {
Modules.Settings.set("xaxisstep", newValue, true) settings.xaxisstep = newValue
settings.changed() settings.changed()
} }
function update() {
value = Modules.Settings.xaxisstep
visible = !Modules.Settings.logscalex
}
} }
Setting.TextSetting { Setting.TextSetting {
@ -311,13 +274,11 @@ ScrollView {
label: qsTr("Y Axis Step") label: qsTr("Y Axis Step")
icon: "settings/yaxisstep.svg" icon: "settings/yaxisstep.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: settings.yaxisstep
onChanged: function(newValue) { onChanged: function(newValue) {
Modules.Settings.set("yaxisstep", newValue, true) settings.yaxisstep = newValue
settings.changed() settings.changed()
} }
function update() { value = Modules.Settings.yaxisstep }
} }
Setting.TextSetting { Setting.TextSetting {
@ -328,13 +289,11 @@ ScrollView {
min: 1 min: 1
icon: "settings/linewidth.svg" icon: "settings/linewidth.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: settings.linewidth
onChanged: function(newValue) { onChanged: function(newValue) {
Modules.Settings.set("linewidth", newValue, true) settings.linewidth = newValue
settings.changed() settings.changed()
} }
function update() { value = Modules.Settings.linewidth }
} }
Setting.TextSetting { Setting.TextSetting {
@ -345,13 +304,11 @@ ScrollView {
min: 1 min: 1
icon: "settings/textsize.svg" icon: "settings/textsize.svg"
width: settings.settingWidth width: settings.settingWidth
defValue: settings.textsize
onChanged: function(newValue) { onChanged: function(newValue) {
Modules.Settings.set("textsize", newValue, true) settings.textsize = newValue
settings.changed() settings.changed()
} }
function update() { value = Modules.Settings.textsize }
} }
Setting.ComboBoxSetting { Setting.ComboBoxSetting {
@ -360,31 +317,24 @@ ScrollView {
width: settings.settingWidth width: settings.settingWidth
label: qsTr('X Label') label: qsTr('X Label')
icon: "settings/xlabel.svg" icon: "settings/xlabel.svg"
editable: true
model: ListModel { model: ListModel {
ListElement { text: "" } ListElement { text: "" }
ListElement { text: "x" } ListElement { text: "x" }
ListElement { text: "ω (rad/s)" } ListElement { text: "ω (rad/s)" }
} }
currentIndex: find(settings.xlabel)
editable: true
onAccepted: function(){ onAccepted: function(){
editText = JS.Utils.parseName(editText, false) editText = Utils.parseName(editText, false)
if(find(editText) === -1) model.append({text: editText}) if (find(editText) === -1) model.append({text: editText})
currentIndex = find(editText) settings.xlabel = editText
Modules.Settings.set("xlabel", editText, true)
settings.changed() settings.changed()
} }
onActivated: function(selectedId) { onActivated: function(selectedId) {
Modules.Settings.set("xlabel", model.get(selectedId).text, true) settings.xlabel = model.get(selectedId).text
settings.changed() settings.changed()
} }
Component.onCompleted: editText = settings.xlabel
function update() {
editText = Modules.Settings.xlabel
if(find(editText) === -1) model.append({text: editText})
currentIndex = find(editText)
}
} }
Setting.ComboBoxSetting { Setting.ComboBoxSetting {
@ -393,7 +343,6 @@ ScrollView {
width: settings.settingWidth width: settings.settingWidth
label: qsTr('Y Label') label: qsTr('Y Label')
icon: "settings/ylabel.svg" icon: "settings/ylabel.svg"
editable: true
model: ListModel { model: ListModel {
ListElement { text: "" } ListElement { text: "" }
ListElement { text: "y" } ListElement { text: "y" }
@ -402,52 +351,39 @@ ScrollView {
ListElement { text: "φ (deg)" } ListElement { text: "φ (deg)" }
ListElement { text: "φ (rad)" } ListElement { text: "φ (rad)" }
} }
currentIndex: find(settings.ylabel)
editable: true
onAccepted: function(){ onAccepted: function(){
editText = JS.Utils.parseName(editText, false) editText = Utils.parseName(editText, false)
if(find(editText) === -1) model.append({text: editText}) if (find(editText) === -1) model.append({text: editText, yaxisstep: root.yaxisstep})
currentIndex = find(editText) settings.ylabel = editText
Modules.Settings.set("ylabel", editText, true)
settings.changed() settings.changed()
} }
onActivated: function(selectedId) { onActivated: function(selectedId) {
Modules.Settings.set("ylabel", model.get(selectedId).text, true) settings.ylabel = model.get(selectedId).text
settings.changed() settings.changed()
} }
Component.onCompleted: editText = settings.ylabel
function update() {
editText = Modules.Settings.ylabel
if(find(editText) === -1) model.append({text: editText})
currentIndex = find(editText)
}
} }
CheckBox { CheckBox {
id: logScaleX id: logScaleX
checked: settings.logscalex
text: qsTr('X Log scale') text: qsTr('X Log scale')
onClicked: { onClicked: {
Modules.Settings.set("logscalex", checked, true) settings.logscalex = checked
if(Modules.Settings.xmin <= 0) // Reset xmin to prevent crash.
Modules.Settings.set("xmin", .5)
settings.changed() settings.changed()
} }
function update() {
checked = Modules.Settings.logscalex
xAxisStep.update()
}
} }
CheckBox { CheckBox {
id: showXGrad id: showXGrad
checked: settings.showxgrad
text: qsTr('Show X graduation') text: qsTr('Show X graduation')
onClicked: { onClicked: {
Modules.Settings.set("showxgrad", checked, true) settings.showxgrad = checked
settings.changed() settings.changed()
} }
function update() { checked = Modules.Settings.showxgrad }
} }
CheckBox { CheckBox {
@ -455,10 +391,9 @@ ScrollView {
checked: settings.showygrad checked: settings.showygrad
text: qsTr('Show Y graduation') text: qsTr('Show Y graduation')
onClicked: { onClicked: {
Modules.Settings.set("showygrad", checked, true) settings.showygrad = checked
settings.changed() settings.changed()
} }
function update() { checked = Modules.Settings.showygrad }
} }
Button { Button {
@ -504,10 +439,10 @@ ScrollView {
Saves the current canvas in the opened file. If no file is currently opened, prompts to pick a save location. Saves the current canvas in the opened file. If no file is currently opened, prompts to pick a save location.
*/ */
function save() { function save() {
if(Modules.Settings.saveFilename == "") { if(settings.saveFilename == "") {
saveAs() saveAs()
} else { } else {
Modules.IO.saveDiagram(Modules.Settings.saveFilename) root.saveDiagram(settings.saveFilename)
} }
} }
@ -528,30 +463,4 @@ ScrollView {
fdiag.exportMode = false fdiag.exportMode = false
fdiag.open() fdiag.open()
} }
/**
* Initializing the settings
*/
Component.onCompleted: function() {
const matchedElements = new Map([
["xzoom", zoomX],
["yzoom", zoomY],
["xmin", minX],
["ymax", maxY],
["xaxisstep", xAxisStep],
["yaxisstep", yAxisStep],
["xlabel", xAxisLabel],
["ylabel", yAxisLabel],
["linewidth", lineWidth],
["textsize", textSize],
["logscalex", logScaleX],
["showxgrad", showXGrad],
["showygrad", showYGrad]
])
Modules.Settings.on("changed", (evt) => {
if(matchedElements.has(evt.property))
matchedElements.get(evt.property).update()
})
Modules.Settings.initialize({ helper: Helper })
}
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,9 +17,14 @@
*/ */
import QtQuick import QtQuick
import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "js/objects.js" as Objects
import "js/mathlib.js" as MathLib
import "js/historylib.js" as HistoryLib
/*! /*!
\qmltype ViewPositionChange.Overlay \qmltype ViewPositionChangeOverlay
\inqmlmodule eu.ad5001.LogarithmPlotter \inqmlmodule eu.ad5001.LogarithmPlotter
\brief Overlay used allow the user to drag the canvas' position and change the zoom level. \brief Overlay used allow the user to drag the canvas' position and change the zoom level.
@ -57,6 +62,16 @@ Item {
*/ */
signal endPositionChange(int deltaX, int deltaY) signal endPositionChange(int deltaX, int deltaY)
/*!
\qmlproperty var ViewPositionChangeOverlay::canvas
LogGraphCanvas instance.
*/
property var canvas
/*!
\qmlproperty var ViewPositionChangeOverlay::settingsInstance
Settings instance.
*/
property var settingsInstance
/*! /*!
\qmlproperty int ViewPositionChangeOverlay::prevX \qmlproperty int ViewPositionChangeOverlay::prevX
The x coordinate (on the mousearea) at the last change of the canvas position. The x coordinate (on the mousearea) at the last change of the canvas position.
@ -69,7 +84,7 @@ Item {
property int prevY property int prevY
/*! /*!
\qmlproperty double ViewPositionChangeOverlay::baseZoomMultiplier \qmlproperty double ViewPositionChangeOverlay::baseZoomMultiplier
How much should the zoom be multiplied/scrolled by for one scroll step (120° on the mouse wheel). How much should the zoom be mutliplied/scrolled by for one scroll step (120° on the mouse wheel).
*/ */
property double baseZoomMultiplier: 0.1 property double baseZoomMultiplier: 0.1
@ -79,15 +94,11 @@ Item {
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
property int positionChangeTimer: 0 property int positionChangeTimer: 0
function updatePosition(deltaX, deltaY, isEnd) { function updatePosition(deltaX, deltaY) {
const unauthorized = [NaN, Infinity, -Infinity] settingsInstance.xmin = (canvas.px2x(canvas.x2px(settingsInstance.xmin)-deltaX))
const xmin = (Modules.Canvas.px2x(Modules.Canvas.x2px(Modules.Settings.xmin)-deltaX)) settingsInstance.ymax += deltaY/canvas.yzoom
const ymax = Modules.Settings.ymax + deltaY/Modules.Settings.yzoom settingsInstance.ymax = settingsInstance.ymax.toFixed(4)
if(!unauthorized.includes(xmin)) settingsInstance.changed()
Modules.Settings.set("xmin", xmin, isEnd)
if(!unauthorized.includes(ymax))
Modules.Settings.set("ymax", ymax.toDecimalPrecision(6), isEnd)
Modules.Canvas.requestPaint()
parent.positionChanged(deltaX, deltaY) parent.positionChanged(deltaX, deltaY)
} }
@ -97,49 +108,43 @@ Item {
prevY = mouse.y prevY = mouse.y
parent.beginPositionChange() parent.beginPositionChange()
} }
onPositionChanged: function(mouse) { onPositionChanged: function(mouse) {
positionChangeTimer++ positionChangeTimer++
if(positionChangeTimer == 3) { if(positionChangeTimer == 3) {
let deltaX = mouse.x - parent.prevX let deltaX = mouse.x - prevX
let deltaY = mouse.y - parent.prevY let deltaY = mouse.y - prevY
updatePosition(deltaX, deltaY, false) updatePosition(deltaX, deltaY)
prevX = mouse.x prevX = mouse.x
prevY = mouse.y prevY = mouse.y
positionChangeTimer = 0 positionChangeTimer = 0
} }
} }
onReleased: function(mouse) { onReleased: function(mouse) {
let deltaX = mouse.x - parent.prevX let deltaX = mouse.x - prevX
let deltaY = mouse.y - parent.prevY let deltaY = mouse.y - prevY
updatePosition(deltaX, deltaY, true) updatePosition(deltaX, deltaY)
parent.endPositionChange(deltaX, deltaY) parent.endPositionChange(deltaX, deltaY)
} }
onWheel: function(wheel) { onWheel: function(wheel) {
// Scrolling // Scrolling
let scrollSteps = Math.round(wheel.angleDelta.y / 120) let scrollSteps = Math.round(wheel.angleDelta.y / 120)
let zoomMultiplier = Math.pow(1+parent.baseZoomMultiplier, Math.abs(scrollSteps)) let zoomMultiplier = Math.pow(1+baseZoomMultiplier, Math.abs(scrollSteps))
// Avoid floating-point rounding errors by removing the zoom *after* // Avoid floating-point rounding errors by removing the zoom *after*
let xZoomDelta = (Modules.Settings.xzoom*zoomMultiplier - Modules.Settings.xzoom) let xZoomDelta = (settingsInstance.xzoom*zoomMultiplier - settingsInstance.xzoom)
let yZoomDelta = (Modules.Settings.yzoom*zoomMultiplier - Modules.Settings.yzoom) let yZoomDelta = (settingsInstance.yzoom*zoomMultiplier - settingsInstance.yzoom)
if(scrollSteps < 0) { // Negative scroll if(scrollSteps < 0) { // Negative scroll
xZoomDelta *= -1 xZoomDelta *= -1
yZoomDelta *= -1 yZoomDelta *= -1
} }
let newXZoom = (Modules.Settings.xzoom+xZoomDelta).toDecimalPrecision(0) let newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(0)
let newYZoom = (Modules.Settings.yzoom+yZoomDelta).toDecimalPrecision(0) let newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(0)
// Check if we need to have more precision if(newXZoom == settingsInstance.xzoom) // No change, allow more precision.
if(newXZoom < 10) newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(4)
newXZoom = (Modules.Settings.xzoom+xZoomDelta).toDecimalPrecision(4) if(newYZoom == settingsInstance.yzoom) // No change, allow more precision.
if(newYZoom < 10) newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(4)
newYZoom = (Modules.Settings.yzoom+yZoomDelta).toDecimalPrecision(4) settingsInstance.xzoom = newXZoom
if(newXZoom > 0.5) settingsInstance.yzoom = newYZoom
Modules.Settings.set("xzoom", newXZoom) settingsInstance.changed()
if(newYZoom > 0.5)
Modules.Settings.set("yzoom", newYZoom)
Modules.Canvas.requestPaint()
} }
} }
} }

View file

Before

Width:  |  Height:  |  Size: 370 B

After

Width:  |  Height:  |  Size: 370 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 289 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 620 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 261 B

After

Width:  |  Height:  |  Size: 261 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 273 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 270 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

@ -0,0 +1 @@
../../common/appearance.svg

View file

@ -0,0 +1 @@
../../common/appearance.svg

View file

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

@ -0,0 +1 @@
../../common/arrow.svg

View file

@ -0,0 +1 @@
../../common/position.svg

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

@ -0,0 +1 @@
../../common/angle.svg

View file

@ -0,0 +1 @@
../../common/appearance.svg

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

@ -0,0 +1 @@
../../common/target.svg

View file

@ -0,0 +1 @@
../../common/position.svg

View file

@ -0,0 +1 @@
../../common/label.svg

View file

@ -0,0 +1 @@
../../common/angle.svg

View file

@ -0,0 +1 @@
../../common/position.svg

View file

@ -0,0 +1 @@
../../common/position.svg

View file

@ -0,0 +1 @@
../../common/angle.svg

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 675 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 414 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more