Compare commits
No commits in common. "master" and "v0.2.0" have entirely different histories.
8
.gitignore
vendored
|
@ -16,20 +16,20 @@ linux/flatpak/.flatpak-builder
|
||||||
*.jsc
|
*.jsc
|
||||||
*.qmlc
|
*.qmlc
|
||||||
*.log
|
*.log
|
||||||
**/*.dxvk-cache
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
.ropeproject
|
.ropeproject
|
||||||
.vscode
|
.vscode
|
||||||
*.kdev4
|
|
||||||
.kdev4
|
|
||||||
.coverage
|
|
||||||
build
|
build
|
||||||
docs/html
|
docs/html
|
||||||
.directory
|
.directory
|
||||||
|
*.kdev4
|
||||||
*.lpf
|
*.lpf
|
||||||
*.lgg
|
*.lgg
|
||||||
*.spec
|
*.spec
|
||||||
|
.kdev4
|
||||||
|
AccountFree.pro
|
||||||
|
AccountFree.pro.user
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
|
|
3
.gitmodules
vendored
|
@ -1,3 +1,6 @@
|
||||||
[submodule "LogarithmPlotter/qml/eu/ad5001/MixedMenu"]
|
[submodule "LogarithmPlotter/qml/eu/ad5001/MixedMenu"]
|
||||||
path = 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
|
||||||
|
[submodule "linux/flatpak"]
|
||||||
|
path = linux/flatpak
|
||||||
|
url = https://github.com/Ad5001/eu.ad5001.LogarithmPlotter
|
||||||
|
|
118
CHANGELOG.md
|
@ -1,123 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v0.5.0 (11 Jan 2024)
|
|
||||||
|
|
||||||
**New**
|
|
||||||
|
|
||||||
* New, reworked application icon.
|
|
||||||
* Graph is now mouse interactive:
|
|
||||||
* You can now drag to move and scroll to zoom!
|
|
||||||
* Builtin functions now provide usage when used in the autocomplete of the expression editor.
|
|
||||||
|
|
||||||
**Changes**
|
|
||||||
|
|
||||||
* When creating an object that can be positioned, new default behavior is to pick first instead of opening object settings.
|
|
||||||
* Icons with text now use the SVG's text element, allowing them to integrate better with the system's default font.
|
|
||||||
* Special characters popup is now context aware (e.g. no sub/supscript symbols in expressions).
|
|
||||||
* New symbols in special characters popup.
|
|
||||||
* Integrals and derivatives can now be provided with an executable object (e.g. Functions) instead of strings as function.
|
|
||||||
* New description on Linux.
|
|
||||||
|
|
||||||
**Fixed bugs**
|
|
||||||
|
|
||||||
* Fixing ∞ 'variable' in domains and expressions.
|
|
||||||
* Several other bugs related to constants in expresions were fixed as well.
|
|
||||||
* Builtin functions now send an error message when not provided with the proper arguments.
|
|
||||||
|
|
||||||
**Internal changes**
|
|
||||||
|
|
||||||
* Updated to PySide6 v6.6.1.
|
|
||||||
* Reworked continuous functions' rendering to make it faster.
|
|
||||||
* Removed old bits from an unfinished new parser that weren't used.
|
|
||||||
|
|
||||||
## v0.4.0 (27 May 2023)
|
|
||||||
|
|
||||||
**Changes**
|
|
||||||
|
|
||||||
* Fully ported to PySide6 (Qt6).
|
|
||||||
* Greet screen settings are now scrollable.
|
|
||||||
* Changelog is now freezed to current version.
|
|
||||||
|
|
||||||
**New**
|
|
||||||
|
|
||||||
* Customizable color schemes for expressions.
|
|
||||||
* New, rewamped and improved picked location overlay settings:
|
|
||||||
* It's now possible to disable picking x or y when setting a location.
|
|
||||||
* Properties which are related to positioning (X, Y, Label's X position) can now be set using the picker.
|
|
||||||
* Visual redesign that enhances readability of settings.
|
|
||||||
* There is now a button to hide picker settings.
|
|
||||||
|
|
||||||
**Fixed bugs**
|
|
||||||
|
|
||||||
* Cursors in expression are now easier to see.
|
|
||||||
* Symbols in LaTeX rendered Texts cause the LaTeX renderer to crash.
|
|
||||||
* Underscores in distribution names are automatically removed if the name is modified.
|
|
||||||
* Autocomplete categories now properly respect theme colors.
|
|
||||||
* Functions in expressions (like indexOf, map...) now properly send errors when the arguments are of the wrong type or count.
|
|
||||||
* Executable Objects called (like functions, bode magnitures, phases...) now send an error if provided with no arguments.
|
|
||||||
* Function calls with no argument no longer make LogarithmPlotter crash under certain circumstances.
|
|
||||||
* Thank you dialog's lists are no longer draggable.
|
|
||||||
|
|
||||||
**Internal changes**
|
|
||||||
|
|
||||||
* A lot of inner changes led by porting to Qt6, fixing a lot of bugs at the same time.
|
|
||||||
* Disabled auto detect of visual theme if the `QT_QUICK_CONTROLS_STYLE` environment variable is set.
|
|
||||||
* (macOS, Windows, Flatpak) Drastically reducing installer sizes (more than halved).
|
|
||||||
* (Launchpad/Ubuntu) Using custom built packages of PySide6, meaning smaller installation and distro dependency.
|
|
||||||
|
|
||||||
## v0.3.0 (28 Oct 2022)
|
|
||||||
|
|
||||||
**New**
|
|
||||||
|
|
||||||
* New completely revamped expression editor:
|
|
||||||
* Automatic closing of parentheses and brackets (can be disabled in settings).
|
|
||||||
* Syntax highlighting (can be disabled in the settings).
|
|
||||||
* Autocompletion is now available (for functions, variables and constants, object names and properties) (can be disabled in the settings).
|
|
||||||
* Object properties can now be used in expressions (e.g. if you have a point named A, you can use A.x to access its x value).
|
|
||||||
* Executable objects can be now be used in expressions (e.g. if you have a function named 'f', it's accessible using `f(<value>)`).
|
|
||||||
* LaTeX-rendered formulas are now used in the Objects and History tabs when LaTeX rendering is enabled.
|
|
||||||
* Errors in formulas are now reported in message boxes.
|
|
||||||
|
|
||||||
**Changes**
|
|
||||||
|
|
||||||
* The Object Editor dialog has been completely reworked internally, resulting in notable performance improvements.
|
|
||||||
* Vast improvements to the objects system: names can no longer be shared amongst different objects.
|
|
||||||
* Disabled access to custom variable and function definition in expressions (can cause issues and vulnerabilities)
|
|
||||||
* When using the set position cursor, the position change is now saved a single history action.
|
|
||||||
* Distribution are now prefixed with an 'F_' to prevent confusion with X Cursors.
|
|
||||||
|
|
||||||
**Added translations**
|
|
||||||
|
|
||||||
* Autocompletion categories (English, French, German, Hungarian).
|
|
||||||
* Expression editor settings (English, French, German, Hungarian).
|
|
||||||
* Expression syntax errors (English, French, German, Hungarian).
|
|
||||||
* On top of the above:
|
|
||||||
* Hungarian: v0.2.0 added text (thanks @ovari!)
|
|
||||||
* Spanish: Menu bars (thanks @Sergio Varela)
|
|
||||||
* You can contribute to translation on [Weblate](https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/#repository).
|
|
||||||
|
|
||||||
**Fixed bugs**
|
|
||||||
|
|
||||||
* Fixing Texts not being properly recognized as texts when saving.
|
|
||||||
* Text's 'Disable LaTeX' property is now properly saved.
|
|
||||||
* X Cursors LaTeX rendering made the app crash.
|
|
||||||
* Attempting to insert special character no longer automatically saves the expression you're editing.
|
|
||||||
* Proper HDPI support for icons and buttons (note: HDPI is not available for the rendered canvas yet).
|
|
||||||
* Support for non-latin characters in variables (e.g. greek letters, subtext, suptext)
|
|
||||||
* Silent error when misentering variable names in the expression editor causing internal issues.
|
|
||||||
* Fixing some utils function simplifying parentheses when they shouldn't have.
|
|
||||||
* (flatpak and KDE SDK) Fixing the sometimes invisible buttons on the objects tab on startup.
|
|
||||||
* (macos) Application string version does not match LogarithmPlotter's version.
|
|
||||||
* (debian) (Normally) Fixing deb building.
|
|
||||||
|
|
||||||
**Internal changes**
|
|
||||||
|
|
||||||
* Object dependencies are now registered on both the dependant object, and the object it's depending on.
|
|
||||||
* Objects now have a proper per-name registry.
|
|
||||||
* Object Editor Dialog has been reworked to use loaders insteads.
|
|
||||||
* Reworked the file loading system to be able to load dependencies properly.
|
|
||||||
|
|
||||||
|
|
||||||
## v0.2.0 (22 Apr 2022)
|
## v0.2.0 (22 Apr 2022)
|
||||||
|
|
||||||
**New**
|
**New**
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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.2.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,7 +27,6 @@ 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 AccountFree directory.
|
cwd = realpath(join(dirname(__file__), '..')) # Root AccountFree directory.
|
||||||
|
@ -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()
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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
|
||||||
|
@ -15,6 +15,6 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
from LogarithmPlotter.logarithmplotter import create_qapp
|
if __name__ == "__main__":
|
||||||
|
from .logarithmplotter import run
|
||||||
app = create_qapp()
|
run()
|
|
@ -1,54 +1,2 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
|
||||||
# This file automatically renames .mjs files to js, and (tries) to fix most common ECMAScript
|
|
||||||
# specificities so that lupdate doesn't cry out in pain.
|
|
||||||
# See also: https://bugreports.qt.io/browse/QTBUG-123819
|
|
||||||
#
|
|
||||||
|
|
||||||
escape() {
|
|
||||||
str="$1"
|
|
||||||
str="${str//\//\\/}" # Escape slashes
|
|
||||||
str="${str//\*/\\*}" # Escape asterixes
|
|
||||||
echo "$str"
|
|
||||||
}
|
|
||||||
|
|
||||||
replace() {
|
|
||||||
file="$1"
|
|
||||||
from="$(escape "$2")"
|
|
||||||
to="$(escape "$3")"
|
|
||||||
sed -i "s/${from}/${to}/g" "$file"
|
|
||||||
}
|
|
||||||
|
|
||||||
files=$(find .. -name *.mjs)
|
|
||||||
for file in $files; do
|
|
||||||
echo "Moving '$file' to '${file%.*}.js'..."
|
|
||||||
mv "$file" "${file%.*}.js"
|
|
||||||
# Replacements to make it valid js
|
|
||||||
replace "${file%.*}.js" "^import" "/*import"
|
|
||||||
replace "${file%.*}.js" '.mjs"$' '.mjs"*/'
|
|
||||||
replace "${file%.*}.js" "^export default" "/*export default*/"
|
|
||||||
replace "${file%.*}.js" "^export" "/*export*/"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "----------------------------"
|
|
||||||
echo "| Updating translations... |"
|
|
||||||
echo "----------------------------"
|
|
||||||
lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts
|
lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts
|
||||||
# Updating locations in files
|
|
||||||
for lp in *.ts; do
|
|
||||||
echo "Replacing locations in $lp..."
|
|
||||||
for file in $files; do
|
|
||||||
echo " > Replacing for file $file..."
|
|
||||||
replace "$lp" "${file%.*}.js" "$file"
|
|
||||||
done
|
|
||||||
done
|
|
||||||
|
|
||||||
for file in $files; do
|
|
||||||
echo "Moving '${file%.*}.js' to '$file'..."
|
|
||||||
mv "${file%.*}.js" "$file"
|
|
||||||
# Resetting changes
|
|
||||||
replace "$file" "^/*import" "import"
|
|
||||||
replace "$file" '.mjs"*/$' '.mjs"'
|
|
||||||
replace "$file" "^/*export default*/" "export default"
|
|
||||||
replace "$file" "^/*export*/" "export"
|
|
||||||
done
|
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,18 +16,18 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from os import getcwd, chdir, environ, path
|
|
||||||
from platform import release as os_release
|
|
||||||
from sys import path as sys_path
|
|
||||||
from sys import platform, argv, exit
|
|
||||||
from tempfile import TemporaryDirectory
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PySide6.QtCore import QTranslator, QLocale
|
from PySide2.QtWidgets import QApplication
|
||||||
from PySide6.QtGui import QIcon
|
from PySide2.QtQml import QQmlApplicationEngine
|
||||||
from PySide6.QtQml import QQmlApplicationEngine
|
from PySide2.QtCore import Qt, QTranslator, QLocale
|
||||||
from PySide6.QtQuickControls2 import QQuickStyle
|
from PySide2.QtGui import QIcon
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
|
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()
|
start_time = time()
|
||||||
|
|
||||||
|
@ -36,163 +36,119 @@ tempdir = TemporaryDirectory()
|
||||||
tmpfile = path.join(tempdir.name, 'graph.png')
|
tmpfile = path.join(tempdir.name, 'graph.png')
|
||||||
pwd = getcwd()
|
pwd = getcwd()
|
||||||
|
|
||||||
logarithmplotter_path = path.dirname(path.realpath(__file__))
|
chdir(path.dirname(path.realpath(__file__)))
|
||||||
chdir(logarithmplotter_path)
|
|
||||||
|
|
||||||
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
|
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
|
||||||
sys_path.append(path.realpath(path.join(getcwd(), "..")))
|
sys_path.append(path.realpath(path.join(getcwd(), "..")))
|
||||||
|
|
||||||
|
|
||||||
from LogarithmPlotter import __VERSION__
|
from LogarithmPlotter import __VERSION__
|
||||||
from LogarithmPlotter.util import config, native
|
from LogarithmPlotter.util import config, native
|
||||||
from LogarithmPlotter.util.update import check_for_updates
|
from LogarithmPlotter.util.update import check_for_updates
|
||||||
from LogarithmPlotter.util.helper import Helper
|
from LogarithmPlotter.util.helper import Helper
|
||||||
from LogarithmPlotter.util.latex import Latex
|
from LogarithmPlotter.util.latex import Latex
|
||||||
from LogarithmPlotter.util.js import PyJSValue
|
|
||||||
|
|
||||||
LINUX_THEMES = { # See https://specifications.freedesktop.org/menu-spec/latest/onlyshowin-registry.html
|
config.init()
|
||||||
"COSMIC": "Basic",
|
|
||||||
"GNOME": "Basic",
|
|
||||||
"GNOME-Classic": "Basic",
|
|
||||||
"GNOME-Flashback": "Basic",
|
|
||||||
"KDE": "Fusion",
|
|
||||||
"LXDE": "Basic",
|
|
||||||
"LXQt": "Fusion",
|
|
||||||
"MATE": "Fusion",
|
|
||||||
"TDE": "Fusion",
|
|
||||||
"Unity": "Basic",
|
|
||||||
"XFCE": "Basic",
|
|
||||||
"Cinnamon": "Fusion",
|
|
||||||
"Pantheon": "Basic",
|
|
||||||
"DDE": "Basic",
|
|
||||||
"EDE": "Fusion",
|
|
||||||
"Endless": "Basic",
|
|
||||||
"Old": "Fusion",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
def get_linux_theme():
|
||||||
def get_linux_theme() -> str:
|
des = {
|
||||||
|
"KDE": "org.kde.desktop",
|
||||||
|
"gnome": "default",
|
||||||
|
"lxqt": "fusion",
|
||||||
|
"mate": "fusion",
|
||||||
|
}
|
||||||
if "XDG_SESSION_DESKTOP" in environ:
|
if "XDG_SESSION_DESKTOP" in environ:
|
||||||
if environ["XDG_SESSION_DESKTOP"] in LINUX_THEMES:
|
return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "fusion"
|
||||||
return LINUX_THEMES[environ["XDG_SESSION_DESKTOP"]]
|
|
||||||
return "Fusion"
|
|
||||||
else:
|
else:
|
||||||
# Android
|
# Android
|
||||||
return "Material"
|
return "Material"
|
||||||
|
|
||||||
|
def run():
|
||||||
|
|
||||||
def get_platform_qt_style(os) -> str:
|
environ["QT_QUICK_CONTROLS_STYLE"] = {
|
||||||
return {
|
|
||||||
"linux": get_linux_theme(),
|
"linux": get_linux_theme(),
|
||||||
"freebsd": get_linux_theme(),
|
"freebsd": get_linux_theme(),
|
||||||
"win32": "Universal" if os_release() in ["10", "11", "12", "13", "14"] else "Windows",
|
"win32": "universal" if os_release == "10" else "fusion",
|
||||||
"cygwin": "Fusion",
|
"cygwin": "fusion",
|
||||||
"darwin": "macOS"
|
"darwin": "default"
|
||||||
}[os]
|
}[platform]
|
||||||
|
|
||||||
|
dep_time = time()
|
||||||
|
print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.")
|
||||||
|
|
||||||
def register_icon_directories() -> None:
|
icon_fallbacks = QIcon.fallbackSearchPaths();
|
||||||
icon_fallbacks = QIcon.fallbackSearchPaths()
|
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
|
||||||
base_icon_path = path.join(logarithmplotter_path, "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
|
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "common")))
|
||||||
paths = [["common"], ["objects"], ["history"], ["settings"], ["settings", "custom"]]
|
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "objects")))
|
||||||
for p in paths:
|
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "history")))
|
||||||
icon_fallbacks.append(path.realpath(path.join(base_icon_path, *p)))
|
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings")))
|
||||||
QIcon.setFallbackSearchPaths(icon_fallbacks)
|
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
|
||||||
|
QIcon.setFallbackSearchPaths(icon_fallbacks);
|
||||||
|
|
||||||
|
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
||||||
def create_qapp() -> QApplication:
|
|
||||||
app = QApplication(argv)
|
app = QApplication(argv)
|
||||||
app.setApplicationName("LogarithmPlotter")
|
app.setApplicationName("LogarithmPlotter")
|
||||||
app.setApplicationDisplayName("LogarithmPlotter")
|
|
||||||
app.setApplicationVersion(f"v{__VERSION__}")
|
|
||||||
app.setDesktopFileName("eu.ad5001.LogarithmPlotter")
|
|
||||||
app.setOrganizationName("Ad5001")
|
app.setOrganizationName("Ad5001")
|
||||||
app.styleHints().setShowShortcutsInContextMenus(True)
|
app.styleHints().setShowShortcutsInContextMenus(True)
|
||||||
app.setWindowIcon(QIcon(path.realpath(path.join(logarithmplotter_path, "logarithmplotter.svg"))))
|
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
def install_translation(app: QApplication) -> QTranslator:
|
|
||||||
# Installing translators
|
# Installing translators
|
||||||
translator = QTranslator()
|
translator = QTranslator()
|
||||||
# Check if lang is forced.
|
# Check if lang is forced.
|
||||||
forcedlang = [p for p in argv if p[:7] == "--lang="]
|
forcedlang = [p for p in argv if p[:7]=="--lang="]
|
||||||
i18n_path = path.realpath(path.join(logarithmplotter_path, "i18n"))
|
|
||||||
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
|
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
|
||||||
if not translator.load(locale, "lp", "_", i18n_path):
|
if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))):
|
||||||
# Load default translation
|
app.installTranslator(translator);
|
||||||
print("Loading default language en...")
|
|
||||||
translator.load(QLocale("en"), "lp", "_", i18n_path)
|
|
||||||
app.installTranslator(translator)
|
|
||||||
return translator
|
|
||||||
|
|
||||||
|
# Installing macOS file handler.
|
||||||
|
macOSFileOpenHandler = None
|
||||||
|
if platform == "darwin":
|
||||||
|
macOSFileOpenHandler = native.MacOSFileOpenHandler()
|
||||||
|
app.installEventFilter(macOSFileOpenHandler)
|
||||||
|
|
||||||
def create_engine(helper: Helper, latex: Latex, dep_time: float) -> tuple[QQmlApplicationEngine, PyJSValue]:
|
|
||||||
global tmpfile
|
|
||||||
engine = QQmlApplicationEngine()
|
engine = QQmlApplicationEngine()
|
||||||
js_globals = PyJSValue(engine.globalObject())
|
global tmpfile
|
||||||
js_globals.Modules = engine.newObject()
|
helper = Helper(pwd, tmpfile)
|
||||||
js_globals.Helper = engine.newQObject(helper)
|
latex = Latex(tempdir)
|
||||||
js_globals.Latex = engine.newQObject(latex)
|
engine.rootContext().setContextProperty("Helper", helper)
|
||||||
|
engine.rootContext().setContextProperty("Latex", latex)
|
||||||
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
|
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
|
||||||
engine.rootContext().setContextProperty("StartTime", dep_time)
|
engine.rootContext().setContextProperty("StartTime", dep_time)
|
||||||
|
|
||||||
qml_path = path.realpath(path.join(logarithmplotter_path, "qml"))
|
app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
|
||||||
engine.addImportPath(qml_path)
|
|
||||||
engine.load(path.join(qml_path, "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml"))
|
|
||||||
|
|
||||||
return engine, js_globals
|
engine.addImportPath(path.realpath(path.join(getcwd(), "qml")))
|
||||||
|
engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
|
||||||
|
|
||||||
|
|
||||||
def run():
|
if not engine.rootObjects():
|
||||||
config.init()
|
|
||||||
|
|
||||||
if not 'QT_QUICK_CONTROLS_STYLE' in environ:
|
|
||||||
QQuickStyle.setStyle(get_platform_qt_style(platform))
|
|
||||||
|
|
||||||
dep_time = time()
|
|
||||||
print("Loaded dependencies in " + str((dep_time - start_time) * 1000) + "ms.")
|
|
||||||
|
|
||||||
register_icon_directories()
|
|
||||||
app = create_qapp()
|
|
||||||
translator = install_translation(app)
|
|
||||||
|
|
||||||
# Installing macOS file handler.
|
|
||||||
macos_file_open_handler = None
|
|
||||||
if platform == "darwin":
|
|
||||||
macos_file_open_handler = native.MacOSFileOpenHandler()
|
|
||||||
app.installEventFilter(macos_file_open_handler)
|
|
||||||
|
|
||||||
helper = Helper(pwd, tmpfile)
|
|
||||||
latex = Latex(tempdir)
|
|
||||||
engine, js_globals = create_engine(helper, latex, dep_time)
|
|
||||||
|
|
||||||
if len(engine.rootObjects()) == 0: # No root objects loaded
|
|
||||||
print("No root object", path.realpath(path.join(getcwd(), "qml")))
|
print("No root object", path.realpath(path.join(getcwd(), "qml")))
|
||||||
|
print(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
|
||||||
exit(-1)
|
exit(-1)
|
||||||
|
|
||||||
# Open the current diagram
|
# Open the current diagram
|
||||||
chdir(pwd)
|
chdir(pwd)
|
||||||
if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']:
|
if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']:
|
||||||
js_globals.Modules.IO.loadDiagram(argv[-1])
|
engine.rootObjects()[0].loadDiagram(argv[-1])
|
||||||
chdir(path.dirname(path.realpath(__file__)))
|
chdir(path.dirname(path.realpath(__file__)))
|
||||||
|
|
||||||
if platform == "darwin":
|
if platform == "darwin":
|
||||||
macos_file_open_handler.init_io(js_globals.Modules.IO)
|
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
|
||||||
|
|
||||||
# Check for LaTeX installation if LaTeX support is enabled
|
# Check for LaTeX installation if LaTeX support is enabled
|
||||||
if config.getSetting("enable_latex"):
|
if config.getSetting("enable_latex"):
|
||||||
latex.checkLatexInstallation()
|
latex.check_latex_install()
|
||||||
|
|
||||||
# Check for updates
|
# Check for updates
|
||||||
if config.getSetting("check_for_updates"):
|
if config.getSetting("check_for_updates"):
|
||||||
check_for_updates(__VERSION__, engine.rootObjects()[0])
|
check_for_updates(__VERSION__, engine.rootObjects()[0])
|
||||||
|
|
||||||
exit_code = app.exec()
|
exit_code = app.exec_()
|
||||||
|
|
||||||
tempdir.cleanup()
|
tempdir.cleanup()
|
||||||
config.save()
|
config.save()
|
||||||
exit(exit_code)
|
exit(exit_code)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run()
|
run()
|
||||||
|
|
||||||
|
|
|
@ -1,64 +1,9 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg id="SVGRoot" width="48" height="48" version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg
|
<title>LogarithmPlotter Icon v1.0</title>
|
||||||
width="24.0px"
|
<g fill-rule="evenodd" stroke-width="2">
|
||||||
height="24.0px"
|
<rect width="48" height="48" ry="6" fill="#fff"/>
|
||||||
viewBox="0 0 24.0 24.0"
|
<rect x="2" y="38" width="44" height="4" stroke-opacity="0"/>
|
||||||
version="1.1"
|
<rect x="18" y="2" width="4" height="44" stroke-opacity="0"/>
|
||||||
id="SVGRoot"
|
</g>
|
||||||
xml:space="preserve"
|
<path d="M 42.05,2 A 40.05,36.05 0 0 1 2,38.05" fill="none" stroke="#f00" stroke-width="3.9012"/>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</svg>
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
|
|
||||||
id="title836">LogarithmPlotter Icon v1.0</title><defs
|
|
||||||
id="defs833" /><metadata
|
|
||||||
id="metadata836"><rdf:RDF><cc:Work
|
|
||||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>LogarithmPlotter Icon v1.0</dc:title><cc:license
|
|
||||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" /><dc:date>2021</dc:date><dc:creator><cc:Agent><dc:title>Ad5001</dc:title></cc:Agent></dc:creator><dc:rights><cc:Agent><dc:title>(c) Ad5001 2021 - All rights reserved</dc:title></cc:Agent></dc:rights></cc:Work><cc:License
|
|
||||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/"><cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
|
|
||||||
id="layer2"
|
|
||||||
transform="matrix(1,0,0,0.94444444,0,1.1666667)"
|
|
||||||
style="fill:#666666"><rect
|
|
||||||
style="fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="rect1546"
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
x="3"
|
|
||||||
y="3"
|
|
||||||
ry="2.25" /></g><g
|
|
||||||
id="layer2-6"
|
|
||||||
transform="matrix(1,0,0,0.94444444,0,0.16666668)"
|
|
||||||
style="fill:#f9f9f9"><rect
|
|
||||||
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="rect1546-7"
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
x="3"
|
|
||||||
y="3"
|
|
||||||
ry="2.25" /></g><g
|
|
||||||
id="layer1"
|
|
||||||
style="stroke-width:2;stroke-dasharray:none"><rect
|
|
||||||
style="fill:#000000;fill-rule:evenodd;stroke-width:1.86898;stroke-dasharray:none;stroke-opacity:0"
|
|
||||||
id="rect1410"
|
|
||||||
width="14"
|
|
||||||
height="2"
|
|
||||||
x="5"
|
|
||||||
y="15.5" /><rect
|
|
||||||
style="fill:#000000;fill-rule:evenodd;stroke-width:2;stroke-dasharray:none;stroke-opacity:0"
|
|
||||||
id="rect1412"
|
|
||||||
width="2"
|
|
||||||
height="15"
|
|
||||||
x="9"
|
|
||||||
y="3.9768662" /><path
|
|
||||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="path1529"
|
|
||||||
d="M 18,4 C 18,10.017307 13.40948,15.5 5,15.5" /></g></svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 488 B |
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,13 @@
|
||||||
* 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 2.12
|
||||||
import Qt.labs.platform as Native
|
import QtQuick.Dialogs 1.3
|
||||||
//import QtQuick.Controls 2.15
|
//import QtQuick.Controls 2.15
|
||||||
import eu.ad5001.MixedMenu 1.1
|
import eu.ad5001.MixedMenu 1.1
|
||||||
import "js/history/index.mjs" as HistoryLib
|
import "js/objects.js" as Objects
|
||||||
|
import "js/historylib.js" as HistoryLib
|
||||||
|
import "js/math/latex.js" as Latex
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -89,36 +91,30 @@ MenuBar {
|
||||||
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
|
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
|
||||||
enabled: history.redoCount > 0
|
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'
|
||||||
}
|
}
|
||||||
MenuSeparator { }
|
|
||||||
Action {
|
|
||||||
text: qsTr("&Preferences")
|
|
||||||
shortcut: StandardKey.Copy
|
|
||||||
onTriggered: preferences.open()
|
|
||||||
icon.name: 'settings'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
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)
|
||||||
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
|
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
|
||||||
objectLists.update()
|
objectLists.update()
|
||||||
}
|
}
|
||||||
|
@ -126,6 +122,42 @@ MenuBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
title: qsTr("&Settings")
|
||||||
|
Action {
|
||||||
|
id: checkForUpdatesMenuSetting
|
||||||
|
text: qsTr("Check for updates on startup")
|
||||||
|
checkable: true
|
||||||
|
checked: Helper.getSettingBool("check_for_updates")
|
||||||
|
onTriggered: Helper.setSettingBool("check_for_updates", checked)
|
||||||
|
icon.name: 'update'
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
|
||||||
|
Action {
|
||||||
|
id: enableLatexSetting
|
||||||
|
text: qsTr("Enable LaTeX rendering")
|
||||||
|
checkable: true
|
||||||
|
checked: Helper.getSettingBool("enable_latex")
|
||||||
|
onTriggered: {
|
||||||
|
Helper.setSettingBool("enable_latex", checked)
|
||||||
|
Latex.enabled = checked
|
||||||
|
drawCanvas.requestPaint()
|
||||||
|
}
|
||||||
|
icon.name: 'Expression'
|
||||||
|
|
||||||
|
Component.onCompleted: Latex.enabled = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
title: qsTr("&Help")
|
title: qsTr("&Help")
|
||||||
Action {
|
Action {
|
||||||
|
@ -167,17 +199,16 @@ MenuBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Native.MessageDialog {
|
MessageDialog {
|
||||||
id: saveUnsavedChangesDialog
|
id: saveUnsavedChangesDialog
|
||||||
title: qsTr("Save unsaved changes?")
|
title: qsTr("Save unsaved changes?")
|
||||||
|
icon: StandardIcon.Question
|
||||||
text: qsTr("This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue?")
|
text: qsTr("This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue?")
|
||||||
buttons: Native.MessageDialog.Save | Native.MessageDialog.Discard | Native.MessageDialog.Cancel
|
standardButtons: StandardButton.Yes | StandardButton.No
|
||||||
|
onYes: Qt.quit()
|
||||||
onSaveClicked: settings.save()
|
|
||||||
onDiscardClicked: Qt.quit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSaveUnsavedChangesDialog() {
|
function showSaveUnsavedChangesDialog() {
|
||||||
saveUnsavedChangesDialog.open()
|
saveUnsavedChangesDialog.visible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,17 +16,18 @@
|
||||||
* 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 2.12
|
||||||
import QtQml
|
import QtQml 2.12
|
||||||
import QtQuick.Window
|
import "../js/objects.js" as Objects
|
||||||
import "../js/history/index.mjs" as HistoryLib
|
import "../js/historylib.js" as HistoryLib
|
||||||
|
import "../js/history/common.js" as HistoryCommon
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype History
|
\qmltype History
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.History
|
\inqmlmodule eu.ad5001.LogarithmPlotter.History
|
||||||
\brief QObject holding persistantly for undo & redo stacks.
|
\brief QObject holding persistantly for undo & redo stacks.
|
||||||
|
|
||||||
\sa HistoryBrowser, HistoryLib
|
\sa HistoryBrowser, historylib
|
||||||
*/
|
*/
|
||||||
Item {
|
Item {
|
||||||
// Using a QtObject is necessary in order to have proper property propagation in QML
|
// Using a QtObject is necessary in order to have proper property propagation in QML
|
||||||
|
@ -107,7 +108,7 @@ Item {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod void History::addToHistory(var action)
|
\qmlmethod void History::addToHistory(var action)
|
||||||
Adds an instance of HistoryLib.Action to history.
|
Adds an instance of historylib.Action to history.
|
||||||
*/
|
*/
|
||||||
function addToHistory(action) {
|
function addToHistory(action) {
|
||||||
if(action instanceof HistoryLib.Action) {
|
if(action instanceof HistoryLib.Action) {
|
||||||
|
@ -124,7 +125,7 @@ Item {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod void History::undo(bool updateObjectList = true)
|
\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.
|
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.
|
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) {
|
function undo(updateObjectList = true) {
|
||||||
|
@ -142,7 +143,7 @@ Item {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod void History::redo(bool updateObjectList = true)
|
\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.
|
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.
|
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) {
|
function redo(updateObjectList = true) {
|
||||||
|
@ -160,7 +161,7 @@ Item {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod void History::undoMultipleDefered(int toUndoCount)
|
\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.
|
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.
|
It undoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
|
||||||
*/
|
*/
|
||||||
function undoMultipleDefered(toUndoCount) {
|
function undoMultipleDefered(toUndoCount) {
|
||||||
|
@ -173,7 +174,7 @@ Item {
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlmethod void History::redoMultipleDefered(int toRedoCount)
|
\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.
|
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.
|
It redoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
|
||||||
*/
|
*/
|
||||||
function redoMultipleDefered(toRedoCount) {
|
function redoMultipleDefered(toRedoCount) {
|
||||||
|
@ -212,11 +213,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
Modules.History.initialize({
|
HistoryLib.history = historyObj
|
||||||
historyObj,
|
HistoryCommon.themeTextColor = sysPalette.windowText
|
||||||
themeTextColor: sysPalette.windowText.toString(),
|
|
||||||
imageDepth: Screen.devicePixelRatio,
|
|
||||||
fontSize: 14
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,10 +16,10 @@
|
||||||
* 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.Controls
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick
|
import QtQuick 2.12
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
||||||
import "../js/utils.mjs" as Utils
|
import "../js/utils.js" as Utils
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -53,9 +53,7 @@ Item {
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,10 +16,10 @@
|
||||||
* 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.Controls
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick
|
import QtQuick 2.12
|
||||||
import Qt5Compat.GraphicalEffects
|
import QtGraphicalEffects 1.15
|
||||||
import "../js/utils.mjs" as Utils
|
import "../js/utils.js" as Utils
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,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 +132,8 @@ Button {
|
||||||
color: sysPalette.windowText
|
color: sysPalette.windowText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//text: content
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.delay: 200
|
ToolTip.delay: 200
|
||||||
ToolTip.text: content
|
ToolTip.text: content
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,10 +16,10 @@
|
||||||
* 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 2.12
|
||||||
import Qt.labs.platform as Native
|
import "js/objects.js" as Objects
|
||||||
import "js/utils.mjs" as Utils
|
import "js/utils.js" as Utils
|
||||||
import "js/math/index.mjs" as MathLib
|
import "js/mathlib.js" as MathLib
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype LogGraphCanvas
|
\qmltype LogGraphCanvas
|
||||||
|
@ -120,6 +120,43 @@ Canvas {
|
||||||
*/
|
*/
|
||||||
property bool showygrad: false
|
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
|
\qmlproperty var LogGraphCanvas::imageLoaders
|
||||||
Dictionary of format {image: [callback.image data]} containing data for defered image loading.
|
Dictionary of format {image: [callback.image data]} containing data for defered image loading.
|
||||||
|
@ -131,25 +168,25 @@ Canvas {
|
||||||
*/
|
*/
|
||||||
property var ctx
|
property var ctx
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: imageLoaders = {}
|
||||||
imageLoaders = {}
|
|
||||||
Modules.Canvas.initialize({ canvas, drawingErrorDialog })
|
|
||||||
}
|
|
||||||
|
|
||||||
Native.MessageDialog {
|
|
||||||
id: drawingErrorDialog
|
|
||||||
title: qsTranslate("expression", "LogarithmPlotter - Drawing error")
|
|
||||||
text: ""
|
|
||||||
function show(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) {
|
onPaint: function(rect) {
|
||||||
//console.log('Redrawing')
|
//console.log('Redrawing')
|
||||||
if(rect.width == canvas.width) { // Redraw full canvas
|
if(rect.width == canvas.width) { // Redraw full canvas
|
||||||
Modules.Canvas.redraw()
|
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) obj.draw(canvas, ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.lineWidth = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,4 +199,277 @@ Canvas {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\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*(y==0)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(var x = 1; x < drawMaxX; x += 1) {
|
||||||
|
var drawX = x*xaxisstep1
|
||||||
|
var txtX = xaxisstepExpr.simplify(x)
|
||||||
|
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)
|
||||||
|
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(visible(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(visible(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::visible(double x, double y)
|
||||||
|
Checks whether a plot point (\c x, \c y) is visible or not on the canvas.
|
||||||
|
*/
|
||||||
|
function visible(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,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 QtQml
|
import QtQml 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
import eu.ad5001.MixedMenu 1.1
|
import eu.ad5001.MixedMenu 1.1
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick
|
import QtQuick 2.12
|
||||||
|
// Auto loading all objects.
|
||||||
// Auto loading all modules.
|
import "js/objs/autoload.js" as ALObjects
|
||||||
import "js/autoload.mjs" as ModulesAutoload
|
|
||||||
|
|
||||||
|
import "js/objects.js" as Objects
|
||||||
import eu.ad5001.LogarithmPlotter.History 1.0
|
import eu.ad5001.LogarithmPlotter.History 1.0
|
||||||
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
|
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
|
||||||
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
|
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
|
||||||
|
@ -55,8 +55,6 @@ ApplicationWindow {
|
||||||
|
|
||||||
Popup.GreetScreen {}
|
Popup.GreetScreen {}
|
||||||
|
|
||||||
Popup.Preferences {id: preferences}
|
|
||||||
|
|
||||||
Popup.Changelog {id: changelog}
|
Popup.Changelog {id: changelog}
|
||||||
|
|
||||||
Popup.About {id: about}
|
Popup.About {id: about}
|
||||||
|
@ -161,7 +159,7 @@ ApplicationWindow {
|
||||||
|
|
||||||
property bool firstDrawDone: false
|
property bool firstDrawDone: false
|
||||||
|
|
||||||
onPainted: if(!firstDrawDone) {
|
onPainted: if(!firstDrawDone) {
|
||||||
firstDrawDone = true;
|
firstDrawDone = true;
|
||||||
console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms")
|
console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms")
|
||||||
if(TestBuild == true) {
|
if(TestBuild == true) {
|
||||||
|
@ -170,13 +168,6 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewPositionChangeOverlay {
|
|
||||||
id: viewPositionChanger
|
|
||||||
anchors.fill: parent
|
|
||||||
canvas: parent
|
|
||||||
settingsInstance: settings
|
|
||||||
}
|
|
||||||
|
|
||||||
PickLocationOverlay {
|
PickLocationOverlay {
|
||||||
id: positionPicker
|
id: positionPicker
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
@ -184,6 +175,120 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\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 = {}
|
||||||
|
for(var objType in data['objects']) {
|
||||||
|
if(Object.keys(Objects.types).indexOf(objType) > -1) {
|
||||||
|
Objects.currentObjects[objType] = []
|
||||||
|
for(var objData of data['objects'][objType]) {
|
||||||
|
var obj = new Objects.types[objType](...objData)
|
||||||
|
Objects.currentObjects[objType].push(obj)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error += qsTr("Unknown object type: %1.").arg(objType) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
Timer {
|
||||||
id: delayRefreshTimer
|
id: delayRefreshTimer
|
||||||
repeat: false
|
repeat: false
|
||||||
|
@ -198,26 +303,10 @@ ApplicationWindow {
|
||||||
onTriggered: Qt.quit() // Quit after paint on test build
|
onTriggered: Qt.quit() // Quit after paint on test build
|
||||||
}
|
}
|
||||||
|
|
||||||
onClosing: function(close) {
|
onClosing: {
|
||||||
if(!history.saved) {
|
if(!history.saved) {
|
||||||
close.accepted = false
|
close.accepted = false
|
||||||
appMenu.openSaveUnsavedChangesDialog()
|
appMenu.showSaveUnsavedChangesDialog()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod void LogarithmPlotter::updateObjectsLists()
|
|
||||||
Updates the objects lists when loading a file.
|
|
||||||
*/
|
|
||||||
function updateObjectsLists() {
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +337,7 @@ ApplicationWindow {
|
||||||
Action {
|
Action {
|
||||||
text: qsTr("&Update LogarithmPlotter")
|
text: qsTr("&Update LogarithmPlotter")
|
||||||
icon.name: 'update'
|
icon.name: 'update'
|
||||||
onTriggered: Qt.openUrlExternally("https://apps.ad5001.eu/logarithmplotter/")
|
onTriggered: Qt.openUrlExternally("https://dev.apps.ad5001.eu/logarithmplotter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,342 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import Qt.labs.platform as Native
|
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
|
||||||
import "../../js/history/index.mjs" as HistoryLib
|
|
||||||
import "../../js/utils.mjs" as Utils
|
|
||||||
import "../../js/math/index.mjs" as MathLib
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype CustomPropertyList
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.ObjectLists.Editor
|
|
||||||
\brief Lists all custom properties editors inside a repeater and allow for edition.
|
|
||||||
|
|
||||||
This class repeats all of the custom properties and loads the appropriate editor for each kind of property.
|
|
||||||
|
|
||||||
\sa Dialog
|
|
||||||
*/
|
|
||||||
Repeater {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
signal changed()
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty var CustomPropertyList::obj
|
|
||||||
Object whose properties to list and edit.
|
|
||||||
*/
|
|
||||||
property var obj
|
|
||||||
/*!
|
|
||||||
\qmlproperty var CustomPropertyList::positionPicker
|
|
||||||
Reference to the global PositionPicker QML object.
|
|
||||||
*/
|
|
||||||
property var positionPicker
|
|
||||||
|
|
||||||
readonly property var textTypes: ['Domain', 'string', 'number', 'int']
|
|
||||||
readonly property var comboBoxTypes: ['ObjectType', 'Enum']
|
|
||||||
readonly property var listTypes: ['List', 'Dict']
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: All components have the declared properties 'propertyLabel', 'propertyIcon', propertyName' and 'propertyType' to access the object in question.
|
|
||||||
Component {
|
|
||||||
id: commentComponent
|
|
||||||
|
|
||||||
// Item for comments.
|
|
||||||
// NOTE: propertyType here is the content of the comment (yes, it's a bit backwards, but it's more clear on the properties side).
|
|
||||||
Label {
|
|
||||||
// Translated text with object name.
|
|
||||||
property string trText: qsTranslate('comment', propertyType).toString()
|
|
||||||
text: (trText.includes("%1") ? trText.arg(obj.name) : trText).toString()
|
|
||||||
//color: sysPalette.windowText
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: expressionEditorComponent
|
|
||||||
|
|
||||||
// Setting for expressions
|
|
||||||
Setting.ExpressionEditor {
|
|
||||||
height: 30
|
|
||||||
label: propertyLabel
|
|
||||||
icon: `settings/custom/${propertyIcon}.svg`
|
|
||||||
defValue: Utils.simplifyExpression(obj[propertyName].toEditableString())
|
|
||||||
self: obj.name
|
|
||||||
variables: propertyType.variables
|
|
||||||
onChanged: function(newExpr) {
|
|
||||||
if(obj[propertyName].toString() != newExpr.toString()) {
|
|
||||||
history.addToHistory(new HistoryLib.EditedProperty(
|
|
||||||
obj.name, objType, propertyName,
|
|
||||||
obj[propertyName], newExpr
|
|
||||||
))
|
|
||||||
obj[propertyName] = newExpr
|
|
||||||
root.changed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: textEditorComponent
|
|
||||||
|
|
||||||
// Setting for text & number settings as well as domains
|
|
||||||
Setting.TextSetting {
|
|
||||||
height: 30
|
|
||||||
label: propertyLabel
|
|
||||||
icon: `settings/custom/${propertyIcon}.svg`
|
|
||||||
min: propertyType == "int" ? 0 : -Infinity
|
|
||||||
isInt: propertyType == "int"
|
|
||||||
isDouble: propertyType == "number"
|
|
||||||
defValue: obj[propertyName] == null ? '' : obj[propertyName].toString()
|
|
||||||
category: {
|
|
||||||
return {
|
|
||||||
"Domain": "domain",
|
|
||||||
"string": "all",
|
|
||||||
"number": "all",
|
|
||||||
"int": "all",
|
|
||||||
}[propertyType]
|
|
||||||
}
|
|
||||||
onChanged: function(newValue) {
|
|
||||||
try {
|
|
||||||
var newValueParsed = {
|
|
||||||
"Domain": () => MathLib.parseDomain(newValue),
|
|
||||||
"string": () => newValue,
|
|
||||||
"number": () => newValue,
|
|
||||||
"int": () => newValue
|
|
||||||
}[propertyType]()
|
|
||||||
|
|
||||||
// Ensuring old and new values are different to prevent useless adding to history.
|
|
||||||
if(obj[propertyName] != newValueParsed) {
|
|
||||||
history.addToHistory(new HistoryLib.EditedProperty(
|
|
||||||
obj.name, objType, propertyName,
|
|
||||||
obj[propertyName], newValueParsed
|
|
||||||
))
|
|
||||||
obj[propertyName] = newValueParsed
|
|
||||||
root.changed()
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
// Error in expression or domain
|
|
||||||
console.trace()
|
|
||||||
parsingErrorDialog.showDialog(propertyName, newValue, e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Native.MessageDialog {
|
|
||||||
id: parsingErrorDialog
|
|
||||||
title: qsTranslate("expression", "LogarithmPlotter - Parsing error")
|
|
||||||
text: ""
|
|
||||||
function showDialog(propName, propValue, error) {
|
|
||||||
text = qsTranslate("error", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3")
|
|
||||||
.arg(qsTranslate('prop', propName))
|
|
||||||
.arg(error).arg(propValue)
|
|
||||||
open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: checkboxComponent
|
|
||||||
|
|
||||||
// Setting for boolean
|
|
||||||
CheckBox {
|
|
||||||
height: 20
|
|
||||||
text: propertyLabel
|
|
||||||
//icon: `settings/custom/${propertyIcon}.svg`
|
|
||||||
|
|
||||||
checked: {
|
|
||||||
//if(obj[propertyName] == null) {
|
|
||||||
// return false
|
|
||||||
//}
|
|
||||||
return obj[propertyName]
|
|
||||||
}
|
|
||||||
onClicked: {
|
|
||||||
history.addToHistory(new HistoryLib.EditedProperty(
|
|
||||||
obj.name, objType, propertyName,
|
|
||||||
obj[propertyName], this.checked
|
|
||||||
))
|
|
||||||
obj[propertyName] = this.checked
|
|
||||||
root.changed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: comboBoxComponent
|
|
||||||
|
|
||||||
// Setting when selecting data from an enum, or an object of a certain type.
|
|
||||||
Setting.ComboBoxSetting {
|
|
||||||
height: 30
|
|
||||||
label: propertyLabel
|
|
||||||
icon: `settings/custom/${propertyIcon}.svg`
|
|
||||||
// True to select an object of type, false for enums.
|
|
||||||
property bool selectObjMode: paramTypeIn(propertyType, ['ObjectType'])
|
|
||||||
property bool isRealObject: !selectObjMode || (propertyType.objType != "ExecutableObject" && propertyType.objType != "DrawableObject")
|
|
||||||
|
|
||||||
// Base, untranslated version of the model.
|
|
||||||
property var baseModel: selectObjMode ?
|
|
||||||
Modules.Objects.getObjectsName(propertyType.objType).concat(
|
|
||||||
isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] : [])
|
|
||||||
: propertyType.values
|
|
||||||
// Translated version of the model.
|
|
||||||
model: selectObjMode ? baseModel : propertyType.translatedValues
|
|
||||||
currentIndex: baseModel.indexOf(selectObjMode ? obj[propertyName].name : obj[propertyName])
|
|
||||||
|
|
||||||
onActivated: function(newIndex) {
|
|
||||||
if(selectObjMode) {
|
|
||||||
// This is only done when what we're selecting are Objects.
|
|
||||||
// Setting object property.
|
|
||||||
var selectedObj = Modules.Objects.currentObjectsByName[baseModel[newIndex]]
|
|
||||||
if(newIndex != 0) {
|
|
||||||
// Make sure we don't set the object to null.
|
|
||||||
if(selectedObj == null) {
|
|
||||||
// Creating new object.
|
|
||||||
selectedObj = Modules.Objects.createNewRegisteredObject(propertyType.objType)
|
|
||||||
history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export()))
|
|
||||||
baseModel = Modules.Objects.getObjectsName(propertyType.objType).concat(
|
|
||||||
isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] :
|
|
||||||
[])
|
|
||||||
currentIndex = baseModel.indexOf(selectedObj.name)
|
|
||||||
}
|
|
||||||
selectedObj.requiredBy.push(Modules.Objects.currentObjects[objType][objIndex])
|
|
||||||
//Modules.Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name)
|
|
||||||
}
|
|
||||||
obj.requiredBy = obj.requiredBy.filter((obj) => obj.name != obj.name)
|
|
||||||
history.addToHistory(new HistoryLib.EditedProperty(
|
|
||||||
obj.name, objType, propertyName,
|
|
||||||
obj[propertyName], selectedObj
|
|
||||||
))
|
|
||||||
obj[propertyName] = selectedObj
|
|
||||||
} else if(baseModel[newIndex] != obj[propertyName]) {
|
|
||||||
// Ensuring new property is different to not add useless history entries.
|
|
||||||
history.addToHistory(new HistoryLib.EditedProperty(
|
|
||||||
obj.name, objType, propertyName,
|
|
||||||
obj[propertyName], baseModel[newIndex]
|
|
||||||
))
|
|
||||||
obj[propertyName] = baseModel[newIndex]
|
|
||||||
}
|
|
||||||
// Refreshing
|
|
||||||
root.changed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
// Setting to edit lists or dictionaries (e.g sequences & repartition function values)
|
|
||||||
id: listDictEditorComponent
|
|
||||||
|
|
||||||
Setting.ListSetting {
|
|
||||||
label: propertyLabel
|
|
||||||
//icon: `settings/custom/${propertyIcon}.svg`
|
|
||||||
dictionaryMode: paramTypeIn(propertyType, ['Dict'])
|
|
||||||
keyType: dictionaryMode ? propertyType.keyType : 'string'
|
|
||||||
valueType: propertyType.valueType
|
|
||||||
preKeyLabel: (dictionaryMode ? propertyType.preKeyLabel : propertyType.label).replace(/\{name\}/g, obj.name).replace(/\{name_\}/g, obj.name.substring(obj.name.indexOf("_")+1))
|
|
||||||
postKeyLabel: (dictionaryMode ? propertyType.postKeyLabel : '').replace(/\{name\}/g, obj.name).replace(/\{name_\}/g, obj.name.substring(obj.name.indexOf("_")+1))
|
|
||||||
keyRegexp: dictionaryMode ? propertyType.keyFormat : /^.+$/
|
|
||||||
valueRegexp: propertyType.format
|
|
||||||
forbidAdding: propertyType.forbidAdding
|
|
||||||
|
|
||||||
onChanged: {
|
|
||||||
var exported = exportModel()
|
|
||||||
history.addToHistory(new HistoryLib.EditedProperty(
|
|
||||||
obj.name, objType, propertyName,
|
|
||||||
obj[propertyName], exported
|
|
||||||
))
|
|
||||||
//Modules.Objects.currentObjects[objType][objIndex][propertyName] = exported
|
|
||||||
obj[propertyName] = exported
|
|
||||||
root.changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
importModel(obj[propertyName])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Component {
|
|
||||||
Row {
|
|
||||||
width: dlgProperties.width
|
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: propertyEditor
|
|
||||||
width: dlgProperties.width - pointerButton.width
|
|
||||||
property string propertyName: modelData[0]
|
|
||||||
property var propertyType: modelData[1]
|
|
||||||
property string propertyLabel: qsTranslate('prop',propertyName)
|
|
||||||
property string propertyIcon: Utils.camelCase2readable(propertyName)
|
|
||||||
|
|
||||||
sourceComponent: {
|
|
||||||
if(propertyName.startsWith('comment'))
|
|
||||||
return commentComponent
|
|
||||||
else if(propertyType == 'boolean')
|
|
||||||
return checkboxComponent
|
|
||||||
else if(paramTypeIn(propertyType, ['Expression']))
|
|
||||||
return expressionEditorComponent
|
|
||||||
else if(paramTypeIn(propertyType, textTypes))
|
|
||||||
return textEditorComponent
|
|
||||||
else if(paramTypeIn(propertyType, comboBoxTypes))
|
|
||||||
return comboBoxComponent
|
|
||||||
else if(paramTypeIn(propertyType, listTypes))
|
|
||||||
return listDictEditorComponent
|
|
||||||
else
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: pointerButton
|
|
||||||
height: parent.height
|
|
||||||
width: visible ? height : 0
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
property bool isXProp: ['labelX', 'x'].includes(propertyEditor.propertyName)
|
|
||||||
property bool isYProp: ['y'].includes(propertyEditor.propertyName)
|
|
||||||
visible: isXProp || isYProp
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Pick on graph")
|
|
||||||
|
|
||||||
Setting.Icon {
|
|
||||||
id: icon
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
color: sysPalette.windowText
|
|
||||||
source: '../icons/common/position.svg'
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
positionPicker.objType = objType
|
|
||||||
positionPicker.objName = obj.name
|
|
||||||
positionPicker.pickX = isXProp
|
|
||||||
positionPicker.pickY = isYProp
|
|
||||||
positionPicker.propertyX = propertyEditor.propertyName
|
|
||||||
positionPicker.propertyY = propertyEditor.propertyName
|
|
||||||
positionPicker.visible = true
|
|
||||||
objEditor.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,170 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Dialogs as D
|
|
||||||
import Qt.labs.platform as Native
|
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
|
||||||
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
|
|
||||||
import "../../js/history/index.mjs" as HistoryLib
|
|
||||||
import "../../js/utils.mjs" as Utils
|
|
||||||
import "../../js/math/index.mjs" as MathLib
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype Dialog
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.ObjectLists.Editor
|
|
||||||
\brief Dialog used to edit properties of objects.
|
|
||||||
|
|
||||||
This class contains the dialog that allows to edit all properties of objects.
|
|
||||||
\todo In the future, this class should be optimized so that each property doesn't instanciate one instance of each setting type.
|
|
||||||
|
|
||||||
\sa Loader, ObjectLists
|
|
||||||
*/
|
|
||||||
Popup.BaseDialog {
|
|
||||||
id: objEditor
|
|
||||||
/*!
|
|
||||||
\qmlproperty string EditorDialog::objType
|
|
||||||
Type of object being edited by the dialog.
|
|
||||||
*/
|
|
||||||
property string objType: 'Point'
|
|
||||||
/*!
|
|
||||||
\qmlproperty int EditorDialog::objIndex
|
|
||||||
Index of the objects amongst the ones of it's type.
|
|
||||||
*/
|
|
||||||
property int objIndex: 0
|
|
||||||
/*!
|
|
||||||
\qmlproperty var EditorDialog::obj
|
|
||||||
Instance of the object being edited.
|
|
||||||
*/
|
|
||||||
property var obj: Modules.Objects.currentObjects[objType][objIndex]
|
|
||||||
/*!
|
|
||||||
\qmlproperty var EditorDialog::posPicker
|
|
||||||
Reference to the global PositionPicker QML object.
|
|
||||||
*/
|
|
||||||
property var posPicker
|
|
||||||
|
|
||||||
title: "LogarithmPlotter"
|
|
||||||
width: 350
|
|
||||||
minimumHeight: Math.max(450,dlgProperties.height + margin*4 + 30)
|
|
||||||
maximumHeight: minimumHeight
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors {
|
|
||||||
top: parent.top;
|
|
||||||
left: parent.left;
|
|
||||||
bottom: parent.bottom;
|
|
||||||
right: parent.right;
|
|
||||||
topMargin: margin;
|
|
||||||
leftMargin: margin;
|
|
||||||
bottomMargin: margin + 30;
|
|
||||||
rightMargin: margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: dlgProperties
|
|
||||||
anchors.top: parent.top
|
|
||||||
width: objEditor.width - 20
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: dlgTitle
|
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
text: qsTr("Edit properties of %1 %2").arg(Modules.Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name)
|
|
||||||
font.pixelSize: 20
|
|
||||||
color: sysPalette.windowText
|
|
||||||
}
|
|
||||||
|
|
||||||
Native.MessageDialog {
|
|
||||||
id: invalidNameDialog
|
|
||||||
title: qsTr("LogarithmPlotter - Invalid object name")
|
|
||||||
text: ""
|
|
||||||
function showDialog(objectName) {
|
|
||||||
text = qsTr("An object with the name '%1' already exists.").arg(objectName)
|
|
||||||
open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Setting.TextSetting {
|
|
||||||
id: nameProperty
|
|
||||||
height: 30
|
|
||||||
label: qsTr("Name")
|
|
||||||
icon: "common/label.svg"
|
|
||||||
category: "name"
|
|
||||||
width: dlgProperties.width
|
|
||||||
value: objEditor.obj.name
|
|
||||||
onChanged: function(newValue) {
|
|
||||||
let newName = Utils.parseName(newValue)
|
|
||||||
if(newName != '' && objEditor.obj.name != newName) {
|
|
||||||
if(newName in Modules.Objects.currentObjectsByName) {
|
|
||||||
invalidNameDialog.showDialog(newName)
|
|
||||||
} else {
|
|
||||||
history.addToHistory(new HistoryLib.NameChanged(
|
|
||||||
objEditor.obj.name, objEditor.objType, newName
|
|
||||||
))
|
|
||||||
Modules.Objects.renameObject(obj.name, newName)
|
|
||||||
objEditor.obj = Modules.Objects.currentObjects[objEditor.objType][objEditor.objIndex]
|
|
||||||
objectListList.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Setting.ComboBoxSetting {
|
|
||||||
id: labelContentProperty
|
|
||||||
height: 30
|
|
||||||
width: dlgProperties.width
|
|
||||||
label: qsTr("Label content")
|
|
||||||
model: [qsTr("null"), qsTr("name"), qsTr("name + value")]
|
|
||||||
property var idModel: ["null", "name", "name + value"]
|
|
||||||
icon: "common/label.svg"
|
|
||||||
currentIndex: idModel.indexOf(objEditor.obj.labelContent)
|
|
||||||
onActivated: function(newIndex) {
|
|
||||||
if(idModel[newIndex] != objEditor.obj.labelContent) {
|
|
||||||
objEditor.obj.labelContent = idModel[newIndex]
|
|
||||||
objEditor.obj.update()
|
|
||||||
objectListList.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dynamic properties
|
|
||||||
CustomPropertyList {
|
|
||||||
id: dlgCustomProperties
|
|
||||||
obj: objEditor.obj
|
|
||||||
positionPicker: posPicker
|
|
||||||
|
|
||||||
onChanged: {
|
|
||||||
obj.update()
|
|
||||||
objectListList.update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod void EditorDialog::open()
|
|
||||||
Shows the editor after the object to be edited is set.
|
|
||||||
*/
|
|
||||||
function open() {
|
|
||||||
dlgCustomProperties.model = [] // Reset
|
|
||||||
let objProps = Modules.Objects.types[objEditor.objType].properties()
|
|
||||||
dlgCustomProperties.model = Object.keys(objProps).map(prop => [prop, objProps[prop]]) // Converted to 2-dimentional array.
|
|
||||||
objEditor.show()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
module eu.ad5001.LogarithmPlotter.ObjectLists.Editor
|
|
||||||
|
|
||||||
Dialog 1.0 Dialog.qml
|
|
||||||
CustomPropertyList 1.0 CustomPropertyList.qml
|
|
||||||
|
|
|
@ -0,0 +1,309 @@
|
||||||
|
/**
|
||||||
|
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||||
|
* Copyright (C) 2022 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 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Dialogs 1.3 as D
|
||||||
|
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
||||||
|
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 EditorDialog
|
||||||
|
\inqmlmodule eu.ad5001.LogarithmPlotter.ObjectLists
|
||||||
|
\brief Dialog used to edit properties of objects.
|
||||||
|
|
||||||
|
This class contains the dialog that allows to edit all properties of objects.
|
||||||
|
\todo In the future, this class should be optimized so that each property doesn't instanciate one instance of each setting type.
|
||||||
|
|
||||||
|
\sa LogarithmPlotter, ObjectLists
|
||||||
|
*/
|
||||||
|
D.Dialog {
|
||||||
|
id: objEditor
|
||||||
|
/*!
|
||||||
|
\qmlproperty string EditorDialog::objType
|
||||||
|
Type of object being edited by the dialog.
|
||||||
|
*/
|
||||||
|
property string objType: 'Point'
|
||||||
|
/*!
|
||||||
|
\qmlproperty int EditorDialog::objIndex
|
||||||
|
Index of the objects amongst the ones of it's type.
|
||||||
|
*/
|
||||||
|
property int objIndex: 0
|
||||||
|
/*!
|
||||||
|
\qmlproperty var EditorDialog::obj
|
||||||
|
Instance of the object being edited.
|
||||||
|
*/
|
||||||
|
property var obj: Objects.currentObjects[objType][objIndex]
|
||||||
|
|
||||||
|
title: "LogarithmPlotter"
|
||||||
|
width: 350
|
||||||
|
height: 400
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: dlgTitle
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
text: qsTr("Edit properties of %1 %2").arg(Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name)
|
||||||
|
font.pixelSize: 20
|
||||||
|
color: sysPalette.windowText
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: dlgProperties
|
||||||
|
anchors.top: dlgTitle.bottom
|
||||||
|
width: objEditor.width - 20
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Setting.TextSetting {
|
||||||
|
id: nameProperty
|
||||||
|
height: 30
|
||||||
|
label: qsTr("Name")
|
||||||
|
icon: "common/label.svg"
|
||||||
|
min: 1
|
||||||
|
width: dlgProperties.width
|
||||||
|
value: objEditor.obj.name
|
||||||
|
onChanged: function(newValue) {
|
||||||
|
var newName = Utils.parseName(newValue)
|
||||||
|
if(newName != '' && objEditor.obj.name != newName) {
|
||||||
|
if(Objects.getObjectByName(newName) != null) {
|
||||||
|
newName = ObjectsCommons.getNewName(newName)
|
||||||
|
}
|
||||||
|
history.addToHistory(new HistoryLib.NameChanged(
|
||||||
|
objEditor.obj.name, objEditor.objType, newName
|
||||||
|
))
|
||||||
|
Objects.currentObjects[objEditor.objType][objEditor.objIndex].name = newName
|
||||||
|
objEditor.obj = Objects.currentObjects[objEditor.objType][objEditor.objIndex]
|
||||||
|
objectListList.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Setting.ComboBoxSetting {
|
||||||
|
id: labelContentProperty
|
||||||
|
height: 30
|
||||||
|
width: dlgProperties.width
|
||||||
|
label: qsTr("Label content")
|
||||||
|
model: [qsTr("null"), qsTr("name"), qsTr("name + value")]
|
||||||
|
property var idModel: ["null", "name", "name + value"]
|
||||||
|
icon: "common/label.svg"
|
||||||
|
currentIndex: idModel.indexOf(objEditor.obj.labelContent)
|
||||||
|
onActivated: function(newIndex) {
|
||||||
|
if(idModel[newIndex] != objEditor.obj.labelContent) {
|
||||||
|
objEditor.obj.labelContent = idModel[newIndex]
|
||||||
|
objectListList.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic properties
|
||||||
|
Repeater {
|
||||||
|
id: dlgCustomProperties
|
||||||
|
|
||||||
|
Item {
|
||||||
|
height: customPropComment.height + customPropText.height + customPropCheckBox.height + customPropCombo.height + customPropListDict.height
|
||||||
|
width: dlgProperties.width
|
||||||
|
property string label: qsTranslate('prop',modelData[0])
|
||||||
|
property string icon: Utils.camelCase2readable(modelData[0])
|
||||||
|
|
||||||
|
// Item for comments
|
||||||
|
Label {
|
||||||
|
id: customPropComment
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
visible: modelData[0].startsWith('comment')
|
||||||
|
// Translated text with object name.
|
||||||
|
property string trText: visible ? qsTranslate('comment', modelData[1]).toString() : ''
|
||||||
|
text: (visible && trText.includes("%1") ? trText.arg(objEditor.obj.name) : trText).toString()
|
||||||
|
//color: sysPalette.windowText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting for text & number settings as well as domains & expressions
|
||||||
|
Setting.TextSetting {
|
||||||
|
id: customPropText
|
||||||
|
height: visible ? 30 : 0
|
||||||
|
width: parent.width
|
||||||
|
label: parent.label
|
||||||
|
icon: `settings/custom/${parent.icon}.svg`
|
||||||
|
isDouble: modelData[1] == 'number'
|
||||||
|
visible: paramTypeIn(modelData[1], ['Expression', 'Domain', 'string', 'number'])
|
||||||
|
defValue: visible ? {
|
||||||
|
'Expression': () => Utils.simplifyExpression(objEditor.obj[modelData[0]].toEditableString()),
|
||||||
|
'Domain': () => objEditor.obj[modelData[0]].toString(),
|
||||||
|
'string': () => objEditor.obj[modelData[0]],
|
||||||
|
'number': () => objEditor.obj[modelData[0]]
|
||||||
|
}[modelData[1]]() : ""
|
||||||
|
onChanged: function(newValue) {
|
||||||
|
var newValue = {
|
||||||
|
'Expression': () => new MathLib.Expression(newValue),
|
||||||
|
'Domain': () => MathLib.parseDomain(newValue),
|
||||||
|
'string': () => newValue,
|
||||||
|
'number': () => parseFloat(newValue)
|
||||||
|
}[modelData[1]]()
|
||||||
|
// Ensuring old and new values are different to prevent useless adding to history.
|
||||||
|
if(objEditor.obj[modelData[0]] != newValue) {
|
||||||
|
history.addToHistory(new HistoryLib.EditedProperty(
|
||||||
|
objEditor.obj.name, objEditor.objType, modelData[0],
|
||||||
|
objEditor.obj[modelData[0]], newValue
|
||||||
|
))
|
||||||
|
objEditor.obj[modelData[0]] = newValue
|
||||||
|
Objects.currentObjects[objEditor.objType][objEditor.objIndex].update()
|
||||||
|
objectListList.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting for boolean
|
||||||
|
CheckBox {
|
||||||
|
id: customPropCheckBox
|
||||||
|
visible: modelData[1] == 'boolean'
|
||||||
|
height: visible ? 20 : 0
|
||||||
|
width: parent.width
|
||||||
|
text: parent.label
|
||||||
|
//icon: visible ? `settings/custom/${parent.icon}.svg` : ''
|
||||||
|
|
||||||
|
checked: visible ? objEditor.obj[modelData[0]] : false
|
||||||
|
onClicked: {
|
||||||
|
history.addToHistory(new HistoryLib.EditedProperty(
|
||||||
|
objEditor.obj.name, objEditor.objType, modelData[0],
|
||||||
|
objEditor.obj[modelData[0]], this.checked
|
||||||
|
))
|
||||||
|
objEditor.obj[modelData[0]] = this.checked
|
||||||
|
Objects.currentObjects[objEditor.objType][objEditor.objIndex].update()
|
||||||
|
objectListList.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting when selecting data from an enum, or an object of a certain type.
|
||||||
|
Setting.ComboBoxSetting {
|
||||||
|
id: customPropCombo
|
||||||
|
width: dlgProperties.width
|
||||||
|
height: visible ? 30 : 0
|
||||||
|
label: parent.label
|
||||||
|
icon: visible ? `settings/custom/${parent.icon}.svg` : ''
|
||||||
|
// True to select an object of type, false for enums.
|
||||||
|
property bool selectObjMode: paramTypeIn(modelData[1], ['ObjectType'])
|
||||||
|
property bool isRealObject: !selectObjMode || (modelData[1].objType != "ExecutableObject" && modelData[1].objType != "DrawableObject")
|
||||||
|
|
||||||
|
// Base, untranslated version of the model.
|
||||||
|
property var baseModel: visible ?
|
||||||
|
(selectObjMode ?
|
||||||
|
Objects.getObjectsName(modelData[1].objType).concat(
|
||||||
|
isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[modelData[1].objType].displayType())] :
|
||||||
|
[]) :
|
||||||
|
modelData[1].values)
|
||||||
|
: []
|
||||||
|
// Translated verison of the model.
|
||||||
|
model: selectObjMode ? baseModel : modelData[1].translatedValues
|
||||||
|
visible: paramTypeIn(modelData[1], ['ObjectType', 'Enum'])
|
||||||
|
currentIndex: baseModel.indexOf(selectObjMode ? objEditor.obj[modelData[0]].name : objEditor.obj[modelData[0]])
|
||||||
|
|
||||||
|
onActivated: function(newIndex) {
|
||||||
|
if(selectObjMode) {
|
||||||
|
// This is only done when what we're selecting are Objects.
|
||||||
|
// Setting object property.
|
||||||
|
var selectedObj = Objects.getObjectByName(baseModel[newIndex], modelData[1].objType)
|
||||||
|
if(newIndex != 0) {
|
||||||
|
// Make sure we don't set the object to null.
|
||||||
|
if(selectedObj == null) {
|
||||||
|
// Creating new object.
|
||||||
|
selectedObj = Objects.createNewRegisteredObject(modelData[1].objType)
|
||||||
|
history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, modelData[1].objType, selectedObj.export()))
|
||||||
|
baseModel = Objects.getObjectsName(modelData[1].objType).concat(
|
||||||
|
isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[modelData[1].objType].displayType())] :
|
||||||
|
[])
|
||||||
|
currentIndex = baseModel.indexOf(selectedObj.name)
|
||||||
|
}
|
||||||
|
selectedObj.requiredBy.push(Objects.currentObjects[objEditor.objType][objEditor.objIndex])
|
||||||
|
//Objects.currentObjects[objEditor.objType][objEditor.objIndex].requiredBy = objEditor.obj[modelData[0]].filter((obj) => objEditor.obj.name != obj.name)
|
||||||
|
}
|
||||||
|
objEditor.obj.requiredBy = objEditor.obj.requiredBy.filter((obj) => objEditor.obj.name != obj.name)
|
||||||
|
history.addToHistory(new HistoryLib.EditedProperty(
|
||||||
|
objEditor.obj.name, objEditor.objType, modelData[0],
|
||||||
|
objEditor.obj[modelData[0]], selectedObj
|
||||||
|
))
|
||||||
|
objEditor.obj[modelData[0]] = selectedObj
|
||||||
|
} else if(baseModel[newIndex] != objEditor.obj[modelData[0]]) {
|
||||||
|
// Ensuring new property is different to not add useless history entries.
|
||||||
|
history.addToHistory(new HistoryLib.EditedProperty(
|
||||||
|
objEditor.obj.name, objEditor.objType, modelData[0],
|
||||||
|
objEditor.obj[modelData[0]], baseModel[newIndex]
|
||||||
|
))
|
||||||
|
objEditor.obj[modelData[0]] = baseModel[newIndex]
|
||||||
|
}
|
||||||
|
// Refreshing
|
||||||
|
Objects.currentObjects[objEditor.objType][objEditor.objIndex].update()
|
||||||
|
objectListList.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting to edit lists or dictionaries (e.g sequences & repartition function values)
|
||||||
|
Setting.ListSetting {
|
||||||
|
id: customPropListDict
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? implicitHeight : 0
|
||||||
|
|
||||||
|
visible: paramTypeIn(modelData[1], ['List', 'Dict'])
|
||||||
|
label: parent.label
|
||||||
|
//icon: `settings/custom/${parent.icon}.svg`
|
||||||
|
dictionaryMode: paramTypeIn(modelData[1], ['Dict'])
|
||||||
|
keyType: dictionaryMode ? modelData[1].keyType : 'string'
|
||||||
|
valueType: visible ? modelData[1].valueType : 'string'
|
||||||
|
preKeyLabel: visible ? (dictionaryMode ? modelData[1].preKeyLabel : modelData[1].label).replace(/\{name\}/g, objEditor.obj.name) : ''
|
||||||
|
postKeyLabel: visible ? (dictionaryMode ? modelData[1].postKeyLabel : '').replace(/\{name\}/g, objEditor.obj.name) : ''
|
||||||
|
keyRegexp: dictionaryMode ? modelData[1].keyFormat : /^.+$/
|
||||||
|
valueRegexp: visible ? modelData[1].format : /^.+$/
|
||||||
|
forbidAdding: visible ? modelData[1].forbidAdding : false
|
||||||
|
|
||||||
|
onChanged: {
|
||||||
|
var exported = exportModel()
|
||||||
|
history.addToHistory(new HistoryLib.EditedProperty(
|
||||||
|
objEditor.obj.name, objEditor.objType, modelData[0],
|
||||||
|
objEditor.obj[modelData[0]], exported
|
||||||
|
))
|
||||||
|
//Objects.currentObjects[objEditor.objType][objEditor.objIndex][modelData[0]] = exported
|
||||||
|
objEditor.obj[modelData[0]] = exported
|
||||||
|
//Objects.currentObjects[objEditor.objType][objEditor.objIndex].update()
|
||||||
|
objEditor.obj.update()
|
||||||
|
objectListList.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if(visible) importModel(objEditor.obj[modelData[0]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\qmlmethod void EditorDialog::show()
|
||||||
|
Shows the editor after the object to be edited is set.
|
||||||
|
*/
|
||||||
|
function show() {
|
||||||
|
dlgCustomProperties.model = [] // Reset
|
||||||
|
let objProps = Objects.types[objEditor.objType].properties()
|
||||||
|
dlgCustomProperties.model = Object.keys(objProps).map(prop => [prop, objProps[prop]]) // Converted to 2-dimentional array.
|
||||||
|
objEditor.open()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,10 @@
|
||||||
* 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 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
import "../js/history/index.mjs" as HistoryLib
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,21 +34,6 @@ Column {
|
||||||
id: createRow
|
id: createRow
|
||||||
property var objectEditor
|
property var objectEditor
|
||||||
property var objectLists
|
property var objectLists
|
||||||
property var posPicker
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod int ObjectCreationGrid::openEditorDialog(var obj)
|
|
||||||
Opens the editor dialog for an object \c obj.
|
|
||||||
*/
|
|
||||||
function openEditorDialog(obj) {
|
|
||||||
// Open editor
|
|
||||||
objectEditor.obj = obj
|
|
||||||
objectEditor.objType = obj.type
|
|
||||||
objectEditor.objIndex = Modules.Objects.currentObjects[obj.type].indexOf(obj)
|
|
||||||
objectEditor.open()
|
|
||||||
// Disconnect potential link
|
|
||||||
posPicker.picked.disconnect(openEditorDialog)
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: createTitle
|
id: createTitle
|
||||||
|
@ -60,12 +46,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 +79,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,26 +89,13 @@ Column {
|
||||||
ToolTip.text: label.text
|
ToolTip.text: label.text
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
let newObj = Modules.Objects.createNewRegisteredObject(modelData)
|
var newObj = Objects.createNewRegisteredObject(modelData)
|
||||||
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
|
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
|
||||||
objectLists.update()
|
objectLists.update()
|
||||||
|
objectEditor.obj = Objects.currentObjects[modelData][Objects.currentObjects[modelData].length - 1]
|
||||||
let hasXProp = newObj.constructor.properties().hasOwnProperty('x')
|
objectEditor.objType = modelData
|
||||||
let hasYProp = newObj.constructor.properties().hasOwnProperty('y')
|
objectEditor.objIndex = Objects.currentObjects[modelData].length - 1
|
||||||
if(hasXProp || hasYProp) {
|
objectEditor.show()
|
||||||
// Open picker
|
|
||||||
posPicker.objType = newObj.type
|
|
||||||
posPicker.objName = newObj.name
|
|
||||||
posPicker.pickX = hasXProp
|
|
||||||
posPicker.pickY = hasYProp
|
|
||||||
posPicker.propertyX = 'x'
|
|
||||||
posPicker.propertyY = 'y'
|
|
||||||
posPicker.visible = true
|
|
||||||
posPicker.picked.connect(openEditorDialog)
|
|
||||||
} else {
|
|
||||||
// Open editor
|
|
||||||
openEditorDialog(newObj)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,18 +16,19 @@
|
||||||
* 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 2.12
|
||||||
// import QtQuick.Dialogs 1.3 as D
|
import QtQuick.Dialogs 1.3 as D
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
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 "../js/objects.js" as Objects
|
||||||
|
import "../js/historylib.js" as HistoryLib
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype ObjectLists
|
\qmltype ObjectLists
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.ObjectLists
|
\inqmlmodule eu.ad5001.LogarithmPlotter
|
||||||
\brief Tab of the drawer that allows the user to manage the objects.
|
\brief Tab of the drawer that allows the user to manage the objects.
|
||||||
|
|
||||||
This item allows the user to synthetically see all objects, while giving the user the ability
|
This item allows the user to syntheticly see all objects, while giving the user the ability
|
||||||
to show, hide, delete, change the location and color, as well as opening the editor dialog
|
to show, hide, delete, change the location and color, as well as opening the editor dialog
|
||||||
for each object.
|
for each object.
|
||||||
|
|
||||||
|
@ -46,15 +47,15 @@ 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 + footer.height + 10
|
||||||
|
|
||||||
delegate: ListView {
|
delegate: ListView {
|
||||||
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
|
||||||
implicitHeight: contentItem.childrenRect.height
|
implicitHeight: contentItem.childrenRect.height
|
||||||
visible: model != undefined && model.length > 0
|
visible: model != undefined && model.length > 0
|
||||||
|
@ -69,39 +70,166 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: ObjectRow {
|
delegate: Item {
|
||||||
id: controlRow
|
id: controlRow
|
||||||
|
property var obj: Objects.currentObjects[objType][index]
|
||||||
|
property alias objVisible: objVisibilityCheckBox.checked
|
||||||
|
height: 40
|
||||||
width: objTypeList.width
|
width: objTypeList.width
|
||||||
obj: Modules.Objects.currentObjects[objType][index]
|
|
||||||
posPicker: positionPicker
|
|
||||||
|
|
||||||
onChanged: {
|
|
||||||
obj = Modules.Objects.currentObjects[objType][index]
|
|
||||||
objectListList.update()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: objTypeList.editingRows.push(controlRow)
|
Component.onCompleted: objTypeList.editingRows.push(controlRow)
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: objVisibilityCheckBox
|
||||||
|
checked: Objects.currentObjects[objType][index].visible
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 5
|
||||||
|
onClicked: {
|
||||||
|
history.addToHistory(new HistoryLib.EditedVisibility(
|
||||||
|
Objects.currentObjects[objType][index].name, objType, this.checked
|
||||||
|
))
|
||||||
|
Objects.currentObjects[objType][index].visible = this.checked
|
||||||
|
objectListList.changed()
|
||||||
|
controlRow.obj = Objects.currentObjects[objType][index]
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: checked ?
|
||||||
|
qsTr("Hide %1 %2").arg(Objects.types[objType].displayType()).arg(obj.name) :
|
||||||
|
qsTr("Show %1 %2").arg(Objects.types[objType].displayType()).arg(obj.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: objDescription
|
||||||
|
anchors.left: objVisibilityCheckBox.right
|
||||||
|
anchors.right: deleteButton.left
|
||||||
|
height: parent.height
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
text: obj.getReadableString()
|
||||||
|
font.pixelSize: 14
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
objEditor.obj = Objects.currentObjects[objType][index]
|
||||||
|
objEditor.objType = objType
|
||||||
|
objEditor.objIndex = index
|
||||||
|
//objEditor.editingRow = controlRow
|
||||||
|
objEditor.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: pointerButton
|
||||||
|
width: parent.height - 10
|
||||||
|
height: width
|
||||||
|
anchors.right: deleteButton.left
|
||||||
|
anchors.rightMargin: 5
|
||||||
|
anchors.topMargin: 5
|
||||||
|
|
||||||
|
Setting.Icon {
|
||||||
|
id: icon
|
||||||
|
width: 18
|
||||||
|
height: 18
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
color: sysPalette.windowText
|
||||||
|
source: '../icons/common/position.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
property bool hasXProp: Objects.types[objType].properties().hasOwnProperty('x')
|
||||||
|
property bool hasYProp: Objects.types[objType].properties().hasOwnProperty('y')
|
||||||
|
visible: hasXProp || hasYProp
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Set %1 %2 position").arg(Objects.types[objType].displayType()).arg(obj.name)
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
positionPicker.objType = objType
|
||||||
|
positionPicker.objName = obj.name
|
||||||
|
positionPicker.pickX = hasXProp
|
||||||
|
positionPicker.pickY = hasYProp
|
||||||
|
positionPicker.propertyX = 'x'
|
||||||
|
positionPicker.propertyY = 'y'
|
||||||
|
positionPicker.visible = true
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: deleteButton
|
||||||
|
width: parent.height - 10
|
||||||
|
height: width
|
||||||
|
anchors.right: colorPickRect.left
|
||||||
|
anchors.rightMargin: 5
|
||||||
|
anchors.topMargin: 5
|
||||||
|
icon.name: 'delete'
|
||||||
|
icon.source: '../icons/common/delete.svg'
|
||||||
|
icon.color: sysPalette.buttonText
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Delete %1 %2").arg(Objects.types[objType].displayType()).arg(obj.name)
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
history.addToHistory(new HistoryLib.DeleteObject(
|
||||||
|
obj.name, objType, obj.export()
|
||||||
|
))
|
||||||
|
Objects.currentObjects[objType][index].delete()
|
||||||
|
Objects.currentObjects[objType].splice(index, 1)
|
||||||
|
objectListList.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: colorPickRect
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 5
|
||||||
|
anchors.topMargin: 5
|
||||||
|
color: obj.color
|
||||||
|
width: parent.height - 10
|
||||||
|
height: width
|
||||||
|
radius: Math.min(width, height)
|
||||||
|
border.width: 2
|
||||||
|
border.color: sysPalette.windowText
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: pickColor.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
D.ColorDialog {
|
||||||
|
id: pickColor
|
||||||
|
color: obj.color
|
||||||
|
title: qsTr("Pick new color for %1 %2").arg(Objects.types[objType].displayType()).arg(obj.name)
|
||||||
|
onAccepted: {
|
||||||
|
history.addToHistory(new HistoryLib.ColorChanged(
|
||||||
|
obj.name, objType, obj.color, color.toString()
|
||||||
|
))
|
||||||
|
obj.color = color.toString()
|
||||||
|
controlRow.obj = Objects.currentObjects[objType][index]
|
||||||
|
objectListList.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,15 +239,12 @@ ScrollView {
|
||||||
width: objectsListView.width
|
width: objectsListView.width
|
||||||
objectEditor: objEditor
|
objectEditor: objEditor
|
||||||
objectLists: objectListList
|
objectLists: objectListList
|
||||||
posPicker: positionPicker
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object editor
|
// Object editor
|
||||||
Editor.Dialog {
|
EditorDialog {
|
||||||
id: objEditor
|
id: objEditor
|
||||||
|
|
||||||
posPicker: positionPicker
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -129,7 +254,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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,240 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Dialogs
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Window
|
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
|
||||||
import "../js/history/index.mjs" as HistoryLib
|
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype ObjectRow
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.ObjectLists
|
|
||||||
\brief Row describing an object.
|
|
||||||
|
|
||||||
This item allows the user to see, control, and modify a graph object.
|
|
||||||
It includes the visibility checkbox, the description label (optionally latex if enabled),
|
|
||||||
the reposition and delete buttons, and the color picker.
|
|
||||||
|
|
||||||
\sa LogarithmPlotter, ObjectCreationGrid, ObjectLists
|
|
||||||
*/
|
|
||||||
Item {
|
|
||||||
id: objectRow
|
|
||||||
|
|
||||||
signal changed()
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty var ObjectRow::obj
|
|
||||||
Object to show.
|
|
||||||
*/
|
|
||||||
property var obj
|
|
||||||
/*!
|
|
||||||
\qmlproperty var ObjectRow::posPicker
|
|
||||||
Reference to the global PositionPicker QML object.
|
|
||||||
*/
|
|
||||||
property var posPicker
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty bool ObjectRow::objVisible
|
|
||||||
True if the object should be visible, false otherwise.
|
|
||||||
*/
|
|
||||||
property alias objVisible: objVisibilityCheckBox.checked
|
|
||||||
/*!
|
|
||||||
\qmlproperty bool ObjectRow::minHeight
|
|
||||||
Minimum height of the row.
|
|
||||||
*/
|
|
||||||
readonly property int minHeight: 40
|
|
||||||
|
|
||||||
height: objDescription.height
|
|
||||||
width: obj.typeList.width
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: objVisibilityCheckBox
|
|
||||||
checked: obj.visible
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 5
|
|
||||||
onClicked: {
|
|
||||||
history.addToHistory(new HistoryLib.EditedVisibility(
|
|
||||||
obj.name, obj.type, this.checked
|
|
||||||
))
|
|
||||||
obj.visible = this.checked
|
|
||||||
changed()
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: checked ?
|
|
||||||
qsTr("Hide %1 %2").arg(obj.constructor.displayType()).arg(obj.name) :
|
|
||||||
qsTr("Show %1 %2").arg(obj.constructor.displayType()).arg(obj.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: objDescription
|
|
||||||
anchors.left: objVisibilityCheckBox.right
|
|
||||||
anchors.right: deleteButton.left
|
|
||||||
height: Modules.Latex.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight
|
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
text: Modules.Latex.enabled ? "" : obj.getReadableString()
|
|
||||||
font.pixelSize: 14
|
|
||||||
|
|
||||||
Image {
|
|
||||||
id: latexDescription
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.left: parent.left
|
|
||||||
visible: Modules.Latex.enabled
|
|
||||||
property double depth: Screen.devicePixelRatio
|
|
||||||
source: ""
|
|
||||||
width: 0/depth
|
|
||||||
height: 0/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 {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
objEditor.obj = Modules.Objects.currentObjects[obj.type][index]
|
|
||||||
objEditor.objType = obj.type
|
|
||||||
objEditor.objIndex = index
|
|
||||||
//objEditor.editingRow = objectRow
|
|
||||||
objEditor.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: pointerButton
|
|
||||||
width: parent.height - 10
|
|
||||||
height: width
|
|
||||||
anchors.right: deleteButton.left
|
|
||||||
anchors.rightMargin: 5
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Setting.Icon {
|
|
||||||
id: icon
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
color: sysPalette.windowText
|
|
||||||
source: '../icons/common/position.svg'
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool hasXProp: obj.constructor.properties().hasOwnProperty('x')
|
|
||||||
property bool hasYProp: obj.constructor.properties().hasOwnProperty('y')
|
|
||||||
visible: hasXProp || hasYProp
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Set %1 %2 position").arg(obj.constructor.displayType()).arg(obj.name)
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
posPicker.objType = obj.type
|
|
||||||
posPicker.objName = obj.name
|
|
||||||
posPicker.pickX = hasXProp
|
|
||||||
posPicker.pickY = hasYProp
|
|
||||||
posPicker.propertyX = 'x'
|
|
||||||
posPicker.propertyY = 'y'
|
|
||||||
posPicker.visible = true
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: deleteButton
|
|
||||||
width: parent.minHeight - 10
|
|
||||||
height: width
|
|
||||||
anchors.right: colorPickRect.left
|
|
||||||
anchors.rightMargin: 5
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
icon.name: 'delete'
|
|
||||||
icon.source: '../icons/common/delete.svg'
|
|
||||||
icon.color: sysPalette.buttonText
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: qsTr("Delete %1 %2").arg(obj.constructor.displayType()).arg(obj.name)
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
deleteRecursively(obj)
|
|
||||||
changed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: colorPickRect
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 5
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
color: obj.color
|
|
||||||
width: parent.minHeight - 10
|
|
||||||
height: width
|
|
||||||
radius: Math.min(width, height)
|
|
||||||
border.width: 2
|
|
||||||
border.color: sysPalette.windowText
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: pickColor.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorDialog {
|
|
||||||
id: pickColor
|
|
||||||
selectedColor: obj.color
|
|
||||||
title: qsTr("Pick new color for %1 %2").arg(obj.constructor.displayType()).arg(obj.name)
|
|
||||||
onAccepted: {
|
|
||||||
history.addToHistory(new HistoryLib.ColorChanged(
|
|
||||||
obj.name, obj.type, obj.color, selectedColor.toString()
|
|
||||||
))
|
|
||||||
obj.color = selectedColor.toString()
|
|
||||||
changed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod void ObjectRow::deleteRecursively(var object)
|
|
||||||
Deletes an object and it's dependencies recursively.
|
|
||||||
*/
|
|
||||||
function deleteRecursively(object) {
|
|
||||||
for(let toRemove of object.requiredBy)
|
|
||||||
deleteRecursively(toRemove)
|
|
||||||
if(Modules.Objects.currentObjectsByName[object.name] !== undefined) {
|
|
||||||
// Object still exists
|
|
||||||
// Temporary fix for objects require not being propertly updated.
|
|
||||||
object.requiredBy = []
|
|
||||||
history.addToHistory(new HistoryLib.DeleteObject(
|
|
||||||
object.name, object.type, object.export()
|
|
||||||
))
|
|
||||||
Modules.Objects.deleteObject(object.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,4 +2,5 @@ module eu.ad5001.LogarithmPlotter.ObjectLists
|
||||||
|
|
||||||
ObjectLists 1.0 ObjectLists.qml
|
ObjectLists 1.0 ObjectLists.qml
|
||||||
ObjectCreationGrid 1.0 ObjectCreationGrid.qml
|
ObjectCreationGrid 1.0 ObjectCreationGrid.qml
|
||||||
ObjectRow 1.0 ObjectRow.qml
|
EditorDialog 1.0 EditorDialog.qml
|
||||||
|
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
import "js/objects.js" as Objects
|
||||||
import "js/math/index.mjs" as MathLib
|
import "js/mathlib.js" as MathLib
|
||||||
import "js/history/index.mjs" as HistoryLib
|
import "js/historylib.js" as HistoryLib
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype PickLocationOverlay
|
\qmltype PickLocationOverlay
|
||||||
|
@ -36,15 +36,6 @@ import "js/history/index.mjs" as HistoryLib
|
||||||
Item {
|
Item {
|
||||||
id: pickerRoot
|
id: pickerRoot
|
||||||
visible: false
|
visible: false
|
||||||
clip: true
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlsignal PickLocationOverlay::picked(var obj)
|
|
||||||
|
|
||||||
Emitted when a location has been picked
|
|
||||||
The corresponding handler is \c onPicked.
|
|
||||||
*/
|
|
||||||
signal picked(var obj)
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty var PickLocationOverlay::canvas
|
\qmlproperty var PickLocationOverlay::canvas
|
||||||
|
@ -63,12 +54,12 @@ Item {
|
||||||
property string objName: 'A'
|
property string objName: 'A'
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty bool PickLocationOverlay::pickX
|
\qmlproperty bool PickLocationOverlay::pickX
|
||||||
true if the property in propertyX is pickable.
|
true if the user should be picking a position on the x axis.
|
||||||
*/
|
*/
|
||||||
property bool pickX: true
|
property bool pickX: true
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty bool PickLocationOverlay::pickY
|
\qmlproperty bool PickLocationOverlay::pickY
|
||||||
true if the property in propertyY is pickable.
|
true if the user should be picking a position on the y axis.
|
||||||
*/
|
*/
|
||||||
property bool pickY: true
|
property bool pickY: true
|
||||||
/*!
|
/*!
|
||||||
|
@ -86,16 +77,6 @@ Item {
|
||||||
Precision of the picked value (post-dot precision).
|
Precision of the picked value (post-dot precision).
|
||||||
*/
|
*/
|
||||||
property alias precision: precisionSlider.value
|
property alias precision: precisionSlider.value
|
||||||
/*!
|
|
||||||
\qmlproperty bool PickLocationOverlay::userPickX
|
|
||||||
true if the user can and wants to be picking a position on the x axis.
|
|
||||||
*/
|
|
||||||
readonly property bool userPickX: pickX && pickXCheckbox.checked
|
|
||||||
/*!
|
|
||||||
\qmlproperty bool PickLocationOverlay::userPickY
|
|
||||||
true if the user can and wants to be picking a position on the y axis.
|
|
||||||
*/
|
|
||||||
readonly property bool userPickY: pickY && pickYCheckbox.checked
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: sysPalette.window
|
color: sysPalette.window
|
||||||
|
@ -109,149 +90,66 @@ Item {
|
||||||
hoverEnabled: parent.visible
|
hoverEnabled: parent.visible
|
||||||
cursorShape: Qt.CrossCursor
|
cursorShape: Qt.CrossCursor
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
onClicked: function(mouse) {
|
onClicked: {
|
||||||
if(mouse.button == Qt.LeftButton) { // Validate
|
if(mouse.button == Qt.LeftButton) { // Validate
|
||||||
let newValueX = !parent.userPickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX)
|
if(parent.pickX) {
|
||||||
let newValueY = !parent.userPickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY)
|
let newValue = picked.mouseX.toString()
|
||||||
let obj = Modules.Objects.currentObjectsByName[objName]
|
newValue = {
|
||||||
// Set values
|
'Expression': () => new MathLib.Expression(newValue),
|
||||||
if(parent.userPickX && parent.userPickY) {
|
'number': () => parseFloat(newValue)
|
||||||
history.addToHistory(new HistoryLib.EditedPosition(
|
}[Objects.types[objType].properties()[propertyX]]()
|
||||||
objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY
|
let obj = Objects.getObjectByName(objName, objType)
|
||||||
))
|
|
||||||
obj[propertyX] = newValueX
|
|
||||||
obj[propertyY] = newValueY
|
|
||||||
obj.update()
|
|
||||||
objectLists.update()
|
|
||||||
pickerRoot.picked(obj)
|
|
||||||
} else if(parent.userPickX) {
|
|
||||||
history.addToHistory(new HistoryLib.EditedProperty(
|
history.addToHistory(new HistoryLib.EditedProperty(
|
||||||
objName, objType, propertyX, obj[propertyX], newValueX
|
objName, objType, propertyX, obj[propertyX], newValue
|
||||||
))
|
))
|
||||||
obj[propertyX] = newValueX
|
obj[propertyX] = newValue
|
||||||
obj.update()
|
obj.update()
|
||||||
objectLists.update()
|
objectLists.update()
|
||||||
pickerRoot.picked(obj)
|
}
|
||||||
} else if(parent.userPickY) {
|
if(parent.pickY) {
|
||||||
|
let newValue = picked.mouseY.toString()
|
||||||
|
newValue = {
|
||||||
|
'Expression': () => new MathLib.Expression(newValue),
|
||||||
|
'number': () => parseFloat(newValue)
|
||||||
|
}[Objects.types[objType].properties()[propertyY]]()
|
||||||
|
let obj = Objects.getObjectByName(objName, objType)
|
||||||
history.addToHistory(new HistoryLib.EditedProperty(
|
history.addToHistory(new HistoryLib.EditedProperty(
|
||||||
objName, objType, propertyY, obj[propertyY], newValueY
|
objName, objType, propertyY, obj[propertyY], newValue
|
||||||
))
|
))
|
||||||
obj[propertyY] = newValueY
|
obj[propertyY] = newValue
|
||||||
obj.update()
|
obj.update()
|
||||||
objectLists.update()
|
objectLists.update()
|
||||||
pickerRoot.picked(obj)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pickerRoot.visible = false;
|
pickerRoot.visible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
height: precisionSlider.height
|
||||||
|
Text {
|
||||||
|
text: " "+ qsTr("Pointer precision:") + " "
|
||||||
|
color: 'black'
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider {
|
||||||
Rectangle {
|
id: precisionSlider
|
||||||
id: pickerSettings
|
from: 0
|
||||||
radius: 15
|
value: 2
|
||||||
color: sysPalette.window
|
to: 10
|
||||||
width: pickerSettingsColumn.width + 30;
|
stepSize: 1
|
||||||
height: pickerSettingsColumn.height + 20
|
ToolTip {
|
||||||
property bool folded: false;
|
parent: precisionSlider.handle
|
||||||
x: -15 - ((width-55) * folded);
|
visible: precisionSlider.pressed
|
||||||
y: 10
|
text: precisionSlider.value.toFixed(0)
|
||||||
z: 2
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: pickerSettingsColumn
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
top: parent.top
|
|
||||||
leftMargin: 20
|
|
||||||
topMargin: 10
|
|
||||||
}
|
}
|
||||||
spacing: 15
|
}
|
||||||
property int cellHeight: 15
|
|
||||||
|
|
||||||
Column {
|
CheckBox {
|
||||||
spacing: 5
|
id: snapToGridCheckbox
|
||||||
// width: 100
|
text: qsTr("Snap to grid")
|
||||||
|
checked: false
|
||||||
Text {
|
|
||||||
text: qsTr("Pointer precision:")
|
|
||||||
color: sysPalette.windowText
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
height: pickerSettingsColumn.cellHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: qsTr("Snap to grid:")
|
|
||||||
color: sysPalette.windowText
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
height: pickerSettingsColumn.cellHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: pickXCheckbox
|
|
||||||
height: pickerSettingsColumn.cellHeight
|
|
||||||
text: qsTr("Pick X")
|
|
||||||
checked: pickX
|
|
||||||
visible: pickX
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
Slider {
|
|
||||||
id: precisionSlider
|
|
||||||
from: 0
|
|
||||||
value: 2
|
|
||||||
to: 10
|
|
||||||
stepSize: 1
|
|
||||||
height: pickerSettingsColumn.cellHeight
|
|
||||||
|
|
||||||
ToolTip {
|
|
||||||
parent: precisionSlider.handle
|
|
||||||
visible: precisionSlider.pressed
|
|
||||||
text: precisionSlider.value.toFixed(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: snapToGridCheckbox
|
|
||||||
height: pickerSettingsColumn.cellHeight
|
|
||||||
// text: qsTr("Snap to grid")
|
|
||||||
checked: false
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: pickYCheckbox
|
|
||||||
height: pickerSettingsColumn.cellHeight
|
|
||||||
text: qsTr("Pick Y")
|
|
||||||
checked: pickY
|
|
||||||
visible: pickY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
width: 24
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
flat: true
|
|
||||||
|
|
||||||
onClicked: pickerSettings.folded = !pickerSettings.folded
|
|
||||||
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.delay: 200
|
|
||||||
ToolTip.text: pickerSettings.folded ? qsTr("Open picker settings") : qsTr("Hide picker settings")
|
|
||||||
|
|
||||||
Setting.Icon {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
width: 18
|
|
||||||
height: 18
|
|
||||||
color: sysPalette.windowText
|
|
||||||
source: `../icons/common/settings.svg`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,8 +160,8 @@ 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.pickX
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -273,60 +171,47 @@ 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.pickY
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: picked
|
id: picked
|
||||||
x: picker.mouseX - width - 5
|
x: picker.mouseX - width - 5
|
||||||
y: picker.mouseY - height - 5
|
y: picker.mouseY - height - 5
|
||||||
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(canvas.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
color: 'black'
|
||||||
text: {
|
text: {
|
||||||
if(parent.userPickX && parent.userPickY)
|
if(parent.pickX && parent.pickY)
|
||||||
return `(${mouseX}, ${mouseY})`
|
return `(${mouseX}, ${mouseY})`
|
||||||
else if(parent.userPickX)
|
if(parent.pickX)
|
||||||
return `X = ${mouseX}`
|
return `X = ${mouseX}`
|
||||||
else if(parent.userPickY)
|
if(parent.pickY)
|
||||||
return `Y = ${mouseY}`
|
return `Y = ${mouseY}`
|
||||||
else
|
|
||||||
return qsTr('(no pick selected)')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod void History::parseValue(string value, string objType, string propertyName)
|
|
||||||
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) {
|
|
||||||
if(Modules.Objects.types[objType].properties()[propertyName] == 'number')
|
|
||||||
return parseFloat(value)
|
|
||||||
else
|
|
||||||
return new MathLib.Expression(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,8 +16,9 @@
|
||||||
* 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 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick.Dialogs 1.3 as D
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype About
|
\qmltype About
|
||||||
|
@ -26,112 +27,98 @@ import QtQuick.Controls
|
||||||
|
|
||||||
\sa LogarithmPlotter
|
\sa LogarithmPlotter
|
||||||
*/
|
*/
|
||||||
BaseDialog {
|
D.Dialog {
|
||||||
id: about
|
id: about
|
||||||
title: qsTr("About LogarithmPlotter")
|
title: qsTr("About LogarithmPlotter")
|
||||||
width: 400
|
width: 400
|
||||||
minimumHeight: 600
|
height: 600
|
||||||
|
|
||||||
Item {
|
Image {
|
||||||
anchors {
|
id: logo
|
||||||
top: parent.top;
|
source: "../icons/logarithmplotter.svg"
|
||||||
left: parent.left;
|
sourceSize.width: 64
|
||||||
bottom: parent.bottom;
|
sourceSize.height: 64
|
||||||
right: parent.right;
|
width: 64
|
||||||
topMargin: margin;
|
height: 64
|
||||||
leftMargin: margin;
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
bottomMargin: margin;
|
anchors.rightMargin: width/2
|
||||||
rightMargin: margin;
|
anchors.top: parent.top
|
||||||
}
|
anchors.topMargin: 10
|
||||||
|
}
|
||||||
|
|
||||||
Image {
|
Label {
|
||||||
id: logo
|
id: appName
|
||||||
source: "../icons/logarithmplotter.svg"
|
anchors.top: logo.bottom
|
||||||
sourceSize.width: 64
|
anchors.left: parent.left
|
||||||
sourceSize.height: 64
|
anchors.topMargin: 10
|
||||||
width: 64
|
horizontalAlignment: Text.AlignHCenter
|
||||||
height: 64
|
width: parent.width
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
wrapMode: Text.WordWrap
|
||||||
anchors.rightMargin: width/2
|
font.pixelSize: 25
|
||||||
anchors.top: parent.top
|
text: qsTr("LogarithmPlotter v%1").arg(Helper.getVersion())
|
||||||
anchors.topMargin: 10
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: appName
|
id: description
|
||||||
anchors.top: logo.bottom
|
anchors.top: appName.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.topMargin: 10
|
anchors.topMargin: 10
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font.pixelSize: 25
|
font.pixelSize: 18
|
||||||
text: qsTr("LogarithmPlotter v%1").arg(Helper.getVersion())
|
text: qsTr("2D plotter software to make BODE plots, sequences and repartition functions.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: description
|
id: debugInfos
|
||||||
anchors.top: appName.bottom
|
anchors.top: description.bottom
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.topMargin: 10
|
anchors.topMargin: 10
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font.pixelSize: 18
|
font.pixelSize: 14
|
||||||
text: qsTr("2D plotter software to make BODE plots, sequences and repartition functions.")
|
text: Helper.getDebugInfos()
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: debugInfos
|
id: copyrightInfos
|
||||||
anchors.top: description.bottom
|
anchors.top: debugInfos.bottom
|
||||||
anchors.left: parent.left
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.topMargin: 10
|
anchors.topMargin: 10
|
||||||
horizontalAlignment: Text.AlignHCenter
|
width: Math.min(410, parent.width)
|
||||||
width: parent.width
|
wrapMode: Text.WordWrap
|
||||||
wrapMode: Text.WordWrap
|
textFormat: Text.RichText
|
||||||
font.pixelSize: 14
|
font.pixelSize: 13
|
||||||
text: Helper.getDebugInfos()
|
text: "Copyright © 2022 Ad5001 <mail@ad5001.eu><br>
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: copyrightInfos
|
|
||||||
anchors.top: debugInfos.bottom
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.topMargin: 10
|
|
||||||
width: Math.min(410, parent.width)
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
textFormat: Text.RichText
|
|
||||||
font.pixelSize: 13
|
|
||||||
text: "Copyright © 2021-2024 Ad5001 <mail@ad5001.eu><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>
|
||||||
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.<br>
|
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.<br>
|
||||||
<br>
|
<br>
|
||||||
You should have received a copy of the GNU General Public License along with this program. If not, see <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>."
|
You should have received a copy of the GNU General Public License along with this program. If not, see <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>."
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.top: copyrightInfos.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.topMargin: 10
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: openIssueButton
|
||||||
|
text: qsTr('Report a bug')
|
||||||
|
icon.name: 'tools-report-bug'
|
||||||
|
onClicked: Qt.openUrlExternally('https://git.ad5001.eu/Ad5001/LogarithmPlotter')
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Button {
|
||||||
id: buttonsRow
|
id: officialWebsiteButton
|
||||||
anchors.top: copyrightInfos.bottom
|
text: qsTr('Official website')
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
icon.name: 'web-browser'
|
||||||
anchors.topMargin: 10
|
onClicked: Qt.openUrlExternally('https://apps.ad5001.eu/logarithmplotter/')
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: openIssueButton
|
|
||||||
text: qsTr('Report a bug')
|
|
||||||
icon.name: 'tools-report-bug'
|
|
||||||
onClicked: Qt.openUrlExternally('https://git.ad5001.eu/Ad5001/LogarithmPlotter')
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: officialWebsiteButton
|
|
||||||
text: qsTr('Official website')
|
|
||||||
icon.name: 'web-browser'
|
|
||||||
onClicked: Qt.openUrlExternally('https://apps.ad5001.eu/logarithmplotter/')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,7 +16,7 @@
|
||||||
* 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 2.12
|
||||||
/*!
|
/*!
|
||||||
\qmltype Alert
|
\qmltype Alert
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
|
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype BaseDialog
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
|
|
||||||
\brief Base dialog window in replacement of Dialog Popup from Qt 5.
|
|
||||||
|
|
||||||
\sa LogarithmPlotter
|
|
||||||
*/
|
|
||||||
|
|
||||||
Window {
|
|
||||||
id: base
|
|
||||||
color: sysPalette.window
|
|
||||||
visible: false;
|
|
||||||
flags: Qt.Dialog | Qt.Popup | Qt.MSWindowsFixedSizeDialogHint
|
|
||||||
modality: Qt.WindowModal
|
|
||||||
minimumWidth: width
|
|
||||||
maximumWidth: width
|
|
||||||
height: minimumHeight
|
|
||||||
property int margin: 10
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: closeButton
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottomMargin: margin
|
|
||||||
anchors.rightMargin: margin
|
|
||||||
text: qsTr('Close')
|
|
||||||
onClicked: close()
|
|
||||||
}
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Esc"
|
|
||||||
onActivated: base.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
function open() {
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,8 +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 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype Changelog
|
\qmltype Changelog
|
||||||
|
@ -32,7 +32,7 @@ Popup {
|
||||||
id: changelogPopup
|
id: changelogPopup
|
||||||
x: (parent.width-width)/2
|
x: (parent.width-width)/2
|
||||||
y: Math.max(20, (parent.height-height)/2)
|
y: Math.max(20, (parent.height-height)/2)
|
||||||
width: 800
|
width: changelog.width+40
|
||||||
height: Math.min(parent.height-40, 500)
|
height: Math.min(parent.height-40, 500)
|
||||||
modal: true
|
modal: true
|
||||||
focus: true
|
focus: true
|
||||||
|
@ -44,62 +44,42 @@ Popup {
|
||||||
*/
|
*/
|
||||||
property bool changelogNeedsFetching: true
|
property bool changelogNeedsFetching: true
|
||||||
|
|
||||||
onAboutToShow: if(changelogNeedsFetching) {
|
onAboutToShow: if(changelogNeedsFetching) Helper.fetchChangelog()
|
||||||
Helper.fetchChangelog()
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Helper
|
target: Helper
|
||||||
function onChangelogFetched(chl) {
|
function onChangelogFetched(chl) {
|
||||||
changelogNeedsFetching = false;
|
changelogNeedsFetching = false;
|
||||||
changelog.text = chl
|
changelog.text = chl
|
||||||
changelogView.contentItem.implicitHeight = changelog.height
|
|
||||||
// console.log(changelog.height, changelogView.contentItem.implicitHeight)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
id: changelogView
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 10
|
anchors.topMargin: 10
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 10
|
anchors.leftMargin: 10
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 10
|
|
||||||
anchors.bottom: doneBtn.top
|
anchors.bottom: doneBtn.top
|
||||||
anchors.bottomMargin: 10
|
anchors.bottomMargin: 10
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: changelog
|
id: changelog
|
||||||
color: sysPalette.windowText
|
color: sysPalette.windowText
|
||||||
width: 760
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
textFormat: TextEdit.MarkdownText
|
textFormat: TextEdit.MarkdownText
|
||||||
|
|
||||||
text: qsTr("Fetching changelog...")
|
text: qsTr("Fetching changelog...")
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
}
|
||||||
id: bottomSeparator
|
|
||||||
opacity: 0.3
|
|
||||||
color: sysPalette.windowText
|
|
||||||
width: parent.width * 2 / 3
|
|
||||||
height: 1
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
anchors.bottom: doneBtn.top
|
|
||||||
anchors.bottomMargin: 7
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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: 10
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
onClicked: changelogPopup.close()
|
onClicked: changelogPopup.close()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,7 +16,7 @@
|
||||||
* 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 Qt.labs.platform
|
import QtQuick.Dialogs 1.3 as D
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype FileDialog
|
\qmltype FileDialog
|
||||||
|
@ -25,7 +25,7 @@ import Qt.labs.platform
|
||||||
|
|
||||||
\sa LogarithmPlotter, Settings
|
\sa LogarithmPlotter, Settings
|
||||||
*/
|
*/
|
||||||
FileDialog {
|
D.FileDialog {
|
||||||
id: fileDialog
|
id: fileDialog
|
||||||
|
|
||||||
property bool exportMode: false
|
property bool exportMode: false
|
||||||
|
@ -33,6 +33,6 @@ FileDialog {
|
||||||
title: exportMode ? qsTr("Export Logarithm Plot file") : qsTr("Import Logarithm Plot file")
|
title: exportMode ? qsTr("Export Logarithm Plot file") : qsTr("Import Logarithm Plot file")
|
||||||
nameFilters: ["Logarithm Plot File (*.lpf)", "All files (*)"]
|
nameFilters: ["Logarithm Plot File (*.lpf)", "All files (*)"]
|
||||||
|
|
||||||
defaultSuffix: 'lpf'
|
folder: shortcuts.documents
|
||||||
fileMode: exportMode ? FileDialog.SaveFile : FileDialog.OpenFile
|
selectExisting: !exportMode
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,9 @@
|
||||||
* 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 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
import "../js/math/latex.js" as Latex
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype GreetScreen
|
\qmltype GreetScreen
|
||||||
|
@ -33,122 +33,133 @@ Popup {
|
||||||
id: greetingPopup
|
id: greetingPopup
|
||||||
x: (parent.width-width)/2
|
x: (parent.width-width)/2
|
||||||
y: Math.max(20, (parent.height-height)/2)
|
y: Math.max(20, (parent.height-height)/2)
|
||||||
width: greetingLayout.width+20
|
width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20
|
||||||
height: Math.min(parent.height-40, 700)
|
height: Math.min(parent.height-40, 500)
|
||||||
modal: true
|
modal: true
|
||||||
focus: true
|
focus: true
|
||||||
clip: true
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
|
||||||
Column {
|
Item {
|
||||||
id: greetingLayout
|
id: welcome
|
||||||
width: 600
|
height: logo.height
|
||||||
spacing: 10
|
width: logo.width + 10 + welcomeText.width
|
||||||
clip: true
|
anchors.top: parent.top
|
||||||
topPadding: 35
|
anchors.topMargin: (parent.width-width)/2
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
Row {
|
Image {
|
||||||
id: welcome
|
id: logo
|
||||||
height: logo.height
|
source: "../icons/logarithmplotter.svg"
|
||||||
spacing: 10
|
sourceSize.width: 48
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
sourceSize.height: 48
|
||||||
|
width: 48
|
||||||
Image {
|
height: 48
|
||||||
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 {
|
Label {
|
||||||
id: versionText
|
id: welcomeText
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: logo.right
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
//width: parent.width
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
width: implicitWidth
|
font.pixelSize: 32
|
||||||
font.pixelSize: 18
|
text: qsTr("Welcome to LogarithmPlotter")
|
||||||
font.italic: true
|
|
||||||
text: qsTr("Version %1").arg(Helper.getVersion())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Grid {
|
Label {
|
||||||
|
id: versionText
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: greetingLayout.bottom
|
anchors.top: welcome.bottom
|
||||||
anchors.topMargin: 50
|
anchors.topMargin: 10
|
||||||
columns: 2
|
//width: parent.width
|
||||||
spacing: 10
|
wrapMode: Text.WordWrap
|
||||||
|
width: implicitWidth
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.italic: true
|
||||||
|
text: qsTr("Version %1").arg(Helper.getVersion())
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Label {
|
||||||
model: [{
|
id: helpText
|
||||||
name: qsTr("Changelog"),
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
icon: 'common/new.svg',
|
anchors.top: versionText.bottom
|
||||||
onClicked: () => changelog.open()
|
anchors.topMargin: 40
|
||||||
},{
|
//width: parent.width
|
||||||
name: qsTr("Preferences"),
|
wrapMode: Text.WordWrap
|
||||||
icon: 'common/settings.svg',
|
font.pixelSize: 14
|
||||||
onClicked: () => preferences.open()
|
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.")
|
||||||
name: qsTr("User manual"),
|
}
|
||||||
icon: 'common/manual.svg',
|
|
||||||
onClicked: () => Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar")
|
|
||||||
},{
|
|
||||||
name: qsTr("Close"),
|
|
||||||
icon: 'common/close.svg',
|
|
||||||
onClicked: () => greetingPopup.close()
|
|
||||||
}]
|
|
||||||
|
|
||||||
Button {
|
CheckBox {
|
||||||
id: createBtn
|
id: checkForUpdatesSetting
|
||||||
width: 96
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
height: 96
|
anchors.top: helpText.bottom
|
||||||
onClicked: modelData.onClicked()
|
anchors.topMargin: 10
|
||||||
|
checked: Helper.getSettingBool("check_for_updates")
|
||||||
Setting.Icon {
|
text: qsTr('Check for updates on startup (requires online connectivity)')
|
||||||
id: icon
|
onClicked: {
|
||||||
width: 24
|
Helper.setSettingBool("check_for_updates", checked)
|
||||||
height: 24
|
//checkForUpdatesMenuSetting.checked = checked
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: (parent.width-width)/2
|
|
||||||
top: parent.top
|
|
||||||
topMargin: (label.y-height)/2
|
|
||||||
}
|
|
||||||
color: sysPalette.windowText
|
|
||||||
source: '../icons/' + modelData.icon
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: label
|
|
||||||
anchors {
|
|
||||||
bottom: parent.bottom
|
|
||||||
bottomMargin: 5
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: 4
|
|
||||||
right: parent.right
|
|
||||||
rightMargin: 4
|
|
||||||
}
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
font.pixelSize: 14
|
|
||||||
text: modelData.name
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
clip: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()+1) {
|
CheckBox {
|
||||||
|
id: resetRedoStackSetting
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: checkForUpdatesSetting.bottom
|
||||||
|
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)
|
||||||
|
//resetRedoStackMenuSetting.checked = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: enableLatexSetting
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: resetRedoStackSetting.bottom
|
||||||
|
checked: Helper.getSettingBool("enable_latex")
|
||||||
|
text: qsTr('Enable LaTeX rendering')
|
||||||
|
onClicked: {
|
||||||
|
Helper.setSettingBool("enable_latex", checked)
|
||||||
|
Latex.enabled = checked
|
||||||
|
drawCanvas.requestPaint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: 10
|
||||||
|
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()
|
greetingPopup.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype InsertCharacter
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Setting
|
|
||||||
\brief Popup to insert special character.
|
|
||||||
|
|
||||||
\sa TextSetting, ExpressionEditor
|
|
||||||
*/
|
|
||||||
Popup {
|
|
||||||
id: insertPopup
|
|
||||||
|
|
||||||
signal selected(string character)
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty string InsertCharacter::category
|
|
||||||
Type of special character to insert.
|
|
||||||
Possible values:
|
|
||||||
- expression
|
|
||||||
- domain
|
|
||||||
- name
|
|
||||||
- all
|
|
||||||
*/
|
|
||||||
property string category: 'all'
|
|
||||||
|
|
||||||
width: 280
|
|
||||||
height: Math.ceil(insertGrid.insertChars.length/insertGrid.columns)*(width/insertGrid.columns)+5
|
|
||||||
modal: true
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
|
||||||
|
|
||||||
Grid {
|
|
||||||
id: insertGrid
|
|
||||||
width: parent.width
|
|
||||||
columns: 7
|
|
||||||
|
|
||||||
property var insertCharsExpression: [
|
|
||||||
"∞","π","¹","²","³","⁴","⁵",
|
|
||||||
"⁶","⁷","⁸","⁹","⁰"
|
|
||||||
]
|
|
||||||
|
|
||||||
property var insertCharsDomain: [
|
|
||||||
"∅","∪","∩","∖","ℝ","ℕ","ℤ",
|
|
||||||
"⁺","⁻",...insertCharsExpression
|
|
||||||
]
|
|
||||||
|
|
||||||
property var insertCharsName: [
|
|
||||||
"α","β","γ","δ","ε","ζ","η",
|
|
||||||
"π","θ","κ","λ","μ","ξ","ρ",
|
|
||||||
"ς","σ","τ","φ","χ","ψ","ω",
|
|
||||||
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
|
|
||||||
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
|
|
||||||
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
|
|
||||||
"ₜ","₁","₂","₃","₄","₅","₆",
|
|
||||||
"₇","₈","₉","₀"
|
|
||||||
]
|
|
||||||
|
|
||||||
property var insertCharsAll: [
|
|
||||||
...insertCharsName, ...insertCharsDomain
|
|
||||||
]
|
|
||||||
|
|
||||||
property var insertChars: {
|
|
||||||
return {
|
|
||||||
"expression": insertCharsExpression,
|
|
||||||
"domain": insertCharsDomain,
|
|
||||||
"name": insertCharsName,
|
|
||||||
"all": insertCharsAll
|
|
||||||
}[insertPopup.category]
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: parent.insertChars.length
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: insertBtn
|
|
||||||
width: insertGrid.width/insertGrid.columns
|
|
||||||
height: width
|
|
||||||
text: insertGrid.insertChars[modelData]
|
|
||||||
flat: text == " "
|
|
||||||
font.pixelSize: 18
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
selected(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,256 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
|
||||||
import "../js/preferences/common.mjs" as S
|
|
||||||
import "../js/utils.mjs" as Utils
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype Preferences
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
|
|
||||||
\brief Popup to change global application preferences.
|
|
||||||
|
|
||||||
\sa LogarithmPlotter, GreetScreen
|
|
||||||
*/
|
|
||||||
Popup {
|
|
||||||
id: preferencesPopup
|
|
||||||
x: (parent.width-width)/2
|
|
||||||
y: Math.max(20, (parent.height-height)/2)
|
|
||||||
width: settingPopupRow.width + 30
|
|
||||||
height: settingPopupRow.height + 20
|
|
||||||
modal: true
|
|
||||||
focus: true
|
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
||||||
|
|
||||||
// Components for the preferences
|
|
||||||
Component {
|
|
||||||
id: boolSettingComponent
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
height: 20
|
|
||||||
text: setting.name
|
|
||||||
checked: setting.value()
|
|
||||||
onClicked: setting.set(this.checked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: enumIntSettingComponent
|
|
||||||
|
|
||||||
// Setting when selecting data from an enum, or an object of a certain type.
|
|
||||||
Setting.ComboBoxSetting {
|
|
||||||
height: 30
|
|
||||||
label: setting.name
|
|
||||||
icon: `settings/${setting.icon}.svg`
|
|
||||||
currentIndex: setting.value()
|
|
||||||
model: setting.values
|
|
||||||
onActivated: function(newIndex) { setting.set(newIndex) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: stringSettingComponent
|
|
||||||
|
|
||||||
Setting.ComboBoxSetting {
|
|
||||||
height: 30
|
|
||||||
label: setting.name
|
|
||||||
icon: `settings/${setting.icon}.svg`
|
|
||||||
editable: true
|
|
||||||
currentIndex: find(setting.value())
|
|
||||||
model: setting.defaultValues
|
|
||||||
onAccepted: function() {
|
|
||||||
editText = Utils.parseName(editText, false)
|
|
||||||
if(find(editText) === -1) model.append(editText)
|
|
||||||
setting.set(editText)
|
|
||||||
}
|
|
||||||
onActivated: function(selectedId) {
|
|
||||||
setting.set(model[selectedId])
|
|
||||||
}
|
|
||||||
Component.onCompleted: editText = setting.value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: numberSettingComponent
|
|
||||||
|
|
||||||
Setting.TextSetting {
|
|
||||||
height: 30
|
|
||||||
isDouble: true
|
|
||||||
label: setting.name
|
|
||||||
min: setting.min()
|
|
||||||
icon: `settings/${setting.icon}.svg`
|
|
||||||
value: setting.value()
|
|
||||||
onChanged: function(newValue) {
|
|
||||||
if(newValue < setting.max())
|
|
||||||
setting.set(newValue)
|
|
||||||
else {
|
|
||||||
value = setting.max()
|
|
||||||
setting.set(setting.max())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: expressionSettingComponent
|
|
||||||
|
|
||||||
Setting.ExpressionEditor {
|
|
||||||
height: 30
|
|
||||||
label: setting.name
|
|
||||||
icon: `settings/${setting.icon}.svg`
|
|
||||||
defValue: Utils.simplifyExpression(setting.value())
|
|
||||||
variables: setting.variables
|
|
||||||
allowGraphObjects: false
|
|
||||||
property string propertyName: setting.name
|
|
||||||
onChanged: function(newExpr) {
|
|
||||||
try {
|
|
||||||
setting.set(newExpr)
|
|
||||||
} catch(e) {
|
|
||||||
errorDialog.showDialog(propertyName, newExpr, e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: settingPopupRow
|
|
||||||
height: 300
|
|
||||||
width: categories.width + categorySeparator.width + settingView.width + 70
|
|
||||||
spacing: 15
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
bottom: parent.bottom
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
topMargin: 10
|
|
||||||
bottomMargin: 10
|
|
||||||
rightMargin: 15
|
|
||||||
leftMargin: 15
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: categories
|
|
||||||
width: 150
|
|
||||||
height: parent.height
|
|
||||||
spacing: 0
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: Object.keys(Modules.Preferences.categories)
|
|
||||||
|
|
||||||
Button {
|
|
||||||
// width: 150
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: qsTranslate('settingCategory', modelData)
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
settingView.modelName = modelData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: closeButton
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
text: qsTr('Close')
|
|
||||||
onClicked: preferencesPopup.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: categorySeparator
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
topMargin: 5
|
|
||||||
}
|
|
||||||
opacity: 0.3
|
|
||||||
color: sysPalette.windowText
|
|
||||||
height: parent.height - 10
|
|
||||||
width: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
id: settingView
|
|
||||||
clip: true
|
|
||||||
width: 500
|
|
||||||
spacing: 10
|
|
||||||
model: Modules.Preferences.categories[modelName]
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
ScrollBar.vertical: ScrollBar { }
|
|
||||||
property string modelName: 'general'
|
|
||||||
|
|
||||||
|
|
||||||
header: Text {
|
|
||||||
id: settingCategoryName
|
|
||||||
font.pixelSize: 32
|
|
||||||
height: 48
|
|
||||||
color: sysPalette.windowText
|
|
||||||
text: qsTranslate('settingCategory', settingView.modelName)
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: bottomSeparator
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 8
|
|
||||||
opacity: 0.3
|
|
||||||
color: sysPalette.windowText
|
|
||||||
width: settingView.width
|
|
||||||
height: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Component {
|
|
||||||
Loader {
|
|
||||||
width: settingView.width - 20
|
|
||||||
property var setting: Modules.Preferences.categories[settingView.modelName][index]
|
|
||||||
sourceComponent: {
|
|
||||||
if(setting.type === "bool")
|
|
||||||
return boolSettingComponent
|
|
||||||
else if(setting.type === "enum")
|
|
||||||
return enumIntSettingComponent
|
|
||||||
else if(setting.type === "number")
|
|
||||||
return numberSettingComponent
|
|
||||||
else if(setting.type === "expression")
|
|
||||||
return expressionSettingComponent
|
|
||||||
else if(setting.type === "string")
|
|
||||||
return stringSettingComponent
|
|
||||||
else
|
|
||||||
console.log('Unknown setting type!', setting.constructor.nameInConfig, setting.constructor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Component.onCompleted: open()
|
|
||||||
}
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,9 @@
|
||||||
* 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 2.12
|
||||||
import QtQuick.Dialogs
|
import QtQuick.Dialogs 1.3 as D
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype ThanksTo
|
\qmltype ThanksTo
|
||||||
|
@ -27,326 +27,289 @@ import QtQuick.Controls
|
||||||
|
|
||||||
\sa LogarithmPlotter
|
\sa LogarithmPlotter
|
||||||
*/
|
*/
|
||||||
BaseDialog {
|
D.Dialog {
|
||||||
id: thanks
|
id: about
|
||||||
title: qsTr("Thanks and Contributions - LogarithmPlotter")
|
title: qsTr("Thanks and Contributions - LogarithmPlotter")
|
||||||
width: 450
|
width: 400
|
||||||
minimumHeight: 600
|
height: 600
|
||||||
|
|
||||||
ScrollView {
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
anchors {
|
ListView {
|
||||||
top: parent.top;
|
id: librariesListView
|
||||||
left: parent.left;
|
anchors.left: parent.left
|
||||||
bottom: parent.bottom;
|
width: parent.width
|
||||||
right: parent.right;
|
//height: parent.height
|
||||||
topMargin: margin;
|
implicitHeight: contentItem.childrenRect.height
|
||||||
leftMargin: margin;
|
|
||||||
bottomMargin: margin+30;
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
model: ListModel {
|
||||||
|
Component.onCompleted: {
|
||||||
anchors {
|
append({
|
||||||
left: parent.left;
|
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')
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
width: thanks.width - 2*margin
|
header: Label {
|
||||||
spacing: 10
|
id: librariesUsedHeader
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font.pixelSize: 25
|
||||||
|
text: qsTr("Libraries included")
|
||||||
|
height: implicitHeight + 10
|
||||||
|
}
|
||||||
|
|
||||||
ListView {
|
delegate: Column {
|
||||||
id: librariesListView
|
id: libClmn
|
||||||
anchors.left: parent.left
|
width: librariesListView.width
|
||||||
width: parent.width
|
spacing: 10
|
||||||
//height: parent.height
|
|
||||||
implicitHeight: contentItem.childrenRect.height
|
|
||||||
interactive: false
|
|
||||||
|
|
||||||
model: ListModel {
|
Item {
|
||||||
Component.onCompleted: {
|
height: libraryHeader.height
|
||||||
append({
|
width: parent.width
|
||||||
libName: 'expr-eval',
|
|
||||||
license: 'MIT',
|
Label {
|
||||||
licenseLink: 'https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt',
|
id: libraryHeader
|
||||||
linkName: qsTr('Source code'),
|
anchors.left: parent.left
|
||||||
link: 'https://github.com/silentmatt/expr-eval',
|
wrapMode: Text.WordWrap
|
||||||
authors: [{
|
font.pixelSize: 18
|
||||||
authorLine: qsTr('Original library by Raphael Graf'),
|
text: libName
|
||||||
email: 'r@undefined.ch',
|
}
|
||||||
website: 'https://web.archive.org/web/20111023001618/http://www.undefined.ch/mparser/index.html',
|
|
||||||
websiteName: qsTr('Source')
|
Row {
|
||||||
}, {
|
anchors.right: parent.right
|
||||||
authorLine: qsTr('Ported to Javascript by Matthew Crumley'),
|
height: parent.height
|
||||||
email: 'email@matthewcrumley.com',
|
spacing: 10
|
||||||
website: 'https://silentmatt.com/',
|
|
||||||
websiteName: qsTr('Website')
|
Button {
|
||||||
}, {
|
height: parent.height
|
||||||
authorLine: qsTr('Ported to QMLJS by Ad5001'),
|
text: license
|
||||||
email: 'mail@ad5001.eu',
|
icon.name: 'license'
|
||||||
website: 'https://ad5001.eu/',
|
onClicked: Qt.openUrlExternally(licenseLink)
|
||||||
websiteName: qsTr('Website')
|
}
|
||||||
}]
|
|
||||||
})
|
Button {
|
||||||
|
height: parent.height
|
||||||
|
text: linkName
|
||||||
|
icon.name: 'web-browser'
|
||||||
|
onClicked: Qt.openUrlExternally(link)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header: Label {
|
ListView {
|
||||||
id: librariesUsedHeader
|
id: libAuthors
|
||||||
wrapMode: Text.WordWrap
|
anchors.left: parent.left
|
||||||
font.pixelSize: 25
|
anchors.leftMargin: 10
|
||||||
text: qsTr("Libraries included")
|
model: authors
|
||||||
height: implicitHeight + 10
|
width: parent.width - 10
|
||||||
}
|
implicitHeight: contentItem.childrenRect.height
|
||||||
|
|
||||||
delegate: Column {
|
delegate: Item {
|
||||||
id: libClmn
|
id: libAuthor
|
||||||
width: librariesListView.width
|
width: librariesListView.width - 10
|
||||||
spacing: 10
|
height: 50
|
||||||
|
|
||||||
Item {
|
|
||||||
height: libraryHeader.height
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: libraryHeader
|
id: libAuthorName
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
anchors.right: buttons.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font.pixelSize: 18
|
font.pixelSize: 14
|
||||||
text: libName
|
text: authorLine
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
id: buttons
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
height: parent.height
|
height: parent.height
|
||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
height: parent.height
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: license
|
text: websiteName
|
||||||
icon.name: 'license'
|
icon.name: 'web-browser'
|
||||||
onClicked: Qt.openUrlExternally(licenseLink)
|
height: parent.height - 10
|
||||||
|
onClicked: Qt.openUrlExternally(website)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
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
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
wrapMode: Text.WordWrap
|
text: qsTr('Email')
|
||||||
font.pixelSize: 14
|
icon.name: 'email'
|
||||||
text: authorLine
|
height: parent.height - 10
|
||||||
}
|
onClicked: Qt.openUrlExternally('mailto:' + email)
|
||||||
|
|
||||||
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 {
|
Rectangle {
|
||||||
id: libSeparator
|
id: libSeparator
|
||||||
opacity: 0.3
|
opacity: 0.3
|
||||||
color: sysPalette.windowText
|
color: sysPalette.windowText
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 1
|
height: 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: translationsListView
|
||||||
|
anchors.left: parent.left
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: contentItem.childrenRect.height
|
||||||
|
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')
|
||||||
|
}]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
header: Label {
|
||||||
id: translationsListView
|
id: translationsHeader
|
||||||
anchors.left: parent.left
|
wrapMode: Text.WordWrap
|
||||||
width: parent.width
|
font.pixelSize: 25
|
||||||
implicitHeight: contentItem.childrenRect.height
|
text: qsTr("Translations included")
|
||||||
interactive: false
|
height: implicitHeight + 10
|
||||||
spacing: 3
|
}
|
||||||
|
|
||||||
|
delegate: Column {
|
||||||
|
id: tranClmn
|
||||||
|
width: translationsListView.width
|
||||||
|
|
||||||
model: ListModel {
|
Item {
|
||||||
Component.onCompleted: {
|
width: parent.width
|
||||||
const authors = {
|
height: translationHeader.height + 10
|
||||||
Ad5001: {
|
|
||||||
authorLine: 'Ad5001',
|
Label {
|
||||||
email: 'mail@ad5001.eu',
|
id: translationHeader
|
||||||
website: 'https://ad5001.eu',
|
anchors.left: parent.left
|
||||||
websiteName: qsTr('Website')
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
},
|
wrapMode: Text.WordWrap
|
||||||
Ovari: {
|
font.pixelSize: 18
|
||||||
authorLine: 'Óvári',
|
text: tranName
|
||||||
website: 'https://github.com/ovari',
|
}
|
||||||
websiteName: qsTr('Github')
|
|
||||||
},
|
Row {
|
||||||
comradekingu: {
|
anchors.right: parent.right
|
||||||
authorLine: 'Allan Nordhøy',
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
website: 'https://github.com/comradekingu',
|
height: 30
|
||||||
websiteName: qsTr('Github')
|
spacing: 10
|
||||||
},
|
|
||||||
IngrownMink4: {
|
Button {
|
||||||
authorLine: 'IngrownMink4',
|
height: parent.height
|
||||||
website: 'https://github.com/IngrownMink4',
|
text: qsTr('Improve')
|
||||||
websiteName: qsTr('Github')
|
icon.name: 'web-browser'
|
||||||
},
|
onClicked: Qt.openUrlExternally(link)
|
||||||
gallegonovato: {
|
|
||||||
authorLine: 'gallegonovato',
|
|
||||||
website: '',
|
|
||||||
websiteName: ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
append({
|
|
||||||
tranName: '🇬🇧 ' + qsTr('English'),
|
|
||||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/',
|
|
||||||
authors: [authors.Ad5001]
|
|
||||||
})
|
|
||||||
append({
|
|
||||||
tranName: '🇫🇷 ' + qsTr('French'),
|
|
||||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/',
|
|
||||||
authors: [authors.Ad5001]
|
|
||||||
})
|
|
||||||
append({
|
|
||||||
tranName: '🇩🇪 ' + qsTr('German'),
|
|
||||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/',
|
|
||||||
authors: [authors.Ad5001]
|
|
||||||
})
|
|
||||||
append({
|
|
||||||
tranName: '🇭🇺 ' + qsTr('Hungarian'),
|
|
||||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/',
|
|
||||||
authors: [authors.Ovari]
|
|
||||||
})
|
|
||||||
append({
|
|
||||||
tranName: '🇳🇴 ' + qsTr('Norwegian'),
|
|
||||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/no/',
|
|
||||||
authors: [authors.comradekingu, authors.Ad5001]
|
|
||||||
})
|
|
||||||
append({
|
|
||||||
tranName: '🇪🇸 ' + qsTr('Spanish'),
|
|
||||||
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/',
|
|
||||||
authors: [authors.IngrownMink4, authors.gallegonovato]
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header: Label {
|
ListView {
|
||||||
id: translationsHeader
|
id: tranAuthors
|
||||||
wrapMode: Text.WordWrap
|
anchors.left: parent.left
|
||||||
font.pixelSize: 25
|
anchors.leftMargin: 10
|
||||||
text: qsTr("Translations included")
|
model: authors
|
||||||
height: implicitHeight + 10
|
width: parent.width - 10
|
||||||
}
|
implicitHeight: contentItem.childrenRect.height
|
||||||
|
|
||||||
delegate: Column {
|
delegate: Item {
|
||||||
id: tranClmn
|
id: tranAuthor
|
||||||
width: translationsListView.width
|
width: tranAuthors.width
|
||||||
|
height: 40
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: translationHeader.height + 10
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: translationHeader
|
id: tranAuthorName
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
anchors.right: buttons.left
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font.pixelSize: 18
|
font.pixelSize: 14
|
||||||
text: tranName
|
text: authorLine
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
id: buttons
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: 30
|
height: 30
|
||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
height: parent.height
|
text: websiteName
|
||||||
text: qsTr('Improve')
|
|
||||||
icon.name: 'web-browser'
|
icon.name: 'web-browser'
|
||||||
onClicked: Qt.openUrlExternally(link)
|
height: parent.height
|
||||||
}
|
onClicked: Qt.openUrlExternally(website)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.left: tranAuthorName.right
|
|
||||||
anchors.leftMargin: 10
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
height: 30
|
|
||||||
spacing: 10
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: websiteName
|
|
||||||
visible: websiteName !== ""
|
|
||||||
icon.name: 'web-browser'
|
|
||||||
height: parent.height
|
|
||||||
onClicked: Qt.openUrlExternally(website)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
module eu.ad5001.LogarithmPlotter.Popup
|
module eu.ad5001.LogarithmPlotter.Popup
|
||||||
|
|
||||||
Alert 1.0 Alert.qml
|
|
||||||
About 1.0 About.qml
|
About 1.0 About.qml
|
||||||
BaseDialog 1.0 BaseDialog.qml
|
Alert 1.0 Alert.qml
|
||||||
Changelog 1.0 Changelog.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
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype AutocompletionCategory
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Setting
|
|
||||||
\brief ListView representing a category of autocompletion.
|
|
||||||
|
|
||||||
\sa ExpressionEditor
|
|
||||||
*/
|
|
||||||
ListView {
|
|
||||||
id: listFiltered
|
|
||||||
/*!
|
|
||||||
\qmlproperty int AutocompletionCategory::itemStartIndex
|
|
||||||
Start index of the first element in this list compared to the global autocompletion index.
|
|
||||||
*/
|
|
||||||
property int itemStartIndex: 0
|
|
||||||
/*!
|
|
||||||
\qmlproperty int AutocompletionCategory::itemSelected
|
|
||||||
The global autocompletion index.
|
|
||||||
*/
|
|
||||||
property int itemSelected: 0
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty string AutocompletionCategory::category
|
|
||||||
Name of the category.
|
|
||||||
*/
|
|
||||||
property string category: ""
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty var AutocompletionCategory::categoryItems
|
|
||||||
List of items in this category. To be filtered by the autocomplete to filters.
|
|
||||||
*/
|
|
||||||
property var categoryItems: []
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty var AutocompletionCategory::autocompleteGenerator
|
|
||||||
Javascript function taking the name of the item to create an autocompletion item (dictionary with
|
|
||||||
a 'text', 'annotation' 'autocomplete', and 'cursorFinalOffset' keys.
|
|
||||||
*/
|
|
||||||
property var autocompleteGenerator: (item) => {return {'text': item, 'autocomplete': item, 'annotation': '', 'cursorFinalOffset': 0}}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty string AutocompletionCategory::baseText
|
|
||||||
Text to autocomplete.
|
|
||||||
*/
|
|
||||||
property string baseText: ""
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty bool AutocompletionCategory::visbilityCondition
|
|
||||||
Condition to be met for the category to be visible.
|
|
||||||
*/
|
|
||||||
property bool visbilityCondition: true
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
visible: model.length > 0
|
|
||||||
implicitHeight: contentItem.childrenRect.height
|
|
||||||
model: visbilityCondition ? categoryItems.filter((item) => item.includes(baseText)).map(autocompleteGenerator) : []
|
|
||||||
|
|
||||||
header: Column {
|
|
||||||
width: listFiltered.width
|
|
||||||
spacing: 2
|
|
||||||
topPadding: 5
|
|
||||||
bottomPadding: 5
|
|
||||||
|
|
||||||
Text {
|
|
||||||
leftPadding: 5
|
|
||||||
text: listFiltered.category
|
|
||||||
color: sysPalette.windowText
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
height: 1
|
|
||||||
color: 'gray'
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
property bool selected: index + listFiltered.itemStartIndex == listFiltered.itemSelected
|
|
||||||
|
|
||||||
width: listFiltered.width
|
|
||||||
height: Math.max(autocompleteText.height, annotationText.height)
|
|
||||||
color: selected ? sysPalette.highlight : 'transparent'
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: autocompleteText
|
|
||||||
topPadding: 2
|
|
||||||
bottomPadding: 2
|
|
||||||
leftPadding: 15
|
|
||||||
text: listFiltered.model[index].text
|
|
||||||
color: parent.selected ? sysPalette.highlightedText : sysPalette.windowText
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: annotationText
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
topPadding: 2
|
|
||||||
bottomPadding: 2
|
|
||||||
rightPadding: 15
|
|
||||||
font.pixelSize: autocompleteText.font.pixelSize - 2
|
|
||||||
text: listFiltered.model[index].annotation
|
|
||||||
color: parent.selected ? sysPaletteIn.highlightedText : sysPaletteIn.windowText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,8 +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 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype ComboBoxSetting
|
\qmltype ComboBoxSetting
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -1,636 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick
|
|
||||||
import Qt.labs.platform as Native
|
|
||||||
import eu.ad5001.LogarithmPlotter.Popup 1.0 as P
|
|
||||||
import "../js/math/index.mjs" as MathLib
|
|
||||||
import "../js/utils.mjs" as Utils
|
|
||||||
import "../js/parsing/parsing.mjs" as Parsing
|
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype ExpressionEditor
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter.Setting
|
|
||||||
\brief Setting to edit strings and numbers.
|
|
||||||
|
|
||||||
\sa EditorDialog, AutocompletionCategory
|
|
||||||
*/
|
|
||||||
Item {
|
|
||||||
id: control
|
|
||||||
height: 30
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlsignal ExpressionEditor::changed(var newValue)
|
|
||||||
|
|
||||||
Emitted when the value of the expression has been changed.
|
|
||||||
The corresponding handler is \c onChanged.
|
|
||||||
*/
|
|
||||||
signal changed(var newValue)
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty string ExpressionEditor::defValue
|
|
||||||
Default editable expression value of the editor.
|
|
||||||
*/
|
|
||||||
property string defValue
|
|
||||||
/*!
|
|
||||||
\qmlproperty string ExpressionEditor::value
|
|
||||||
Value of the editor.
|
|
||||||
*/
|
|
||||||
property alias value: editor.text
|
|
||||||
/*!
|
|
||||||
\qmlproperty string ExpressionEditor::self
|
|
||||||
Object or context of the expression to be edited.
|
|
||||||
Used to prevent circular dependency.
|
|
||||||
*/
|
|
||||||
property string self: ""
|
|
||||||
/*!
|
|
||||||
\qmlproperty var ExpressionEditor::variables
|
|
||||||
Accepted variables for the expression.
|
|
||||||
*/
|
|
||||||
property var variables: []
|
|
||||||
/*!
|
|
||||||
\qmlproperty string ExpressionEditor::placeholderText
|
|
||||||
Value of the editor.
|
|
||||||
*/
|
|
||||||
property alias placeholderText: editor.placeholderText
|
|
||||||
/*!
|
|
||||||
\qmlproperty string ExpressionEditor::label
|
|
||||||
Label of the editor.
|
|
||||||
*/
|
|
||||||
property string label
|
|
||||||
/*!
|
|
||||||
\qmlproperty string ExpressionEditor::icon
|
|
||||||
Icon path of the editor.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
Characters that when pressed, should be immediately followed up by their closing character.
|
|
||||||
TODO: Make it configurable.
|
|
||||||
*/
|
|
||||||
readonly property var openAndCloseMatches: {
|
|
||||||
"(": ")",
|
|
||||||
"[": "]",
|
|
||||||
"'": "'",
|
|
||||||
'"': '"'
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlproperty string ExpressionEditor::colorSchemes
|
|
||||||
Color schemes of the editor.
|
|
||||||
*/
|
|
||||||
readonly property var colorSchemes: [
|
|
||||||
{ // Breeze Light
|
|
||||||
'NORMAL': "#1F1C1B",
|
|
||||||
'VARIABLE': "#0057AE",
|
|
||||||
'CONSTANT': "#006E28",
|
|
||||||
'FUNCTION': "#644A9B",
|
|
||||||
'OPERATOR': "#CA60CA",
|
|
||||||
'STRING': "#BF0303",
|
|
||||||
'NUMBER': "#B08000"
|
|
||||||
},
|
|
||||||
{ // Breeze Dark
|
|
||||||
'NORMAL': "#CFCFC2",
|
|
||||||
'VARIABLE': "#2980B9",
|
|
||||||
'CONSTANT': "#27AE60",
|
|
||||||
'FUNCTION': "#8E44AD",
|
|
||||||
'OPERATOR': "#A44EA4",
|
|
||||||
'STRING': "#F44F4F",
|
|
||||||
'NUMBER': "#F67400"
|
|
||||||
},
|
|
||||||
{ // Solarized
|
|
||||||
'NORMAL': "#839496",
|
|
||||||
'VARIABLE': "#B58900",
|
|
||||||
'CONSTANT': "#859900",
|
|
||||||
'FUNCTION': "#268BD2",
|
|
||||||
'OPERATOR': "#859900",
|
|
||||||
'STRING': "#2AA198",
|
|
||||||
'NUMBER': "#2AA198"
|
|
||||||
},
|
|
||||||
{ // GitHub Light
|
|
||||||
'NORMAL': "#24292E",
|
|
||||||
'VARIABLE': "#D73A49",
|
|
||||||
'CONSTANT': "#6F42C1",
|
|
||||||
'FUNCTION': "#6F42C1",
|
|
||||||
'OPERATOR': "#24292E",
|
|
||||||
'STRING': "#032F62",
|
|
||||||
'NUMBER': "#005CC5"
|
|
||||||
},
|
|
||||||
{ // GitHub Dark
|
|
||||||
'NORMAL': "#E1E4E8",
|
|
||||||
'VARIABLE': "#F97583",
|
|
||||||
'CONSTANT': "#B392f0",
|
|
||||||
'FUNCTION': "#B392f0",
|
|
||||||
'OPERATOR': "#E1E4E8",
|
|
||||||
'STRING': "#9ECBFF",
|
|
||||||
'NUMBER': "#79B8FF"
|
|
||||||
},
|
|
||||||
{ // Nord
|
|
||||||
'NORMAL': "#D8DEE9",
|
|
||||||
'VARIABLE': "#81A1C1",
|
|
||||||
'CONSTANT': "#8FBCBB",
|
|
||||||
'FUNCTION': "#88C0D0",
|
|
||||||
'OPERATOR': "#81A1C1",
|
|
||||||
'STRING': "#A3BE8C",
|
|
||||||
'NUMBER': "#B48EAD"
|
|
||||||
},
|
|
||||||
{ // Monokai
|
|
||||||
'NORMAL': "#F8F8F2",
|
|
||||||
'VARIABLE': "#66D9EF",
|
|
||||||
'CONSTANT': "#F92672",
|
|
||||||
'FUNCTION': "#A6E22E",
|
|
||||||
'OPERATOR': "#F8F8F2",
|
|
||||||
'STRING': "#E6DB74",
|
|
||||||
'NUMBER': "#AE81FF"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Icon {
|
|
||||||
id: iconLabel
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: icon == "" ? 0 : 3
|
|
||||||
source: control.visible && icon != "" ? "../icons/" + control.icon : ""
|
|
||||||
width: height
|
|
||||||
height: icon == "" || !visible ? 0 : 24
|
|
||||||
color: sysPalette.windowText
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
id: labelItem
|
|
||||||
anchors.left: iconLabel.right
|
|
||||||
anchors.leftMargin: icon == "" ? 0 : 5
|
|
||||||
anchors.top: parent.top
|
|
||||||
height: parent.height
|
|
||||||
width: Math.max(85, implicitWidth)
|
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
//color: sysPalette.windowText
|
|
||||||
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
|
|
||||||
visible: control.label != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
Native.MessageDialog {
|
|
||||||
id: parsingErrorDialog
|
|
||||||
title: qsTranslate("expression", "LogarithmPlotter - Parsing error")
|
|
||||||
text: ""
|
|
||||||
function showDialog(propName, propValue, error) {
|
|
||||||
text = qsTranslate("expression", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3")
|
|
||||||
.arg(qsTranslate('prop', propName))
|
|
||||||
.arg(error).arg(propValue)
|
|
||||||
open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
|
||||||
id: editor
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.left: labelItem.right
|
|
||||||
anchors.leftMargin: 5
|
|
||||||
width: control.width - (labelItem.visible ? labelItem.width + 5 : 0) - iconLabel.width - 5
|
|
||||||
height: parent.height
|
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
|
|
||||||
text: control.defValue
|
|
||||||
color: syntaxHighlightingEnabled ? sysPalette.window : sysPalette.windowText
|
|
||||||
focus: true
|
|
||||||
selectByMouse: true
|
|
||||||
|
|
||||||
property bool autocompleteEnabled: Helper.getSettingBool("autocompletion.enabled")
|
|
||||||
property bool syntaxHighlightingEnabled: Helper.getSettingBool("expression_editor.colorize")
|
|
||||||
property bool autoClosing: Helper.getSettingBool("expression_editor.autoclose")
|
|
||||||
property var tokens: autocompleteEnabled || syntaxHighlightingEnabled ? parent.tokens(text) : []
|
|
||||||
|
|
||||||
Keys.priority: Keys.BeforeItem // Required for knowing which key the user presses.
|
|
||||||
|
|
||||||
onEditingFinished: {
|
|
||||||
if(insertButton.focus || insertPopup.focus) return
|
|
||||||
let value = text
|
|
||||||
if(value != "" && value.toString() != defValue) {
|
|
||||||
let expr = parse(value)
|
|
||||||
if(expr != null) {
|
|
||||||
control.changed(expr)
|
|
||||||
defValue = expr.toEditableString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onActiveFocusChanged: {
|
|
||||||
if(activeFocus && autocompleteEnabled)
|
|
||||||
autocompletePopup.open()
|
|
||||||
else
|
|
||||||
autocompletePopup.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
cursorDelegate: Rectangle {
|
|
||||||
visible: editor.cursorVisible
|
|
||||||
color: sysPalette.windowText
|
|
||||||
width: editor.cursorRectangle.width
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onUpPressed: function(event) {
|
|
||||||
if(autocompleteEnabled)
|
|
||||||
if(acPopupContent.itemSelected == 0)
|
|
||||||
acPopupContent.itemSelected = acPopupContent.itemCount-1
|
|
||||||
else
|
|
||||||
acPopupContent.itemSelected = acPopupContent.itemSelected-1
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onDownPressed: function(event) {
|
|
||||||
if(autocompleteEnabled)
|
|
||||||
if(acPopupContent.itemSelected == Math.min(acPopupContent.itemCount-1))
|
|
||||||
acPopupContent.itemSelected = 0
|
|
||||||
else
|
|
||||||
acPopupContent.itemSelected = acPopupContent.itemSelected+1
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: function(event) {
|
|
||||||
// Autocomplete popup events
|
|
||||||
if(autocompleteEnabled && (event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) {
|
|
||||||
acPopupContent.autocomplete()
|
|
||||||
event.accepted = true
|
|
||||||
} else
|
|
||||||
acPopupContent.itemSelected = 0
|
|
||||||
|
|
||||||
|
|
||||||
if(event.text in openAndCloseMatches && autoClosing) {
|
|
||||||
let start = selectionStart
|
|
||||||
insert(selectionStart, event.text)
|
|
||||||
insert(selectionEnd, openAndCloseMatches[event.text])
|
|
||||||
cursorPosition = start+1
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: colorizedEditor
|
|
||||||
anchors.fill: editor
|
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
|
||||||
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
|
|
||||||
textFormat: Text.StyledText
|
|
||||||
text: parent.syntaxHighlightingEnabled ? colorize(parent.tokens) : ""
|
|
||||||
color: sysPalette.windowText
|
|
||||||
visible: parent.syntaxHighlightingEnabled
|
|
||||||
//font.pixelSize: parent.font.pixelSize
|
|
||||||
//opacity: editor.activeFocus ? 0 : 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Popup {
|
|
||||||
id: autocompletePopup
|
|
||||||
x: 0
|
|
||||||
y: parent.height
|
|
||||||
closePolicy: Popup.NoAutoClose
|
|
||||||
|
|
||||||
width: editor.width
|
|
||||||
height: acPopupContent.height
|
|
||||||
padding: 0
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: acPopupContent
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
readonly property var identifierTokenTypes: [
|
|
||||||
Parsing.TokenType.VARIABLE,
|
|
||||||
Parsing.TokenType.FUNCTION,
|
|
||||||
Parsing.TokenType.CONSTANT
|
|
||||||
]
|
|
||||||
property var currentToken: generateTokenInformation(getTokenAt(editor.tokens, editor.cursorPosition))
|
|
||||||
property var previousToken: generateTokenInformation(getPreviousToken(currentToken.token))
|
|
||||||
property var previousToken2: generateTokenInformation(getPreviousToken(previousToken.token))
|
|
||||||
property var previousToken3: generateTokenInformation(getPreviousToken(previousToken2.token))
|
|
||||||
visible: currentToken.exists
|
|
||||||
|
|
||||||
// Focus handling.
|
|
||||||
readonly property var lists: [objectPropertiesList, variablesList, constantsList, functionsList, executableObjectsList, objectsList]
|
|
||||||
readonly property int itemCount: objectPropertiesList.model.length + variablesList.model.length + constantsList.model.length + functionsList.model.length + executableObjectsList.model.length + objectsList.model.length
|
|
||||||
property int itemSelected: 0
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod var ExpressionEditor::generateTokenInformation(var token)
|
|
||||||
Generates basic information about the given token (existence and type) used in autocompletion).
|
|
||||||
*/
|
|
||||||
function generateTokenInformation(token) {
|
|
||||||
let exists = token != null
|
|
||||||
return {
|
|
||||||
'token': token,
|
|
||||||
'exists': exists,
|
|
||||||
'value': exists ? token.value : null,
|
|
||||||
'type': exists ? token.type : null,
|
|
||||||
'startPosition': exists ? token.startPosition : 0,
|
|
||||||
'dot': exists ? (token.type == Parsing.TokenType.PUNCT && token.value == ".") : false,
|
|
||||||
'identifier': exists ? identifierTokenTypes.includes(token.type) : false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*!
|
|
||||||
\qmlmethod void ExpressionEditor::autocompleteInfoAt(int idx)
|
|
||||||
Returns the autocompletion text information at a given position.
|
|
||||||
The information contains key 'text' (description text), 'autocomplete' (text to insert)
|
|
||||||
and 'cursorFinalOffset' (amount to add to the cursor's position after the end of the autocomplete)
|
|
||||||
*/
|
|
||||||
function autocompleteInfoAt(idx) {
|
|
||||||
if(idx >= itemCount) return ""
|
|
||||||
let startIndex = 0
|
|
||||||
for(let list of lists) {
|
|
||||||
if(idx < startIndex + list.model.length)
|
|
||||||
return list.model[idx-startIndex]
|
|
||||||
startIndex += list.model.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod void ExpressionEditor::autocomplete()
|
|
||||||
Autocompletes with the current selected word.
|
|
||||||
*/
|
|
||||||
function autocomplete() {
|
|
||||||
let autotext = autocompleteInfoAt(itemSelected)
|
|
||||||
let startPos = currentToken.startPosition
|
|
||||||
console.log("Replacing", currentToken.value, "at", startPos, "with", autotext.autocomplete)
|
|
||||||
editor.remove(startPos, startPos+currentToken.value.length)
|
|
||||||
editor.insert(startPos, autotext.autocomplete)
|
|
||||||
editor.cursorPosition = startPos+autotext.autocomplete.length+autotext.cursorFinalOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod var ExpressionEditor::getPreviousToken(var token)
|
|
||||||
Returns the token before this one.
|
|
||||||
*/
|
|
||||||
function getPreviousToken(token) {
|
|
||||||
let newToken = getTokenAt(editor.tokens, token.startPosition)
|
|
||||||
if(newToken != null && newToken.type == Parsing.TokenType.WHITESPACE)
|
|
||||||
return getPreviousToken(newToken)
|
|
||||||
return newToken
|
|
||||||
}
|
|
||||||
|
|
||||||
AutocompletionCategory {
|
|
||||||
id: objectPropertiesList
|
|
||||||
|
|
||||||
category: qsTr("Object Properties")
|
|
||||||
visbilityCondition: control.allowGraphObjects && doesObjectExist
|
|
||||||
itemStartIndex: 0
|
|
||||||
itemSelected: parent.itemSelected
|
|
||||||
property bool isEnteringProperty: (
|
|
||||||
// Current token is dot.
|
|
||||||
(parent.currentToken.dot && parent.previousToken.identifier && !parent.previousToken2.dot) ||
|
|
||||||
// Current token is property identifier
|
|
||||||
(parent.currentToken.identifier && parent.previousToken.dot && parent.previousToken2.identifier && !parent.previousToken3.dot))
|
|
||||||
property string objectName: isEnteringProperty ?
|
|
||||||
(parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value)
|
|
||||||
: ""
|
|
||||||
property bool doesObjectExist: isEnteringProperty && (objectName in Modules.Objects.currentObjectsByName)
|
|
||||||
property var objectProperties: doesObjectExist ?
|
|
||||||
Modules.Objects.currentObjectsByName[objectName].constructor.properties() :
|
|
||||||
{}
|
|
||||||
categoryItems: Object.keys(objectProperties)
|
|
||||||
autocompleteGenerator: (item) => {
|
|
||||||
let propType = objectProperties[item]
|
|
||||||
return {
|
|
||||||
'text': item, 'annotation': propType == null ? '' : propType.toString(),
|
|
||||||
'autocomplete': parent.currentToken.dot ? `.${item} ` : `${item} `,
|
|
||||||
'cursorFinalOffset': 0
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
baseText: parent.visible && !parent.currentToken.dot ? parent.currentToken.value : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
AutocompletionCategory {
|
|
||||||
id: variablesList
|
|
||||||
|
|
||||||
category: qsTr("Variables")
|
|
||||||
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
|
|
||||||
itemStartIndex: objectPropertiesList.model.length
|
|
||||||
itemSelected: parent.itemSelected
|
|
||||||
categoryItems: control.variables
|
|
||||||
autocompleteGenerator: (item) => {return {
|
|
||||||
'text': item, 'annotation': '',
|
|
||||||
'autocomplete': item + " ", 'cursorFinalOffset': 0
|
|
||||||
}}
|
|
||||||
baseText: parent.visible ? parent.currentToken.value : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
AutocompletionCategory {
|
|
||||||
id: constantsList
|
|
||||||
|
|
||||||
category: qsTr("Constants")
|
|
||||||
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
|
|
||||||
itemStartIndex: variablesList.itemStartIndex + variablesList.model.length
|
|
||||||
itemSelected: parent.itemSelected
|
|
||||||
categoryItems: Parsing.CONSTANTS_LIST
|
|
||||||
autocompleteGenerator: (item) => {return {
|
|
||||||
'text': item, 'annotation': Parsing.CONSTANTS[item],
|
|
||||||
'autocomplete': item + " ", 'cursorFinalOffset': 0
|
|
||||||
}}
|
|
||||||
baseText: parent.visible ? parent.currentToken.value : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
AutocompletionCategory {
|
|
||||||
id: functionsList
|
|
||||||
|
|
||||||
category: qsTr("Functions")
|
|
||||||
visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot
|
|
||||||
itemStartIndex: constantsList.itemStartIndex + constantsList.model.length
|
|
||||||
itemSelected: parent.itemSelected
|
|
||||||
categoryItems: Parsing.FUNCTIONS_LIST
|
|
||||||
autocompleteGenerator: (item) => {return {
|
|
||||||
'text': item, 'annotation': Parsing.FUNCTIONS_USAGE[item].join(', '),
|
|
||||||
'autocomplete': item+'()', 'cursorFinalOffset': -1
|
|
||||||
}}
|
|
||||||
baseText: parent.visible ? parent.currentToken.value : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
AutocompletionCategory {
|
|
||||||
id: executableObjectsList
|
|
||||||
|
|
||||||
category: qsTr("Executable Objects")
|
|
||||||
visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot
|
|
||||||
itemStartIndex: functionsList.itemStartIndex + functionsList.model.length
|
|
||||||
itemSelected: parent.itemSelected
|
|
||||||
categoryItems: Modules.Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
|
|
||||||
autocompleteGenerator: (item) => {return {
|
|
||||||
'text': item, 'annotation': Modules.Objects.currentObjectsByName[item] == null ? '' : Modules.Objects.currentObjectsByName[item].constructor.displayType(),
|
|
||||||
'autocomplete': item+'()', 'cursorFinalOffset': -1
|
|
||||||
}}
|
|
||||||
baseText: parent.visible ? parent.currentToken.value : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
AutocompletionCategory {
|
|
||||||
id: objectsList
|
|
||||||
|
|
||||||
category: qsTr("Objects")
|
|
||||||
visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot
|
|
||||||
itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length
|
|
||||||
itemSelected: parent.itemSelected
|
|
||||||
categoryItems: Object.keys(Modules.Objects.currentObjectsByName).filter(obj => obj != self)
|
|
||||||
autocompleteGenerator: (item) => {return {
|
|
||||||
'text': item, 'annotation': `${Modules.Objects.currentObjectsByName[item].constructor.displayType()}`,
|
|
||||||
'autocomplete': item+'.', 'cursorFinalOffset': 0
|
|
||||||
}}
|
|
||||||
baseText: parent.visible ? parent.currentToken.value : ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: insertButton
|
|
||||||
text: "α"
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 5
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 20
|
|
||||||
height: width
|
|
||||||
onClicked: {
|
|
||||||
insertPopup.open()
|
|
||||||
insertPopup.focus = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
P.InsertCharacter {
|
|
||||||
id: insertPopup
|
|
||||||
|
|
||||||
x: Math.round((parent.width - width) / 2)
|
|
||||||
y: Math.round((parent.height - height) / 2)
|
|
||||||
|
|
||||||
category: "expression"
|
|
||||||
|
|
||||||
onSelected: function(c) {
|
|
||||||
editor.insert(editor.cursorPosition, c)
|
|
||||||
insertPopup.close()
|
|
||||||
focus = false
|
|
||||||
editor.focus = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod var ExpressionEditor::parse(string newExpression)
|
|
||||||
Parses the \c newExpression as an expression, checks for errors, shows them if any.
|
|
||||||
Returns the parsed expression if possible, null otherwise..
|
|
||||||
*/
|
|
||||||
function parse(newExpression) {
|
|
||||||
let expr = null
|
|
||||||
try {
|
|
||||||
expr = new MathLib.Expression(value.toString())
|
|
||||||
// Check if the expression is valid, throws error otherwise.
|
|
||||||
if(!expr.allRequirementsFullfilled()) {
|
|
||||||
let undefVars = expr.undefinedVariables()
|
|
||||||
if(undefVars.length > 1)
|
|
||||||
throw new Error(qsTranslate('error', 'No object found with names %1.').arg(undefVars.join(', ')))
|
|
||||||
else
|
|
||||||
throw new Error(qsTranslate('error', 'No object found with name %1.').arg(undefVars.join(', ')))
|
|
||||||
}
|
|
||||||
if(expr.requiredObjects().includes(control.self))
|
|
||||||
throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.'))
|
|
||||||
// Recursive dependencies
|
|
||||||
let dependentOnSelfObjects = expr.requiredObjects().filter(
|
|
||||||
(obj) => Modules.Objects.currentObjectsByName[obj].getDependenciesList()
|
|
||||||
.includes(Modules.Objects.currentObjectsByName[control.self])
|
|
||||||
)
|
|
||||||
if(dependentOnSelfObjects.length == 1)
|
|
||||||
throw new Error(qsTranslate('error', 'Circular dependency detected. Object %1 depends on %2.').arg(dependentOnSelfObjects[0].toString()).arg(control.self))
|
|
||||||
else if(dependentOnSelfObjects.length > 1)
|
|
||||||
throw new Error(qsTranslate('error', 'Circular dependency detected. Objects %1 depend on %2.').arg(dependentOnSelfObjects.map(obj => obj.toString()).join(', ')).arg(control.self))
|
|
||||||
//console.log(control.self, propertyName, expr.execute())
|
|
||||||
return expr
|
|
||||||
} catch(e) {
|
|
||||||
// Error in expression
|
|
||||||
parsingErrorDialog.showDialog(propertyName, newExpression, e.message)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod var ExpressionEditor::tokens(string expressionText)
|
|
||||||
Generates a list of tokens from the given.
|
|
||||||
*/
|
|
||||||
function tokens(text) {
|
|
||||||
let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false)
|
|
||||||
let tokenList = []
|
|
||||||
let token
|
|
||||||
while((token = tokenizer.next()) != null)
|
|
||||||
tokenList.push(token)
|
|
||||||
return tokenList
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod var ExpressionEditor::getTokenAt(var tokens, int position)
|
|
||||||
Gets the token at the given position within the text.
|
|
||||||
Returns null if out of bounds.
|
|
||||||
*/
|
|
||||||
function getTokenAt(tokenList, position) {
|
|
||||||
let currentPosition = 0
|
|
||||||
for(let token of tokenList)
|
|
||||||
if(position <= (currentPosition + token.value.length))
|
|
||||||
return token
|
|
||||||
else
|
|
||||||
currentPosition += token.value.length
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlmethod var ExpressionEditor::colorize(var tokenList)
|
|
||||||
Creates an HTML colorized string of the given tokens.
|
|
||||||
Returns the colorized and escaped expression if possible, null otherwise..
|
|
||||||
*/
|
|
||||||
function colorize(tokenList) {
|
|
||||||
let parsedText = ""
|
|
||||||
let scheme = colorSchemes[Helper.getSettingInt("expression_editor.color_scheme")]
|
|
||||||
for(let token of tokenList) {
|
|
||||||
switch(token.type) {
|
|
||||||
case Parsing.TokenType.VARIABLE:
|
|
||||||
parsedText += `<font color="${scheme.VARIABLE}">${token.value}</font>`
|
|
||||||
break;
|
|
||||||
case Parsing.TokenType.CONSTANT:
|
|
||||||
parsedText += `<font color="${scheme.CONSTANT}">${token.value}</font>`
|
|
||||||
break;
|
|
||||||
case Parsing.TokenType.FUNCTION:
|
|
||||||
parsedText += `<font color="${scheme.FUNCTION}">${Utils.escapeHTML(token.value)}</font>`
|
|
||||||
break;
|
|
||||||
case Parsing.TokenType.OPERATOR:
|
|
||||||
parsedText += `<font color="${scheme.OPERATOR}">${Utils.escapeHTML(token.value)}</font>`
|
|
||||||
break;
|
|
||||||
case Parsing.TokenType.NUMBER:
|
|
||||||
parsedText += `<font color="${scheme.NUMBER}">${Utils.escapeHTML(token.value)}</font>`
|
|
||||||
break;
|
|
||||||
case Parsing.TokenType.STRING:
|
|
||||||
parsedText += `<font color="${scheme.STRING}">${token.limitator}${Utils.escapeHTML(token.value)}${token.limitator}</font>`
|
|
||||||
break;
|
|
||||||
case Parsing.TokenType.WHITESPACE:
|
|
||||||
case Parsing.TokenType.PUNCT:
|
|
||||||
default:
|
|
||||||
parsedText += Utils.escapeHTML(token.value).replace(/ /g, ' ')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parsedText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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
|
||||||
|
@ -15,9 +15,8 @@
|
||||||
* 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/>.
|
||||||
*/
|
*/
|
||||||
import QtQuick
|
import QtQuick 2.7
|
||||||
import QtQuick.Window
|
import QtGraphicalEffects 1.0
|
||||||
import QtQuick.Controls.impl
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype Icon
|
\qmltype Icon
|
||||||
|
@ -41,16 +40,19 @@ 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
|
//smooth: true
|
||||||
property int sourceS: width*Screen.devicePixelRatio
|
visible: false
|
||||||
sourceSize.width: sourceS
|
sourceSize.height: sourceSize.width
|
||||||
sourceSize.height: sourceS
|
}
|
||||||
|
ColorOverlay {
|
||||||
|
anchors.fill: img
|
||||||
|
source: img
|
||||||
color: parent.color
|
color: parent.color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,12 +16,13 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Loading modules in order
|
|
||||||
import * as Objects from "./module/objects.mjs"
|
import QtQuick 2.12
|
||||||
import * as ExprParser from "./module/expreval.mjs"
|
import QtQuick.Controls 2.12
|
||||||
import * as ObjsAutoload from "./objs/autoload.mjs"
|
|
||||||
import * as Latex from "./module/latex.mjs"
|
Image {
|
||||||
import * as History from "./module/history.mjs"
|
id: expr
|
||||||
import * as CanvasAPI from "./module/canvas.mjs"
|
property string expression
|
||||||
import * as IOAPI from "./module/io.mjs"
|
|
||||||
import * as PreferencesAPI from "./module/preferences.mjs"
|
src: Latex.render(expression).split(',')[0]
|
||||||
|
}
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,9 @@
|
||||||
* 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 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls 2.12
|
||||||
import QtQml.Models
|
import QtQml.Models 2.12
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype ListSetting
|
\qmltype ListSetting
|
||||||
|
@ -140,8 +140,8 @@ Column {
|
||||||
visible: control.dictionaryMode
|
visible: control.dictionaryMode
|
||||||
height: parent.height
|
height: parent.height
|
||||||
width: visible ? 50 : 0
|
width: visible ? 50 : 0
|
||||||
validator: RegularExpressionValidator {
|
validator: RegExpValidator {
|
||||||
regularExpression: control.keyRegexp
|
regExp: control.keyRegexp
|
||||||
}
|
}
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
horizontalAlignment: TextInput.AlignHCenter
|
horizontalAlignment: TextInput.AlignHCenter
|
||||||
|
@ -180,8 +180,8 @@ Column {
|
||||||
id: valueInput
|
id: valueInput
|
||||||
height: parent.height
|
height: parent.height
|
||||||
width: parent.width - x - deleteButton.width - 5
|
width: parent.width - x - deleteButton.width - 5
|
||||||
validator: RegularExpressionValidator {
|
validator: RegExpValidator {
|
||||||
regularExpression: control.valueRegexp
|
regExp: control.valueRegexp
|
||||||
}
|
}
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
horizontalAlignment: TextInput.AlignHCenter
|
horizontalAlignment: TextInput.AlignHCenter
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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.Controls
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick
|
import QtQuick 2.12
|
||||||
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype TextSetting
|
\qmltype TextSetting
|
||||||
|
@ -49,12 +48,6 @@ Item {
|
||||||
If true, the input is being parsed an double before being emitting the \a changed signal.
|
If true, the input is being parsed an double before being emitting the \a changed signal.
|
||||||
*/
|
*/
|
||||||
property bool isDouble: false
|
property bool isDouble: false
|
||||||
/*!
|
|
||||||
\qmlproperty bool TextSetting::category
|
|
||||||
Type of special character to insert from the popup.
|
|
||||||
\sa InsertCharacter::category
|
|
||||||
*/
|
|
||||||
property alias category: insertPopup.category
|
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty double TextSetting::min
|
\qmlproperty double TextSetting::min
|
||||||
Minimum value for numbers that can be entered into the input.
|
Minimum value for numbers that can be entered into the input.
|
||||||
|
@ -100,15 +93,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
|
||||||
|
@ -119,23 +112,17 @@ Item {
|
||||||
verticalAlignment: TextInput.AlignVCenter
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
|
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
|
||||||
color: sysPalette.windowText
|
color: sysPalette.windowText
|
||||||
validator: RegularExpressionValidator {
|
validator: RegExpValidator {
|
||||||
regularExpression: control.isInt ? /-?[0-9]+/ : control.isDouble ? /-?[0-9]+(\.[0-9]+)?/ : /.+/
|
regExp: control.isInt ? /-?[0-9]+/ : control.isDouble ? /-?[0-9]+(\.[0-9]+)?/ : /.+/
|
||||||
}
|
}
|
||||||
focus: true
|
focus: true
|
||||||
text: control.defValue
|
text: control.defValue
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
onEditingFinished: function() {
|
onEditingFinished: {
|
||||||
if(insertButton.focus || insertPopup.focus) return
|
var value = text
|
||||||
let value = text
|
if(control.isInt) value = Math.max(control.min,parseInt(value).toString()=="NaN"?control.min:parseInt(value))
|
||||||
if(control.isInt) {
|
if(control.isDouble) value = Math.max(control.min,parseFloat(value).toString()=="NaN"?control.min:parseFloat(value))
|
||||||
let parsed = parseInt(value)
|
if(value != "" && value.toString() != defValue) {
|
||||||
value = isNaN(parsed) ? control.min : Math.max(control.min,parsed)
|
|
||||||
} 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()
|
||||||
}
|
}
|
||||||
|
@ -143,7 +130,6 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
id: insertButton
|
|
||||||
text: "α"
|
text: "α"
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 5
|
anchors.rightMargin: 5
|
||||||
|
@ -151,23 +137,54 @@ Item {
|
||||||
width: 20
|
width: 20
|
||||||
height: width
|
height: width
|
||||||
visible: !isInt && !isDouble
|
visible: !isInt && !isDouble
|
||||||
onClicked: {
|
onClicked: insertPopup.open()
|
||||||
insertPopup.open()
|
|
||||||
insertPopup.focus = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Popup.InsertCharacter {
|
Popup {
|
||||||
id: insertPopup
|
id: insertPopup
|
||||||
|
|
||||||
x: Math.round((parent.width - width) / 2)
|
x: Math.round((parent.width - width) / 2)
|
||||||
y: Math.round((parent.height - height) / 2)
|
y: Math.round((parent.height - height) / 2)
|
||||||
|
width: 280
|
||||||
|
height: insertGrid.insertChars.length/insertGrid.columns*(width/insertGrid.columns)
|
||||||
|
modal: true
|
||||||
|
focus: true
|
||||||
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||||
|
|
||||||
onSelected: function(c) {
|
Grid {
|
||||||
input.insert(input.cursorPosition, c)
|
id: insertGrid
|
||||||
insertPopup.close()
|
width: parent.width
|
||||||
focus = false
|
columns: 7
|
||||||
input.focus = true
|
|
||||||
|
property var insertChars: [
|
||||||
|
"α","β","γ","δ","ε","ζ","η",
|
||||||
|
"π","θ","κ","λ","μ","ξ","ρ",
|
||||||
|
"ς","σ","τ","φ","χ","ψ","ω",
|
||||||
|
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
|
||||||
|
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
|
||||||
|
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
|
||||||
|
"ₜ","¹","²","³","⁴","⁵","⁶",
|
||||||
|
"⁷","⁸","⁹","⁰","₁","₂","₃",
|
||||||
|
"₄","₅","₆","₇","₈","₉","₀"
|
||||||
|
|
||||||
|
]
|
||||||
|
Repeater {
|
||||||
|
model: parent.insertChars.length
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: insertBtn
|
||||||
|
width: insertGrid.width/insertGrid.columns
|
||||||
|
height: width
|
||||||
|
text: insertGrid.insertChars[modelData]
|
||||||
|
flat: text == " "
|
||||||
|
font.pixelSize: 18
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
input.insert(input.cursorPosition, insertGrid.insertChars[modelData])
|
||||||
|
insertPopup.close()
|
||||||
|
input.focus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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 2.12
|
||||||
import QtQuick.Controls
|
import QtQuick 2.12
|
||||||
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 "js/utils.mjs" as Utils
|
import "js/utils.js" as Utils
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\qmltype Settings
|
\qmltype Settings
|
||||||
|
@ -44,93 +44,89 @@ 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.getSettingInt('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.getSettingInt('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.getSettingInt('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.getSettingInt('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.getSettingInt('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.getSettingInt('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.getSettingBool('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.getSettingBool('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.getSettingBool('default_graph.showygrad')
|
property bool showygrad: true
|
||||||
/*!
|
/*!
|
||||||
\qmlproperty bool Settings::saveFilename
|
\qmlproperty bool Settings::saveFilename
|
||||||
Path of the currently opened file. Empty if no file is opened.
|
Path of the currently opened file. Empty if no file is opened.
|
||||||
*/
|
*/
|
||||||
property string saveFilename: ""
|
property string saveFilename: ""
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
Modules.IO.initialize({ root, settings, alert })
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: 10
|
spacing: 10
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
@ -139,12 +135,12 @@ ScrollView {
|
||||||
Popup.FileDialog {
|
Popup.FileDialog {
|
||||||
id: fdiag
|
id: fdiag
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
var filePath = fdiag.currentFile.toString().substr(7)
|
var filePath = fileUrl.toString().substr(7)
|
||||||
settings.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)
|
||||||
if(xAxisLabel.find(settings.xlabel) == -1) xAxisLabel.model.append({text: settings.xlabel})
|
if(xAxisLabel.find(settings.xlabel) == -1) xAxisLabel.model.append({text: settings.xlabel})
|
||||||
xAxisLabel.editText = settings.xlabel
|
xAxisLabel.editText = settings.xlabel
|
||||||
if(yAxisLabel.find(settings.ylabel) == -1) yAxisLabel.model.append({text: settings.ylabel})
|
if(yAxisLabel.find(settings.ylabel) == -1) yAxisLabel.model.append({text: settings.ylabel})
|
||||||
|
@ -159,7 +155,7 @@ 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)
|
value: settings.xzoom.toFixed(2)
|
||||||
|
@ -173,7 +169,6 @@ ScrollView {
|
||||||
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
|
||||||
|
@ -227,10 +222,10 @@ ScrollView {
|
||||||
label: qsTr("Max X")
|
label: qsTr("Max X")
|
||||||
icon: "settings/xmax.svg"
|
icon: "settings/xmax.svg"
|
||||||
width: settings.settingWidth
|
width: settings.settingWidth
|
||||||
defValue: Modules.Canvas.px2x(canvas.width).toFixed(2)
|
value: canvas.px2x(canvas.canvasSize.width).toFixed(2)
|
||||||
onChanged: function(xvaluemax) {
|
onChanged: function(xvaluemax) {
|
||||||
if(xvaluemax > settings.xmin) {
|
if(xvaluemax > settings.xmin) {
|
||||||
settings.xzoom = 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)
|
||||||
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.")
|
||||||
|
@ -246,10 +241,10 @@ ScrollView {
|
||||||
label: qsTr("Min Y")
|
label: qsTr("Min Y")
|
||||||
icon: "settings/ymin.svg"
|
icon: "settings/ymin.svg"
|
||||||
width: settings.settingWidth
|
width: settings.settingWidth
|
||||||
defValue: Modules.Canvas.px2y(canvas.height).toFixed(2)
|
defValue: canvas.px2y(canvas.canvasSize.height).toFixed(2)
|
||||||
onChanged: function(yvaluemin) {
|
onChanged: function(yvaluemin) {
|
||||||
if(yvaluemin < settings.ymax) {
|
if(yvaluemin < settings.ymax) {
|
||||||
settings.yzoom = 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)
|
||||||
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.")
|
||||||
|
@ -260,7 +255,6 @@ ScrollView {
|
||||||
Setting.TextSetting {
|
Setting.TextSetting {
|
||||||
id: xAxisStep
|
id: xAxisStep
|
||||||
height: 30
|
height: 30
|
||||||
category: "expression"
|
|
||||||
label: qsTr("X Axis Step")
|
label: qsTr("X Axis Step")
|
||||||
icon: "settings/xaxisstep.svg"
|
icon: "settings/xaxisstep.svg"
|
||||||
width: settings.settingWidth
|
width: settings.settingWidth
|
||||||
|
@ -275,7 +269,6 @@ ScrollView {
|
||||||
Setting.TextSetting {
|
Setting.TextSetting {
|
||||||
id: yAxisStep
|
id: yAxisStep
|
||||||
height: 30
|
height: 30
|
||||||
category: "expression"
|
|
||||||
label: qsTr("Y Axis Step")
|
label: qsTr("Y Axis Step")
|
||||||
icon: "settings/yaxisstep.svg"
|
icon: "settings/yaxisstep.svg"
|
||||||
width: settings.settingWidth
|
width: settings.settingWidth
|
||||||
|
@ -447,7 +440,7 @@ ScrollView {
|
||||||
if(settings.saveFilename == "") {
|
if(settings.saveFilename == "") {
|
||||||
saveAs()
|
saveAs()
|
||||||
} else {
|
} else {
|
||||||
Modules.IO.saveDiagram(settings.saveFilename)
|
root.saveDiagram(settings.saveFilename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,160 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Controls
|
|
||||||
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
|
|
||||||
import "js/math/index.mjs" as MathLib
|
|
||||||
import "js/history/index.mjs" as HistoryLib
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmltype ViewPositionChangeOverlay
|
|
||||||
\inqmlmodule eu.ad5001.LogarithmPlotter
|
|
||||||
\brief Overlay used allow the user to drag the canvas' position and change the zoom level.
|
|
||||||
|
|
||||||
Provides an overlay over the canvas that detects mouse movements and changes the canvas view position
|
|
||||||
accordingly by providing new signals.
|
|
||||||
|
|
||||||
\sa LogarithmPlotter, LogGraphCanvas, Settings
|
|
||||||
*/
|
|
||||||
Item {
|
|
||||||
id: viewChangeRoot
|
|
||||||
visible: true
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlsignal ViewPositionChangeOverlay::positionChanged(int deltaX, int deltaY)
|
|
||||||
|
|
||||||
Emmited when the user dragged the canvas and the view should be refreshed.
|
|
||||||
The corresponding handler is \c onPositionChanged.
|
|
||||||
*/
|
|
||||||
signal positionChanged(int deltaX, int deltaY)
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlsignal ViewPositionChangeOverlay::beginPositionChange()
|
|
||||||
|
|
||||||
Emmited when the user starts dragging the canvas.
|
|
||||||
The corresponding handler is \c onBeginPositionChange.
|
|
||||||
*/
|
|
||||||
signal beginPositionChange()
|
|
||||||
|
|
||||||
/*!
|
|
||||||
\qmlsignal ViewPositionChangeOverlay::endPositionChange(int deltaX, int deltaY)
|
|
||||||
|
|
||||||
Emmited when the user stops dragging the canvas.
|
|
||||||
The corresponding handler is \c onEndPositionChange.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
The x coordinate (on the mousearea) at the last change of the canvas position.
|
|
||||||
*/
|
|
||||||
property int prevX
|
|
||||||
/*!
|
|
||||||
\qmlproperty int ViewPositionChangeOverlay::prevY
|
|
||||||
The y coordinate (on the mousearea) at the last change of the canvas position.
|
|
||||||
*/
|
|
||||||
property int prevY
|
|
||||||
/*!
|
|
||||||
\qmlproperty double ViewPositionChangeOverlay::baseZoomMultiplier
|
|
||||||
How much should the zoom be mutliplied/scrolled by for one scroll step (120° on the mouse wheel).
|
|
||||||
*/
|
|
||||||
property double baseZoomMultiplier: 0.1
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: dragArea
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
|
||||||
property int positionChangeTimer: 0
|
|
||||||
|
|
||||||
function updatePosition(deltaX, deltaY) {
|
|
||||||
const unauthorized = [NaN, Infinity, -Infinity]
|
|
||||||
const xmin = (Modules.Canvas.px2x(Modules.Canvas.x2px(settingsInstance.xmin)-deltaX))
|
|
||||||
const ymax = settingsInstance.ymax + deltaY/canvas.yzoom
|
|
||||||
if(!unauthorized.includes(xmin))
|
|
||||||
settingsInstance.xmin = xmin
|
|
||||||
if(!unauthorized.includes(ymax))
|
|
||||||
settingsInstance.ymax = ymax.toFixed(4)
|
|
||||||
settingsInstance.changed()
|
|
||||||
parent.positionChanged(deltaX, deltaY)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onPressed: function(mouse) {
|
|
||||||
prevX = mouse.x
|
|
||||||
prevY = mouse.y
|
|
||||||
parent.beginPositionChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
onPositionChanged: function(mouse) {
|
|
||||||
positionChangeTimer++
|
|
||||||
if(positionChangeTimer == 3) {
|
|
||||||
let deltaX = mouse.x - prevX
|
|
||||||
let deltaY = mouse.y - prevY
|
|
||||||
updatePosition(deltaX, deltaY)
|
|
||||||
prevX = mouse.x
|
|
||||||
prevY = mouse.y
|
|
||||||
positionChangeTimer = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onReleased: function(mouse) {
|
|
||||||
let deltaX = mouse.x - prevX
|
|
||||||
let deltaY = mouse.y - prevY
|
|
||||||
updatePosition(deltaX, deltaY)
|
|
||||||
parent.endPositionChange(deltaX, deltaY)
|
|
||||||
}
|
|
||||||
|
|
||||||
onWheel: function(wheel) {
|
|
||||||
// Scrolling
|
|
||||||
let scrollSteps = Math.round(wheel.angleDelta.y / 120)
|
|
||||||
let zoomMultiplier = Math.pow(1+baseZoomMultiplier, Math.abs(scrollSteps))
|
|
||||||
// Avoid floating-point rounding errors by removing the zoom *after*
|
|
||||||
let xZoomDelta = (settingsInstance.xzoom*zoomMultiplier - settingsInstance.xzoom)
|
|
||||||
let yZoomDelta = (settingsInstance.yzoom*zoomMultiplier - settingsInstance.yzoom)
|
|
||||||
if(scrollSteps < 0) { // Negative scroll
|
|
||||||
xZoomDelta *= -1
|
|
||||||
yZoomDelta *= -1
|
|
||||||
}
|
|
||||||
let newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(0)
|
|
||||||
let newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(0)
|
|
||||||
// Check if we need to have more precision
|
|
||||||
if(newXZoom < 10)
|
|
||||||
newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(4)
|
|
||||||
if(newYZoom < 10)
|
|
||||||
newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(4)
|
|
||||||
if(newXZoom > 0.5)
|
|
||||||
settingsInstance.xzoom = newXZoom
|
|
||||||
if(newYZoom > 0.5)
|
|
||||||
settingsInstance.yzoom = newYZoom
|
|
||||||
settingsInstance.changed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Before Width: | Height: | Size: 370 B After Width: | Height: | Size: 370 B |
|
@ -1,48 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
version="1.1"
|
|
||||||
id="svg6"
|
|
||||||
sodipodi:docname="remove.svg"
|
|
||||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<defs
|
|
||||||
id="defs10" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview8"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
showgrid="true"
|
|
||||||
inkscape:zoom="34.458333"
|
|
||||||
inkscape:cx="12"
|
|
||||||
inkscape:cy="10.505441"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1007"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg6">
|
|
||||||
<inkscape:grid
|
|
||||||
type="xygrid"
|
|
||||||
id="grid822" />
|
|
||||||
</sodipodi:namedview>
|
|
||||||
<path
|
|
||||||
id="rect2"
|
|
||||||
style="fill-rule:evenodd;stroke-width:3.16228"
|
|
||||||
transform="rotate(135)"
|
|
||||||
d="M -1.4142136,-26.870058 H 1.4142136 V -7.0710678 H -1.4142136 Z"
|
|
||||||
sodipodi:nodetypes="ccccc" />
|
|
||||||
<path
|
|
||||||
style="fill:#000000;fill-rule:evenodd;stroke-width:3.16228"
|
|
||||||
d="M 20,6 6,20 4,18 18,4 Z"
|
|
||||||
id="path4"
|
|
||||||
sodipodi:nodetypes="ccccc" />
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1 +0,0 @@
|
||||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M14 0v10l2-1.518 2 1.518v-10h4v24h-17c-1.657 0-3-1.343-3-3v-18c0-1.657 1.343-3 3-3h9zm6 20h-14.505c-1.375 0-1.375 2 0 2h14.505v-2z"/></svg>
|
|
Before Width: | Height: | Size: 251 B |
|
@ -1 +0,0 @@
|
||||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M12 0l-2.138 2.63-3.068-1.441-.787 3.297-3.389.032.722 3.312-3.039 1.5 2.088 2.671-2.088 2.67 3.039 1.499-.722 3.312 3.389.033.787 3.296 3.068-1.441 2.138 2.63 2.139-2.63 3.068 1.441.786-3.296 3.39-.033-.722-3.312 3.038-1.499-2.087-2.67 2.087-2.671-3.038-1.5.722-3.312-3.39-.032-.786-3.297-3.068 1.441-2.139-2.63zm0 15.5c.69 0 1.25.56 1.25 1.25s-.56 1.25-1.25 1.25-1.25-.56-1.25-1.25.56-1.25 1.25-1.25zm1-1.038v-7.462h-2v7.462h2z"/></svg>
|
|
Before Width: | Height: | Size: 550 B |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M24 13.616v-3.232l-2.869-1.02c-.198-.687-.472-1.342-.811-1.955l1.308-2.751-2.285-2.285-2.751 1.307c-.613-.339-1.269-.613-1.955-.811l-1.021-2.869h-3.232l-1.021 2.869c-.686.198-1.342.471-1.955.811l-2.751-1.308-2.285 2.285 1.308 2.752c-.339.613-.614 1.268-.811 1.955l-2.869 1.02v3.232l2.869 1.02c.197.687.472 1.342.811 1.955l-1.308 2.751 2.285 2.286 2.751-1.308c.613.339 1.269.613 1.955.811l1.021 2.869h3.232l1.021-2.869c.687-.198 1.342-.472 1.955-.811l2.751 1.308 2.285-2.286-1.308-2.751c.339-.613.613-1.268.811-1.955l2.869-1.02zm-12 2.384c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z"/></svg>
|
|
Before Width: | Height: | Size: 696 B |
|
@ -1 +0,0 @@
|
||||||
../common/close.svg
|
|
Before Width: | Height: | Size: 19 B After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,48 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
version="1.1"
|
||||||
|
id="svg6"
|
||||||
|
sodipodi:docname="remove.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs10" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview8"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="34.458333"
|
||||||
|
inkscape:cx="12"
|
||||||
|
inkscape:cy="10.505441"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1007"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg6">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid822" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<path
|
||||||
|
id="rect2"
|
||||||
|
style="fill-rule:evenodd;stroke-width:3.16228"
|
||||||
|
transform="rotate(135)"
|
||||||
|
d="M -1.4142136,-26.870058 H 1.4142136 V -7.0710678 H -1.4142136 Z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke-width:3.16228"
|
||||||
|
d="M 20,6 6,20 4,18 18,4 Z"
|
||||||
|
id="path4"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
</svg>
|
Before Width: | Height: | Size: 19 B After Width: | Height: | Size: 1.4 KiB |
|
@ -1,17 +1,44 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
width="24.0px"
|
width="24.0px"
|
||||||
height="24.0px"
|
height="24.0px"
|
||||||
viewBox="0 0 24.0 24.0"
|
viewBox="0 0 24.0 24.0"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="SVGRoot"
|
id="SVGRoot"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
sodipodi:docname="Function.svg"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
<defs
|
||||||
id="defs833" />
|
id="defs833" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="31.678384"
|
||||||
|
inkscape:cx="15.268708"
|
||||||
|
inkscape:cy="12.238724"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid1403" />
|
||||||
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata10">
|
id="metadata10">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
|
@ -20,7 +47,8 @@
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:type
|
<dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<dc:date>2021-2023</dc:date>
|
<dc:title />
|
||||||
|
<dc:date>2021</dc:date>
|
||||||
<dc:creator>
|
<dc:creator>
|
||||||
<cc:Agent>
|
<cc:Agent>
|
||||||
<dc:title>Ad5001</dc:title>
|
<dc:title>Ad5001</dc:title>
|
||||||
|
@ -28,7 +56,7 @@
|
||||||
</dc:creator>
|
</dc:creator>
|
||||||
<dc:rights>
|
<dc:rights>
|
||||||
<cc:Agent>
|
<cc:Agent>
|
||||||
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
|
<dc:title>(C) Ad5001 2021 - Licensed under CC4.0-BY-NC-SA</dc:title>
|
||||||
</cc:Agent>
|
</cc:Agent>
|
||||||
</dc:rights>
|
</dc:rights>
|
||||||
<cc:license
|
<cc:license
|
||||||
|
@ -50,46 +78,33 @@
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</metadata>
|
</metadata>
|
||||||
<g
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<text
|
<path
|
||||||
xml:space="preserve"
|
id="rect1415"
|
||||||
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
|
style="fill:#000000;fill-rule:evenodd"
|
||||||
x="0.012050295"
|
d="M 2,9 C 2,5 6,5 6,5 H 7 V 7 H 6 C 6,7 4,7 4,9 v 2 h 2 v 2 H 4 v 5 H 2 V 13 H 0 v -2 h 2 z"
|
||||||
y="17.985596"
|
sodipodi:nodetypes="ccccccccccccccccc" />
|
||||||
id="text1"><tspan
|
<path
|
||||||
id="tspan1"
|
id="rect839"
|
||||||
x="0.012050295"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
y="17.985596"
|
d="m 12,3 v 2 c 0,0 -2,0 -2,7 0,7 2,7 2,7 v 2 C 8,21 8,12 8,12 8,12 8,3 12,3 Z"
|
||||||
style="font-size:17.3333px">f</tspan></text>
|
sodipodi:nodetypes="ccccccc" />
|
||||||
<text
|
<path
|
||||||
xml:space="preserve"
|
id="rect857"
|
||||||
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
x="10.913334"
|
d="m 12,10 h 2 l 6,8 h -2 z"
|
||||||
y="18.134649"
|
sodipodi:nodetypes="ccccc" />
|
||||||
id="text1-3"><tspan
|
<path
|
||||||
id="tspan1-6"
|
id="rect857-7"
|
||||||
x="10.913334"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
y="18.134649"
|
d="m 12,18 h 2 l 6,-8 h -2 z"
|
||||||
style="font-size:17.3333px">x</tspan></text>
|
sodipodi:nodetypes="ccccc" />
|
||||||
<text
|
<path
|
||||||
xml:space="preserve"
|
id="rect839-3"
|
||||||
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
x="6.3066678"
|
d="m 20,3 v 2 c 0,0 2,0 2,7 0,7 -2,7 -2,7 v 2 c 4,0 4,-9 4,-9 0,0 0,-9 -4,-9 z"
|
||||||
y="17.646639"
|
sodipodi:nodetypes="ccccccc" />
|
||||||
id="text2"><tspan
|
|
||||||
id="tspan2"
|
|
||||||
x="6.3066678"
|
|
||||||
y="17.646639"
|
|
||||||
style="font-size:17.3333px">(</tspan></text>
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
|
|
||||||
x="18.306667"
|
|
||||||
y="17.646639"
|
|
||||||
id="text2-7"><tspan
|
|
||||||
id="tspan2-5"
|
|
||||||
x="18.306667"
|
|
||||||
y="17.646639"
|
|
||||||
style="font-size:17.3333px">)</tspan></text>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.6 KiB |
|
@ -1,17 +1,47 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
width="24.0px"
|
width="24.0px"
|
||||||
height="24.0px"
|
height="24.0px"
|
||||||
viewBox="0 0 24.0 24.0"
|
viewBox="0 0 24.0 24.0"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="SVGRoot"
|
id="SVGRoot"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
sodipodi:docname="Gain Bode.svg"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
<defs
|
||||||
id="defs1469" />
|
id="defs1469" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="15.839192"
|
||||||
|
inkscape:cx="15.763196"
|
||||||
|
inkscape:cy="7.8365971"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2039" />
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2058" />
|
||||||
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata1472">
|
id="metadata1472">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
|
@ -20,40 +50,13 @@
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:type
|
<dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<dc:date>2021-2023</dc:date>
|
<dc:title />
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Ad5001</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:rights>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:rights>
|
|
||||||
<cc:license
|
|
||||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
<cc:License
|
|
||||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
|
||||||
<cc:prohibits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</metadata>
|
</metadata>
|
||||||
<g
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<rect
|
<rect
|
||||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
|
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
|
||||||
|
@ -64,14 +67,15 @@
|
||||||
y="17" />
|
y="17" />
|
||||||
<text
|
<text
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
x="-0.105"
|
x="-0.12666447"
|
||||||
y="11.959"
|
y="12.134649"
|
||||||
id="text839"><tspan
|
id="text839"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
id="tspan837"
|
id="tspan837"
|
||||||
x="-0.105"
|
x="-0.12666447"
|
||||||
y="11.959"
|
y="12.134649"
|
||||||
style="font-size:17px">ω</tspan></text>
|
style="font-size:17.3333px">ω</tspan></text>
|
||||||
<circle
|
<circle
|
||||||
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
id="path837"
|
id="path837"
|
||||||
|
@ -82,6 +86,7 @@
|
||||||
id="rect837"
|
id="rect837"
|
||||||
style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035"
|
||||||
transform="rotate(30)"
|
transform="rotate(30)"
|
||||||
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z" />
|
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
@ -1,17 +1,44 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
width="24.0px"
|
width="24.0px"
|
||||||
height="24.0px"
|
height="24.0px"
|
||||||
viewBox="0 0 24.0 24.0"
|
viewBox="0 0 24.0 24.0"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="SVGRoot"
|
id="SVGRoot"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
sodipodi:docname="Phase Bode.svg"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
<defs
|
||||||
id="defs10" />
|
id="defs10" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.4"
|
||||||
|
inkscape:cx="15.347905"
|
||||||
|
inkscape:cy="8.3727678"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid19" />
|
||||||
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata13">
|
id="metadata13">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
|
@ -20,40 +47,13 @@
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:type
|
<dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<dc:date>2021-2023</dc:date>
|
<dc:title />
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Ad5001</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:rights>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:rights>
|
|
||||||
<cc:license
|
|
||||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
<cc:License
|
|
||||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
|
||||||
<cc:prohibits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</metadata>
|
</metadata>
|
||||||
<g
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<rect
|
<rect
|
||||||
style="fill:#000000;stroke-width:1.1547"
|
style="fill:#000000;stroke-width:1.1547"
|
||||||
|
@ -65,7 +65,8 @@
|
||||||
<path
|
<path
|
||||||
id="rect26-3"
|
id="rect26-3"
|
||||||
style="fill:#000000;stroke-width:1.22474"
|
style="fill:#000000;stroke-width:1.22474"
|
||||||
d="m 15,2 v 14 h 2 V 4 h 7 V 2 Z" />
|
d="m 15,2 v 14 h 2 V 4 h 7 V 2 Z"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
<circle
|
<circle
|
||||||
style="fill:#000000;stroke-width:2.09999"
|
style="fill:#000000;stroke-width:2.09999"
|
||||||
id="path45"
|
id="path45"
|
||||||
|
@ -74,13 +75,14 @@
|
||||||
r="4" />
|
r="4" />
|
||||||
<text
|
<text
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
|
style="font-size:18px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
x="6.1339936"
|
x="6.4359932"
|
||||||
y="11.163"
|
y="11.702"
|
||||||
id="text49"><tspan
|
id="text49"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
id="tspan47"
|
id="tspan47"
|
||||||
x="6.1339936"
|
x="6.4359932"
|
||||||
y="11.163"
|
y="11.702"
|
||||||
style="font-size:17px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text>
|
style="font-size:18px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -1,67 +1 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path style="opacity:1;vector-effect:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" d="M9.98 18.12a3.5 3.5 0 0 1-3.064 3.855 3.5 3.5 0 0 1-3.887-3.022 3.5 3.5 0 0 1 2.982-3.919 3.5 3.5 0 0 1 3.95 2.94"/><text xml:space="preserve" style="font-style:normal;font-weight:400;font-size:17.3373px;line-height:1.25;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill-opacity:1;stroke:none;stroke-width:1.00023" x="12.067" y="13.923" transform="scale(.99447 1.00556)"><tspan x="12.067" y="13.923" style="font-size:17.3373px;stroke-width:1.00023">A</tspan></text></svg>
|
||||||
<svg
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
|
||||||
id="defs1" />
|
|
||||||
<path
|
|
||||||
style="opacity:1;vector-effect:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
|
|
||||||
d="M9.98 18.12a3.5 3.5 0 0 1-3.064 3.855 3.5 3.5 0 0 1-3.887-3.022 3.5 3.5 0 0 1 2.982-3.919 3.5 3.5 0 0 1 3.95 2.94"
|
|
||||||
id="path1" />
|
|
||||||
<text
|
|
||||||
xml:space="preserve"
|
|
||||||
style="font-style:normal;font-weight:400;font-size:17px;line-height:1.25;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill-opacity:1;stroke:none;stroke-width:1.00023"
|
|
||||||
x="11.964725"
|
|
||||||
y="13.701941"
|
|
||||||
transform="scale(0.99447036,1.0055604)"
|
|
||||||
id="text1"><tspan
|
|
||||||
x="11.964725"
|
|
||||||
y="13.701941"
|
|
||||||
style="font-size:17px;stroke-width:1.00023"
|
|
||||||
id="tspan1">A</tspan></text>
|
|
||||||
<metadata
|
|
||||||
id="metadata1">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:date>2021-2023</dc:date>
|
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Ad5001</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:rights>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:rights>
|
|
||||||
<cc:license
|
|
||||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
|
||||||
</cc:Work>
|
|
||||||
<cc:License
|
|
||||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
|
||||||
<cc:prohibits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 772 B |
|
@ -1,17 +1,45 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
width="24.0px"
|
width="24.0px"
|
||||||
height="24.0px"
|
height="24.0px"
|
||||||
viewBox="0 0 24.0 24.0"
|
viewBox="0 0 24.0 24.0"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="SVGRoot"
|
id="SVGRoot"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
sodipodi:docname="Sequence.svg"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
<defs
|
||||||
id="defs10" />
|
id="defs10" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.4"
|
||||||
|
inkscape:cx="-1.4929284"
|
||||||
|
inkscape:cy="9.7261905"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid19" />
|
||||||
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata13">
|
id="metadata13">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
|
@ -20,62 +48,56 @@
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:type
|
<dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<dc:date>2021-2023</dc:date>
|
<dc:title />
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Ad5001</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:rights>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:rights>
|
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</metadata>
|
</metadata>
|
||||||
<g
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<text
|
<text
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
style="font-size:17.3333px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;-inkscape-font-specification:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
|
style="font-size:17.3333px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle"
|
||||||
x="7.483983"
|
x="7.483983"
|
||||||
y="16.134649"
|
y="16.134649"
|
||||||
id="text908"><tspan
|
id="text908"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
id="tspan906"
|
id="tspan906"
|
||||||
x="7.483983"
|
x="7.483983"
|
||||||
y="16.134649"
|
y="16.134649"
|
||||||
style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">u</tspan></text>
|
style="font-size:17.3333px">u</tspan></text>
|
||||||
<text
|
<text
|
||||||
xml:space="preserve"
|
xml:space="preserve"
|
||||||
style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:1.00003;-inkscape-font-specification:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
|
style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:1.00003"
|
||||||
x="16.365566"
|
x="16.365566"
|
||||||
y="18.663307"
|
y="18.663307"
|
||||||
id="text912"
|
id="text912"
|
||||||
transform="scale(1.0000324,0.9999676)"><tspan
|
transform="scale(1.0000324,0.9999676)"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
id="tspan910"
|
id="tspan910"
|
||||||
x="16.365566"
|
x="16.365566"
|
||||||
y="18.663307"
|
y="18.663307"
|
||||||
style="font-size:17px;stroke-width:1.00003;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">n</tspan></text>
|
style="font-size:17px;stroke-width:1.00003">n</tspan></text>
|
||||||
<text
|
<g
|
||||||
xml:space="preserve"
|
aria-label="("
|
||||||
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal"
|
id="text852"
|
||||||
x="-0.69333196"
|
style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
y="17.646639"
|
transform="matrix(1.0022756,0,0,1.2616817,-0.26079098,-9.0560687)">
|
||||||
id="text2"><tspan
|
<path
|
||||||
id="tspan2"
|
d="M 2.712,9.86 C 1.536,11.528 0.48,12.956 0.48,15.8 c 0,2.844 1.056,4.272 2.232,5.94 L 3.408,21.26 C 2.328,19.664 1.632,18.368 1.632,15.8 c 0,-2.58 0.696,-3.864 1.776,-5.46 z"
|
||||||
x="-0.69333196"
|
style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
y="17.646639"
|
id="path854" />
|
||||||
style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">(</tspan></text>
|
</g>
|
||||||
<text
|
<g
|
||||||
xml:space="preserve"
|
aria-label="("
|
||||||
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal"
|
id="text852-3"
|
||||||
x="18.806667"
|
style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
y="17.646639"
|
transform="matrix(-1.0030304,0,0,1.2658306,24.414952,-9.1000412)">
|
||||||
id="text2-7"><tspan
|
<path
|
||||||
id="tspan2-5"
|
d="M 2.712,9.86 C 1.536,11.528 0.48,12.956 0.48,15.8 c 0,2.844 1.056,4.272 2.232,5.94 L 3.408,21.26 C 2.328,19.664 1.632,18.368 1.632,15.8 c 0,-2.58 0.696,-3.864 1.776,-5.46 z"
|
||||||
x="18.806667"
|
style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
y="17.646639"
|
id="path854-6" />
|
||||||
style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">)</tspan></text>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,96 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="24.0px"
|
||||||
|
height="24.0px"
|
||||||
|
viewBox="0 0 24.0 24.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="Somme gains Bode.svg"
|
||||||
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
|
<defs
|
||||||
|
id="defs1469" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="63.356768"
|
||||||
|
inkscape:cx="15.947723"
|
||||||
|
inkscape:cy="5.6917309"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2039" />
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2058" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata1472">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
id="rect838"
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
|
d="M 2,2 H 8 V 3 H 4 L 7,6 4,9 h 4 v 1 H 2 V 9 L 5,6 2,3 Z"
|
||||||
|
sodipodi:nodetypes="ccccccccccccc" />
|
||||||
|
<rect
|
||||||
|
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
|
||||||
|
id="rect835"
|
||||||
|
width="14"
|
||||||
|
height="2"
|
||||||
|
x="0"
|
||||||
|
y="17" />
|
||||||
|
<circle
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
|
id="path837"
|
||||||
|
cx="13"
|
||||||
|
cy="18"
|
||||||
|
r="4" />
|
||||||
|
<path
|
||||||
|
id="rect837"
|
||||||
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035"
|
||||||
|
transform="rotate(30)"
|
||||||
|
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<g
|
||||||
|
aria-label="G"
|
||||||
|
id="text846"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none">
|
||||||
|
<path
|
||||||
|
d="m 13,2 c 0,0 -3,0 -3,3 0,3 0,5 3,5 2,0 3,0 3,-2 V 6 h -3 v 1 h 2 v 1 c 0,1 -1,1 -2,1 -1,0 -2,0 -2,-4 0,-1 1,-2 2,-2 2,0 2,1 2,1 h 1 c 0,0 0,-2 -3,-2 z"
|
||||||
|
style="font-size:12px"
|
||||||
|
id="path848"
|
||||||
|
sodipodi:nodetypes="sssccccccsssccs" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1,93 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="24.0px"
|
||||||
|
height="24.0px"
|
||||||
|
viewBox="0 0 24.0 24.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="Somme gains Bode.svg"
|
||||||
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
|
<defs
|
||||||
|
id="defs1469" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.4"
|
||||||
|
inkscape:cx="22.985246"
|
||||||
|
inkscape:cy="9.8906279"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2039" />
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2058" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata1472">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
id="path1414"
|
||||||
|
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
|
||||||
|
d="m 19.979376,19.120606 a 3.5,3.5 0 0 1 -3.06333,3.854578 3.5,3.5 0 0 1 -3.886652,-3.022533 3.5,3.5 0 0 1 2.9814,-3.918293 3.5,3.5 0 0 1 3.9495,2.939936" />
|
||||||
|
<rect
|
||||||
|
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16.166;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
|
||||||
|
id="rect835"
|
||||||
|
width="18"
|
||||||
|
height="2"
|
||||||
|
x="0"
|
||||||
|
y="18.5" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:16px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||||
|
x="-0.5703125"
|
||||||
|
y="11.875"
|
||||||
|
id="text839"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan837"
|
||||||
|
x="-0.5703125"
|
||||||
|
y="11.875"
|
||||||
|
style="font-size:16px">ΣG</tspan></text>
|
||||||
|
<rect
|
||||||
|
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:19.0663;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
|
||||||
|
id="rect835-3"
|
||||||
|
width="25.038315"
|
||||||
|
height="2"
|
||||||
|
x="-10.17229"
|
||||||
|
y="23.748709"
|
||||||
|
ry="0"
|
||||||
|
transform="rotate(-60)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
|
@ -0,0 +1,92 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="24.0px"
|
||||||
|
height="24.0px"
|
||||||
|
viewBox="0 0 24.0 24.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="Somme phases Bode.svg"
|
||||||
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
|
<defs
|
||||||
|
id="defs10" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.4"
|
||||||
|
inkscape:cx="15.347905"
|
||||||
|
inkscape:cy="8.3727678"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid19" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata13">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#000000;stroke-width:1.41421"
|
||||||
|
id="rect26"
|
||||||
|
width="12"
|
||||||
|
height="2"
|
||||||
|
x="9"
|
||||||
|
y="18" />
|
||||||
|
<rect
|
||||||
|
style="fill:#000000;stroke-width:0.912867"
|
||||||
|
id="rect26-3"
|
||||||
|
width="5"
|
||||||
|
height="2"
|
||||||
|
x="19"
|
||||||
|
y="2" />
|
||||||
|
<rect
|
||||||
|
style="fill:#000000;stroke-width:1.5"
|
||||||
|
id="rect43"
|
||||||
|
width="2"
|
||||||
|
height="16"
|
||||||
|
x="19"
|
||||||
|
y="4" />
|
||||||
|
<circle
|
||||||
|
style="fill:#000000;stroke-width:2.09999"
|
||||||
|
id="path45"
|
||||||
|
cx="20"
|
||||||
|
cy="19"
|
||||||
|
r="3.5" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:16px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle"
|
||||||
|
x="8.7617188"
|
||||||
|
y="11.664062"
|
||||||
|
id="text49"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan47"
|
||||||
|
x="8.7617188"
|
||||||
|
y="11.664062"
|
||||||
|
style="font-size:16px">Σφ</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -1,19 +1,19 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
width="24.0px"
|
width="24.0px"
|
||||||
height="24.0px"
|
height="24.0px"
|
||||||
viewBox="0 0 24.0 24.0"
|
viewBox="0 0 24.0 24.0"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="SVGRoot"
|
id="SVGRoot"
|
||||||
sodipodi:docname="Text.svg"
|
sodipodi:docname="Text.svg"
|
||||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
<defs
|
||||||
id="defs1469" />
|
id="defs1469" />
|
||||||
<sodipodi:namedview
|
<sodipodi:namedview
|
||||||
|
@ -24,38 +24,23 @@
|
||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="22.4"
|
inkscape:zoom="22.4"
|
||||||
inkscape:cx="13.772321"
|
inkscape:cx="13.763421"
|
||||||
inkscape:cy="8.4598214"
|
inkscape:cy="16.975675"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
inkscape:document-rotation="0"
|
inkscape:document-rotation="0"
|
||||||
showgrid="true"
|
showgrid="true"
|
||||||
inkscape:window-width="1920"
|
inkscape:window-width="1920"
|
||||||
inkscape:window-height="1010"
|
inkscape:window-height="1011"
|
||||||
inkscape:window-x="0"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="0"
|
inkscape:window-y="0"
|
||||||
inkscape:window-maximized="1"
|
inkscape:window-maximized="1">
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1">
|
|
||||||
<inkscape:grid
|
<inkscape:grid
|
||||||
type="xygrid"
|
type="xygrid"
|
||||||
id="grid2039"
|
id="grid2039" />
|
||||||
originx="0"
|
|
||||||
originy="0"
|
|
||||||
spacingy="1"
|
|
||||||
spacingx="1"
|
|
||||||
units="px"
|
|
||||||
visible="true" />
|
|
||||||
<inkscape:grid
|
<inkscape:grid
|
||||||
type="xygrid"
|
type="xygrid"
|
||||||
id="grid2058"
|
id="grid2058" />
|
||||||
originx="0"
|
|
||||||
originy="0"
|
|
||||||
spacingy="1"
|
|
||||||
spacingx="1"
|
|
||||||
units="px"
|
|
||||||
visible="true" />
|
|
||||||
</sodipodi:namedview>
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata1472">
|
id="metadata1472">
|
||||||
|
@ -65,37 +50,8 @@
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:type
|
<dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<dc:date>2021-2023</dc:date>
|
<dc:title />
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Ad5001</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:rights>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:rights>
|
|
||||||
<cc:license
|
|
||||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
<cc:License
|
|
||||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
|
||||||
<cc:prohibits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</metadata>
|
</metadata>
|
||||||
<g
|
<g
|
||||||
|
@ -112,16 +68,10 @@
|
||||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.670999;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.670999;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m 4,3 h 8 V 5 H 9 v 14 h 3 v 2 H 4 V 19 H 7 V 5 H 4 Z"
|
d="m 4,3 h 8 V 5 H 9 v 14 h 3 v 2 H 4 V 19 H 7 V 5 H 4 Z"
|
||||||
sodipodi:nodetypes="ccccccccccccc" />
|
sodipodi:nodetypes="ccccccccccccc" />
|
||||||
<text
|
<path
|
||||||
xml:space="preserve"
|
id="rect837"
|
||||||
style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
x="13.844"
|
d="m 16,5 h 2 v 3 h 2 v 2 h -2 v 5 c 0,2 2,2 2,2 v 2 c 0,0 -4,0 -4,-4 V 10 H 14 V 8 h 2 z"
|
||||||
y="17.387978"
|
sodipodi:nodetypes="ccccccccccccccc" />
|
||||||
id="text1"><tspan
|
|
||||||
sodipodi:role="line"
|
|
||||||
id="tspan1"
|
|
||||||
x="13.844"
|
|
||||||
y="17.387978"
|
|
||||||
style="font-size:17px">t</tspan></text>
|
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -1,17 +1,47 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<svg
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
width="24.0px"
|
width="24.0px"
|
||||||
height="24.0px"
|
height="24.0px"
|
||||||
viewBox="0 0 24.0 24.0"
|
viewBox="0 0 24.0 24.0"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="SVGRoot"
|
id="SVGRoot"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
sodipodi:docname="X Cursor.svg"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
<defs
|
||||||
id="defs1469" />
|
id="defs1469" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="22.4"
|
||||||
|
inkscape:cx="19.545462"
|
||||||
|
inkscape:cy="13.163586"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="1829"
|
||||||
|
inkscape:window-height="916"
|
||||||
|
inkscape:window-x="19"
|
||||||
|
inkscape:window-y="31"
|
||||||
|
inkscape:window-maximized="0">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2039" />
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2058" />
|
||||||
|
</sodipodi:namedview>
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata1472">
|
id="metadata1472">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
|
@ -20,40 +50,13 @@
|
||||||
<dc:format>image/svg+xml</dc:format>
|
<dc:format>image/svg+xml</dc:format>
|
||||||
<dc:type
|
<dc:type
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
<dc:date>2021-2023</dc:date>
|
<dc:title />
|
||||||
<dc:creator>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>Ad5001</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:creator>
|
|
||||||
<dc:rights>
|
|
||||||
<cc:Agent>
|
|
||||||
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
|
|
||||||
</cc:Agent>
|
|
||||||
</dc:rights>
|
|
||||||
<cc:license
|
|
||||||
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
|
|
||||||
</cc:Work>
|
</cc:Work>
|
||||||
<cc:License
|
|
||||||
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
|
||||||
<cc:prohibits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
|
||||||
<cc:permits
|
|
||||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
|
||||||
<cc:requires
|
|
||||||
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
|
|
||||||
</cc:License>
|
|
||||||
</rdf:RDF>
|
</rdf:RDF>
|
||||||
</metadata>
|
</metadata>
|
||||||
<g
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
id="layer1">
|
id="layer1">
|
||||||
<g
|
<g
|
||||||
aria-label="X"
|
aria-label="X"
|
||||||
|
@ -65,18 +68,18 @@
|
||||||
id="rect12"
|
id="rect12"
|
||||||
width="2"
|
width="2"
|
||||||
height="24"
|
height="24"
|
||||||
x="5"
|
x="17"
|
||||||
y="0"
|
y="0"
|
||||||
ry="2.14841e-13" />
|
ry="2.14841e-13" />
|
||||||
<text
|
<path
|
||||||
xml:space="preserve"
|
id="rect835"
|
||||||
style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
x="10.915"
|
d="m 5,2 h 2 l 8,14 h -2 z"
|
||||||
y="13.713"
|
sodipodi:nodetypes="ccccc" />
|
||||||
id="text1"><tspan
|
<path
|
||||||
id="tspan1"
|
id="rect835-6"
|
||||||
x="10.915"
|
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
|
||||||
y="13.713"
|
d="M 15,2 H 13 L 5,16 h 2 z"
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17px;font-family:sans-serif;-inkscape-font-specification:sans-serif">X</tspan></text>
|
sodipodi:nodetypes="ccccc" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -1 +0,0 @@
|
||||||
../common/appearance.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/label.svg
|
|
|
@ -1 +0,0 @@
|
||||||
../common/text.svg
|
|
1841
LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js
Normal 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-2024 Ad5001
|
* Copyright (C) 2022 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,10 +16,13 @@
|
||||||
* 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 EditedProperty from "editproperty.mjs"
|
.pragma library
|
||||||
import Objects from "../module/objects.mjs"
|
|
||||||
|
|
||||||
export default class ColorChanged extends EditedProperty {
|
.import "editproperty.js" as EP
|
||||||
|
.import "../objects.js" as Objects
|
||||||
|
|
||||||
|
|
||||||
|
class ColorChanged extends EP.EditedProperty {
|
||||||
// Action used everytime when an object's color is changed
|
// Action used everytime when an object's color is changed
|
||||||
type(){return 'ColorChanged'}
|
type(){return 'ColorChanged'}
|
||||||
|
|
||||||
|
@ -37,7 +40,7 @@ export default class ColorChanged extends EditedProperty {
|
||||||
color(darkVer=false){return darkVer ? 'purple' : 'plum'}
|
color(darkVer=false){return darkVer ? 'purple' : 'plum'}
|
||||||
|
|
||||||
getReadableString() {
|
getReadableString() {
|
||||||
return qsTranslate("color", "%1 %2's color changed from %3 to %4.")
|
return qsTr("%1 %2's color changed from %3 to %4.")
|
||||||
.arg(Objects.types[this.targetType].displayType()).arg(this.targetName)
|
.arg(Objects.types[this.targetType].displayType()).arg(this.targetName)
|
||||||
.arg(this.previousValue).arg(this.newValue)
|
.arg(this.previousValue).arg(this.newValue)
|
||||||
}
|
}
|
||||||
|
@ -47,7 +50,7 @@ export default class ColorChanged extends EditedProperty {
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLString() {
|
getHTMLString() {
|
||||||
return qsTranslate("color", "%1 %2's color changed from %3 to %4.")
|
return qsTr("%1 %2's color changed from %3 to %4.")
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
.arg(Objects.types[this.targetType].displayType())
|
||||||
.arg('<b style="font-size: 15px;"> ' + this.targetName + " </b>")
|
.arg('<b style="font-size: 15px;"> ' + this.targetName + " </b>")
|
||||||
.arg(this.formatColor(this.previousValue)).arg(this.formatColor(this.newValue))
|
.arg(this.formatColor(this.previousValue)).arg(this.formatColor(this.newValue))
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||||
|
* Copyright (C) 2022 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.pragma library
|
||||||
|
|
||||||
|
var themeTextColor;
|
||||||
|
|
||||||
|
|
||||||
|
class Action {
|
||||||
|
// Type of the action done.
|
||||||
|
type(){return 'Unknown'}
|
||||||
|
|
||||||
|
// Icon associated with the item
|
||||||
|
|
||||||
|
// TargetName is the name of the object that's targeted by the event.
|
||||||
|
constructor(targetName = "", targetType = "Point") {
|
||||||
|
this.targetName = targetName
|
||||||
|
this.targetType = targetType
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {}
|
||||||
|
|
||||||
|
redo() {}
|
||||||
|
|
||||||
|
export() {
|
||||||
|
return [this.targetName, this.targetType]
|
||||||
|
}
|
||||||
|
|
||||||
|
// String used in the toolkit
|
||||||
|
getReadableString() {
|
||||||
|
return 'Unknown action'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an HTML tag containing the icon of a type
|
||||||
|
getIconRichText(type) {
|
||||||
|
return `<img source="../icons/objects/${type}.svg" style="color: ${themeTextColor};" width=18 height=18></img>`
|
||||||
|
}
|
||||||
|
|
||||||
|
// String used in the preview
|
||||||
|
getHTMLString() {
|
||||||
|
return this.getReadableString()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,112 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import History from "../module/history.mjs"
|
|
||||||
import Latex from "../module/latex.mjs"
|
|
||||||
|
|
||||||
export class Action {
|
|
||||||
/**
|
|
||||||
* Type of the action.
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
type(){return 'Unknown'}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon associated with the action.
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
icon(){return 'position'}
|
|
||||||
|
|
||||||
// TargetName is the name of the object that's targeted by the event.
|
|
||||||
constructor(targetName = "", targetType = "Point") {
|
|
||||||
this.targetName = targetName
|
|
||||||
this.targetType = targetType
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Undoes the action.
|
|
||||||
*/
|
|
||||||
undo() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redoes the action.
|
|
||||||
*/
|
|
||||||
redo() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export the action to a serializable format.
|
|
||||||
* NOTE: These arguments will be reinputed in the constructor in this order.
|
|
||||||
*
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
export() {
|
|
||||||
return [this.targetName, this.targetType]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string with the human readable description of the action.
|
|
||||||
*
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
getReadableString() {
|
|
||||||
return 'Unknown action'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string containing an HTML tag describing the icon of a type
|
|
||||||
*
|
|
||||||
* @param {string} type - Name of the icon to put in rich text.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
getIconRichText(type) {
|
|
||||||
return `<img source="../icons/objects/${type}.svg" style="color: ${History.themeTextColor};" width=18 height=18></img>`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a LaTeX-formatted string to an image and wraps it in an HTML tag in a string.
|
|
||||||
*
|
|
||||||
* @param {string} latexString - Source string of the latex.
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
*/
|
|
||||||
renderLatexAsHtml(latexString) {
|
|
||||||
if(!Latex.enabled)
|
|
||||||
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
|
|
||||||
return new Promise(resolve => {
|
|
||||||
let imgDepth = History.imageDepth
|
|
||||||
Latex.requestAsyncRender(
|
|
||||||
latexString,
|
|
||||||
imgDepth * (History.fontSize + 2),
|
|
||||||
History.themeTextColor
|
|
||||||
).then((imgData) => {
|
|
||||||
const { source, width, height } = imgData
|
|
||||||
resolve(`<img src="${source}" width="${width/imgDepth}" height="${height/imgDepth}" style="vertical-align: middle"/>`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string with the HTML-formatted description of the action.
|
|
||||||
*
|
|
||||||
* @returns {string|Promise<string>}
|
|
||||||
*/
|
|
||||||
getHTMLString() {
|
|
||||||
return this.getReadableString()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,10 +16,12 @@
|
||||||
* 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 Objects from "../module/objects.mjs"
|
.pragma library
|
||||||
import { Action } from "common.mjs"
|
|
||||||
|
|
||||||
export default class CreateNewObject extends Action {
|
.import "../objects.js" as Objects
|
||||||
|
.import "common.js" as C
|
||||||
|
|
||||||
|
class CreateNewObject extends C.Action {
|
||||||
// Action used for the creation of an object
|
// Action used for the creation of an object
|
||||||
type(){return 'CreateNewObject'}
|
type(){return 'CreateNewObject'}
|
||||||
|
|
||||||
|
@ -34,12 +36,13 @@ export default class CreateNewObject extends Action {
|
||||||
|
|
||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
Objects.deleteObject(this.targetName)
|
var targetIndex = Objects.getObjectsName(this.targetType).indexOf(this.targetName)
|
||||||
|
Objects.currentObjects[this.targetType][targetIndex].delete()
|
||||||
|
Objects.currentObjects[this.targetType].splice(targetIndex, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
redo() {
|
redo() {
|
||||||
Objects.createNewRegisteredObject(this.targetType, this.targetProperties)
|
Objects.createNewRegisteredObject(this.targetType, this.targetProperties)
|
||||||
Objects.currentObjectsByName[this.targetName].update()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export() {
|
export() {
|
||||||
|
@ -47,13 +50,11 @@ export default class CreateNewObject extends Action {
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadableString() {
|
getReadableString() {
|
||||||
return qsTranslate("create", "New %1 %2 created.")
|
return qsTr("New %1 %2 created.").arg(Objects.types[this.targetType].displayType()).arg(this.targetName)
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
|
||||||
.arg(this.targetName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLString() {
|
getHTMLString() {
|
||||||
return qsTranslate("create", "New %1 %2 created.")
|
return qsTr("New %1 %2 created.")
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
.arg(Objects.types[this.targetType].displayType())
|
||||||
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
|
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
|
||||||
}
|
}
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,14 +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/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Objects from "../module/objects.mjs"
|
.pragma library
|
||||||
import CreateNewObject from "create.mjs"
|
|
||||||
|
.import "../objects.js" as Objects
|
||||||
|
.import "create.js" as Create
|
||||||
|
|
||||||
|
|
||||||
export default class DeleteObject extends CreateNewObject {
|
class DeleteObject extends Create.CreateNewObject {
|
||||||
/**
|
// Action used at the deletion of an object. Basicly the same thing as creating a new object, except Redo & Undo are reversed.
|
||||||
* Action used at the deletion of an object. Basically the same thing as creating a new object, except Redo & Undo are reversed.
|
|
||||||
*/
|
|
||||||
type(){return 'DeleteObject'}
|
type(){return 'DeleteObject'}
|
||||||
|
|
||||||
icon(){return 'delete'}
|
icon(){return 'delete'}
|
||||||
|
@ -39,13 +39,11 @@ export default class DeleteObject extends CreateNewObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadableString() {
|
getReadableString() {
|
||||||
return qsTranslate("delete", "%1 %2 deleted.")
|
return qsTr("%1 %2 deleted.").arg(Objects.types[this.targetType].displayType()).arg(this.targetName)
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
|
||||||
.arg(this.targetName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLString() {
|
getHTMLString() {
|
||||||
return qsTranslate("delete", "%1 %2 deleted.")
|
return qsTr("%1 %2 deleted.")
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
.arg(Objects.types[this.targetType].displayType())
|
||||||
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
|
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
|
||||||
}
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
/**
|
||||||
|
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||||
|
* Copyright (C) 2022 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.pragma library
|
||||||
|
|
||||||
|
.import "../objects.js" as Objects
|
||||||
|
.import "../mathlib.js" as MathLib
|
||||||
|
.import "../objs/common.js" as Common
|
||||||
|
.import "common.js" as C
|
||||||
|
|
||||||
|
class EditedProperty extends C.Action {
|
||||||
|
// Action used everytime an object's property has been changed
|
||||||
|
type(){return 'EditedProperty'}
|
||||||
|
|
||||||
|
icon(){return 'modify'}
|
||||||
|
|
||||||
|
color(darkVer=false){
|
||||||
|
return darkVer ? 'darkslateblue' : 'cyan';
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(targetName = "", targetType = "Point", targetProperty = "visible", previousValue = false, newValue = true, valueIsExpressionNeedingImport = false) {
|
||||||
|
super(targetName, targetType)
|
||||||
|
this.targetProperty = targetProperty
|
||||||
|
this.targetPropertyReadable = qsTranslate("prop", this.targetProperty)
|
||||||
|
this.previousValue = previousValue
|
||||||
|
this.newValue = newValue
|
||||||
|
this.propertyType = Objects.types[targetType].properties()[targetProperty]
|
||||||
|
if(valueIsExpressionNeedingImport) {
|
||||||
|
if(this.propertyType == "Expression") {
|
||||||
|
this.previousValue = new MathLib.Expression(this.previousValue);
|
||||||
|
this.newValue = new MathLib.Expression(this.newValue);
|
||||||
|
} else if(this.propertyType == "Domain") {
|
||||||
|
this.previousValue = MathLib.parseDomain(this.previousValue);
|
||||||
|
this.newValue = MathLib.parseDomain(this.newValue);
|
||||||
|
} else {
|
||||||
|
// Objects
|
||||||
|
this.previousValue = Objects.getObjectByName(this.previousValue);
|
||||||
|
this.newValue = Objects.getObjectByName(this.newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setReadableValues()
|
||||||
|
}
|
||||||
|
|
||||||
|
undo() {
|
||||||
|
Objects.getObjectByName(this.targetName, this.targetType)[this.targetProperty] = this.previousValue
|
||||||
|
}
|
||||||
|
|
||||||
|
redo() {
|
||||||
|
Objects.getObjectByName(this.targetName, this.targetType)[this.targetProperty] = this.newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export() {
|
||||||
|
if(this.previousValue instanceof MathLib.Expression) {
|
||||||
|
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.toEditableString(), this.newValue.toEditableString(), true]
|
||||||
|
} else if(this.previousValue instanceof Common.DrawableObject) {
|
||||||
|
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.name, this.newValue.name, true]
|
||||||
|
} else {
|
||||||
|
return [this.targetName, this.targetType, this.targetProperty, this.previousValue, this.newValue, false]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setReadableValues() {
|
||||||
|
this.prev = "";
|
||||||
|
this.next = "";
|
||||||
|
if(this.propertyType instanceof Object) {
|
||||||
|
switch(this.propertyType.type) {
|
||||||
|
case "Enum":
|
||||||
|
this.prev = this.propertyType.translatedValues[this.propertyType.values.indexOf(this.previousValue)]
|
||||||
|
this.next = this.propertyType.translatedValues[this.propertyType.values.indexOf(this.newValue)]
|
||||||
|
break;
|
||||||
|
case "ObjectType":
|
||||||
|
this.prev = this.previousValue == null ? "null" : this.previousValue.name
|
||||||
|
this.next = this.newValue == null ? "null" : this.newValue.name
|
||||||
|
break;
|
||||||
|
case "List":
|
||||||
|
this.prev = this.previousValue.join(",")
|
||||||
|
this.next = this.newValue.name.join(",")
|
||||||
|
break;
|
||||||
|
case "Dict":
|
||||||
|
this.prev = JSON.stringify(this.previousValue).replace("'", "\\'").replace('"', "'")
|
||||||
|
this.next = JSON.stringify(this.newValue).replace("'", "\\'").replace('"', "'")
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.prev = this.previousValue == null ? "null" : this.previousValue.toString()
|
||||||
|
this.next = this.newValue == null ? "null" : this.newValue.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getReadableString() {
|
||||||
|
return qsTr('%1 of %2 %3 changed from "%4" to "%5".')
|
||||||
|
.arg(this.targetPropertyReadable)
|
||||||
|
.arg(Objects.types[this.targetType].displayType())
|
||||||
|
.arg(this.targetName).arg(this.prev).arg(this.next)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHTMLString() {
|
||||||
|
return qsTr('%1 of %2 changed from %3 to %4.')
|
||||||
|
.arg(this.targetPropertyReadable)
|
||||||
|
.arg('<b style="font-size: 15px;"> ' + this.targetName + ' </b>')
|
||||||
|
.arg('<tt style="background: rgba(128,128,128,0.1);"> '+this.prev+' </tt>')
|
||||||
|
.arg('<tt style="background: rgba(128,128,128,0.1);"> '+this.next+'</tt>')
|
||||||
|
// .arg('<b style="font-size: 15px;">' + Objects.types[this.targetType].displayType())
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,159 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Objects from "../module/objects.mjs"
|
|
||||||
import Latex from "../module/latex.mjs"
|
|
||||||
import * as MathLib from "../math/index.mjs"
|
|
||||||
import { Action } from "common.mjs"
|
|
||||||
import { DrawableObject } from "../objs/common.mjs"
|
|
||||||
|
|
||||||
export default class EditedProperty extends Action {
|
|
||||||
// Action used everytime an object's property has been changed
|
|
||||||
type(){return 'EditedProperty'}
|
|
||||||
|
|
||||||
icon(){return 'modify'}
|
|
||||||
|
|
||||||
color(darkVer=false){
|
|
||||||
return darkVer ? 'darkslateblue' : 'cyan';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} targetName - Name of the object to target
|
|
||||||
* @param {string} targetType - Type of the object to target.
|
|
||||||
* @param {string} targetProperty - Property being changed
|
|
||||||
* @param {any} previousValue - Previous value before change
|
|
||||||
* @param {any} newValue - New value after change
|
|
||||||
* @param {boolean} valueIsExpressionNeedingImport - True if the value needs to be imported. (e.g expressions)
|
|
||||||
*/
|
|
||||||
constructor(targetName = "", targetType = "Point", targetProperty = "visible", previousValue = false, newValue = true, valueIsExpressionNeedingImport = false) {
|
|
||||||
super(targetName, targetType)
|
|
||||||
this.targetProperty = targetProperty
|
|
||||||
this.targetPropertyReadable = qsTranslate("prop", this.targetProperty)
|
|
||||||
this.previousValue = previousValue
|
|
||||||
this.newValue = newValue
|
|
||||||
this.propertyType = Objects.types[targetType].properties()[targetProperty]
|
|
||||||
if(valueIsExpressionNeedingImport) {
|
|
||||||
if(typeof this.propertyType == 'object' && this.propertyType.type === "Expression") {
|
|
||||||
this.previousValue = new MathLib.Expression(this.previousValue);
|
|
||||||
this.newValue = new MathLib.Expression(this.newValue);
|
|
||||||
} else if(this.propertyType === "Domain") {
|
|
||||||
this.previousValue = MathLib.parseDomain(this.previousValue);
|
|
||||||
this.newValue = MathLib.parseDomain(this.newValue);
|
|
||||||
} else {
|
|
||||||
// Objects
|
|
||||||
this.previousValue = Objects.currentObjectsByName[this.previousValue] // Objects.getObjectByName(this.previousValue);
|
|
||||||
this.newValue = Objects.currentObjectsByName[this.newValue] // Objects.getObjectByName(this.newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setReadableValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
undo() {
|
|
||||||
Objects.currentObjectsByName[this.targetName][this.targetProperty] = this.previousValue
|
|
||||||
Objects.currentObjectsByName[this.targetName].update()
|
|
||||||
}
|
|
||||||
|
|
||||||
redo() {
|
|
||||||
Objects.currentObjectsByName[this.targetName][this.targetProperty] = this.newValue
|
|
||||||
Objects.currentObjectsByName[this.targetName].update()
|
|
||||||
}
|
|
||||||
|
|
||||||
export() {
|
|
||||||
if(this.previousValue instanceof MathLib.Expression) {
|
|
||||||
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.toEditableString(), this.newValue.toEditableString(), true]
|
|
||||||
} else if(this.previousValue instanceof DrawableObject) {
|
|
||||||
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.name, this.newValue.name, true]
|
|
||||||
} else {
|
|
||||||
return [this.targetName, this.targetType, this.targetProperty, this.previousValue, this.newValue, false]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setReadableValues() {
|
|
||||||
this.prevString = ""
|
|
||||||
this.nextString = ""
|
|
||||||
this._renderPromises = []
|
|
||||||
if(this.propertyType instanceof Object) {
|
|
||||||
switch(this.propertyType.type) {
|
|
||||||
case "Enum":
|
|
||||||
this.prevString = this.propertyType.translatedValues[this.propertyType.values.indexOf(this.previousValue)]
|
|
||||||
this.nextString = this.propertyType.translatedValues[this.propertyType.values.indexOf(this.newValue)]
|
|
||||||
break;
|
|
||||||
case "ObjectType":
|
|
||||||
this.prevString = this.previousValue == null ? "null" : this.previousValue.name
|
|
||||||
this.nextString = this.newValue == null ? "null" : this.newValue.name
|
|
||||||
break;
|
|
||||||
case "List":
|
|
||||||
this.prevString = this.previousValue.join(",")
|
|
||||||
this.nextString = this.newValue.name.join(",")
|
|
||||||
break;
|
|
||||||
case "Dict":
|
|
||||||
this.prevString = JSON.stringify(this.previousValue)
|
|
||||||
this.nextString = JSON.stringify(this.newValue)
|
|
||||||
break;
|
|
||||||
case "Expression":
|
|
||||||
this.prevString = this.previousValue == null ? "null" : this.previousValue.toString()
|
|
||||||
this.nextString = this.newValue == null ? "null" : this.newValue.toString()
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.prevString = this.previousValue == null ? "null" : this.previousValue.toString()
|
|
||||||
this.nextString = this.newValue == null ? "null" : this.newValue.toString()
|
|
||||||
}
|
|
||||||
// HTML
|
|
||||||
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+this.prevString+' </tt>'
|
|
||||||
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+this.nextString+' </tt>'
|
|
||||||
if(Latex.enabled && typeof this.propertyType == 'object' && this.propertyType.type === "Expression") {
|
|
||||||
// Store promises so that querying can wait for them to finish.
|
|
||||||
this._renderPromises = [
|
|
||||||
this.renderLatexAsHtml(this.previousValue.latexMarkup).then(prev => this.prevHTML = prev),
|
|
||||||
this.renderLatexAsHtml(this.newValue.latexMarkup).then(next => this.nextHTML = prev)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getReadableString() {
|
|
||||||
return qsTranslate("editproperty", '%1 of %2 %3 changed from "%4" to "%5".')
|
|
||||||
.arg(this.targetPropertyReadable)
|
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
|
||||||
.arg(this.targetName).arg(this.prevString).arg(this.nextString)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return {Promise<string>|string}
|
|
||||||
*/
|
|
||||||
getHTMLString() {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const translation = qsTranslate("editproperty", '%1 of %2 changed from %3 to %4.')
|
|
||||||
.arg(this.targetPropertyReadable)
|
|
||||||
.arg('<b style="font-size: 15px;"> ' + this.targetName + ' </b>')
|
|
||||||
// Check if we need to wait for LaTeX HTML to be rendered.
|
|
||||||
if(this.prevHTML !== undefined && this.nextHTML !== undefined)
|
|
||||||
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
|
|
||||||
else
|
|
||||||
Promise.all(this._renderPromises).then((rendered) => {
|
|
||||||
// Rendered are (potentially) two HTML strings which are defined during rendering
|
|
||||||
this.prevHTML = this.prevHTML ?? rendered[0]
|
|
||||||
this.nextHTML = this.prevHTML ?? rendered[1]
|
|
||||||
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,16 +16,19 @@
|
||||||
* 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 EditedProperty from "editproperty.mjs"
|
.pragma library
|
||||||
import Objects from "../module/objects.mjs"
|
|
||||||
|
.import "editproperty.js" as EP
|
||||||
|
.import "../objects.js" as Objects
|
||||||
|
|
||||||
|
|
||||||
export default class NameChanged extends EditedProperty {
|
class NameChanged extends EP.EditedProperty {
|
||||||
// Action used everytime an object's property has been changed
|
// Action used everytime an object's property has been changed
|
||||||
type(){return 'NameChanged'}
|
type(){return 'NameChanged'}
|
||||||
|
|
||||||
icon(){return 'name'}
|
icon(){return 'name'}
|
||||||
|
|
||||||
|
|
||||||
color(darkVer=false){return darkVer ? 'darkorange' : 'orange'}
|
color(darkVer=false){return darkVer ? 'darkorange' : 'orange'}
|
||||||
|
|
||||||
constructor(targetName = "", targetType = "Point", newName = "") {
|
constructor(targetName = "", targetType = "Point", newName = "") {
|
||||||
|
@ -37,21 +40,21 @@ export default class NameChanged extends EditedProperty {
|
||||||
}
|
}
|
||||||
|
|
||||||
undo() {
|
undo() {
|
||||||
Objects.renameObject(this.newValue, this.previousValue)
|
Objects.getObjectByName(this.newValue, this.targetType)['name'] = this.previousValue
|
||||||
}
|
}
|
||||||
|
|
||||||
redo() {
|
redo() {
|
||||||
Objects.renameObject(this.previousValue, this.newValue)
|
Objects.getObjectByName(this.previousValue, this.targetType)['name'] = this.newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadableString() {
|
getReadableString() {
|
||||||
return qsTranslate("name", '%1 %2 renamed to %3.')
|
return qsTr('%1 %2 renamed to %3.')
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
.arg(Objects.types[this.targetType].displayType())
|
||||||
.arg(this.targetName).arg(this.newValue)
|
.arg(this.targetName).arg(this.newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLString() {
|
getHTMLString() {
|
||||||
return qsTranslate("name", '%1 %2 renamed to %3.')
|
return qsTr('%1 %2 renamed to %3.')
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
.arg(Objects.types[this.targetType].displayType())
|
||||||
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>").arg('<b>'+this.newValue+'</b>')
|
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>").arg('<b>'+this.newValue+'</b>')
|
||||||
}
|
}
|
|
@ -1,109 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Objects from "../module/objects.mjs"
|
|
||||||
import Latex from "../module/latex.mjs"
|
|
||||||
import * as MathLib from "../math/index.mjs"
|
|
||||||
import { escapeHTML } from "../utils.mjs"
|
|
||||||
import { Action } from "common.mjs"
|
|
||||||
import { DrawableObject } from "../objs/common.mjs"
|
|
||||||
|
|
||||||
export default class EditedPosition extends Action {
|
|
||||||
// Action used for objects that have a X and Y expression properties (points, texts...)
|
|
||||||
type(){return 'EditedPosition'}
|
|
||||||
|
|
||||||
icon(){return 'position'}
|
|
||||||
|
|
||||||
color(darkVer=false){
|
|
||||||
return darkVer ? 'seagreen' : 'lightseagreen';
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(targetName = "", targetType = "Point", previousXValue = "", newXValue = "", previousYValue = "", newYValue = "") {
|
|
||||||
super(targetName, targetType)
|
|
||||||
let imports = {
|
|
||||||
'previousXValue': previousXValue,
|
|
||||||
'previousYValue': previousYValue,
|
|
||||||
'newXValue': newXValue,
|
|
||||||
'newYValue': newYValue
|
|
||||||
}
|
|
||||||
for(let name in imports)
|
|
||||||
this[name] = (typeof imports[name]) == 'string' ? new MathLib.Expression(imports[name]) : imports[name]
|
|
||||||
this.setReadableValues()
|
|
||||||
}
|
|
||||||
|
|
||||||
undo() {
|
|
||||||
Objects.currentObjectsByName[this.targetName].x = this.previousXValue
|
|
||||||
Objects.currentObjectsByName[this.targetName].y = this.previousYValue
|
|
||||||
Objects.currentObjectsByName[this.targetName].update()
|
|
||||||
}
|
|
||||||
|
|
||||||
redo() {
|
|
||||||
Objects.currentObjectsByName[this.targetName].x = this.newXValue
|
|
||||||
Objects.currentObjectsByName[this.targetName].y = this.newYValue
|
|
||||||
Objects.currentObjectsByName[this.targetName].update()
|
|
||||||
}
|
|
||||||
|
|
||||||
setReadableValues() {
|
|
||||||
this.prevString = `(${this.previousXValue.toString()},${this.previousYValue.toString()})`
|
|
||||||
this.nextString = `(${this.newXValue.toString()},${this.newYValue.toString()})`
|
|
||||||
this._renderPromises = []
|
|
||||||
// Render as LaTeX
|
|
||||||
if(Latex.enabled) {
|
|
||||||
const prevMarkup = `\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`
|
|
||||||
const nextMarkup = `\\left(${this.newXValue.latexMarkup},${this.newYValue.latexMarkup}\\right)`
|
|
||||||
this._renderPromises = [ // Will be taken in promise.all
|
|
||||||
this.renderLatexAsHtml(prevMarkup),
|
|
||||||
this.renderLatexAsHtml(nextMarkup)
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+escapeHTML(this.prevString)+' </tt>'
|
|
||||||
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);"> '+escapeHTML(this.nextString)+' </tt>'
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export() {
|
|
||||||
return [this.targetName, this.targetType,
|
|
||||||
this.previousXValue.toEditableString(), this.newXValue.toEditableString(),
|
|
||||||
this.previousYValue.toEditableString(), this.newYValue.toEditableString()]
|
|
||||||
}
|
|
||||||
|
|
||||||
getReadableString() {
|
|
||||||
return qsTranslate("position", 'Position of %1 %2 set from "%3" to "%4".')
|
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
|
||||||
.arg(this.targetName).arg(this.prevString).arg(this.nextString)
|
|
||||||
}
|
|
||||||
|
|
||||||
getHTMLString() {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const translation = qsTranslate("position", 'Position of %1 set from %2 to %3.')
|
|
||||||
.arg('<b style="font-size: 15px;"> ' + this.targetName + ' </b>')
|
|
||||||
// Check if we need to wait for LaTeX HTML to be rendered.
|
|
||||||
if(this.prevHTML !== undefined && this.nextHTML !== undefined)
|
|
||||||
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
|
|
||||||
else
|
|
||||||
Promise.all(this._renderPromises).then((rendered) => {
|
|
||||||
// Rendered are (potentially) two HTML strings which are defined during rendering
|
|
||||||
this.prevHTML = this.prevHTML ?? rendered[0]
|
|
||||||
this.nextHTML = this.nextHTML ?? rendered[1]
|
|
||||||
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,13 @@
|
||||||
* 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 EditedProperty from "editproperty.mjs"
|
.pragma library
|
||||||
import Objects from "../module/objects.mjs"
|
|
||||||
|
.import "editproperty.js" as EP
|
||||||
|
.import "../objects.js" as Objects
|
||||||
|
|
||||||
|
|
||||||
export default class EditedVisibility extends EditedProperty {
|
class EditedVisibility extends EP.EditedProperty {
|
||||||
// Action used when an object's shown or hidden.
|
// Action used when an object's shown or hidden.
|
||||||
type(){return 'EditedVisibility'}
|
type(){return 'EditedVisibility'}
|
||||||
|
|
||||||
|
@ -41,13 +43,13 @@ export default class EditedVisibility extends EditedProperty {
|
||||||
}
|
}
|
||||||
|
|
||||||
getReadableString() {
|
getReadableString() {
|
||||||
return (this.newValue ? qsTranslate('visibility', '%1 %2 shown.') : qsTranslate('visibility', '%1 %2 hidden.'))
|
return (this.newValue ? qsTr('%1 %2 shown.') : qsTr('%1 %2 hidden.'))
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
.arg(Objects.types[this.targetType].displayType())
|
||||||
.arg(this.targetName)
|
.arg(this.targetName)
|
||||||
}
|
}
|
||||||
|
|
||||||
getHTMLString() {
|
getHTMLString() {
|
||||||
return (this.newValue ? qsTranslate('visibility', '%1 %2 shown.') : qsTranslate('visibility', '%1 %2 hidden.'))
|
return (this.newValue ? qsTr('%1 %2 shown.') : qsTr('%1 %2 hidden.'))
|
||||||
.arg(Objects.types[this.targetType].displayType())
|
.arg(Objects.types[this.targetType].displayType())
|
||||||
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
|
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
|
||||||
}
|
}
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,32 +18,32 @@
|
||||||
|
|
||||||
// This library helps containing actions to be undone or redone (in other words, editing history)
|
// This library helps containing actions to be undone or redone (in other words, editing history)
|
||||||
// Each type of event is repertoried as an action that can be listed for everything that's undoable.
|
// Each type of event is repertoried as an action that can be listed for everything that's undoable.
|
||||||
|
.pragma library
|
||||||
|
|
||||||
import { Action as A } from "./common.mjs"
|
.import "history/common.js" as Common
|
||||||
import Create from "./create.mjs"
|
.import "history/create.js" as Create
|
||||||
import Delete from "./delete.mjs"
|
.import "history/delete.js" as Delete
|
||||||
import EP from "./editproperty.mjs"
|
.import "history/editproperty.js" as EP
|
||||||
import Pos from "./position.mjs"
|
.import "history/visibility.js" as V
|
||||||
import V from "./visibility.mjs"
|
.import "history/name.js" as Name
|
||||||
import Name from "./name.mjs"
|
.import "history/color.js" as Color
|
||||||
import Color from "./color.mjs"
|
|
||||||
|
var history = null;
|
||||||
|
|
||||||
|
|
||||||
export const Action = A
|
var Action = Common.Action
|
||||||
export const CreateNewObject = Create
|
var CreateNewObject = Create.CreateNewObject
|
||||||
export const DeleteObject = Delete
|
var DeleteObject = Delete.DeleteObject
|
||||||
export const EditedProperty = EP
|
var EditedProperty = EP.EditedProperty
|
||||||
export const EditedPosition = Pos
|
var EditedVisibility = V.EditedVisibility
|
||||||
export const EditedVisibility = V
|
var NameChanged = Name.NameChanged
|
||||||
export const NameChanged = Name
|
var ColorChanged = Color.ColorChanged
|
||||||
export const ColorChanged = Color
|
|
||||||
|
|
||||||
export const Actions = {
|
var Actions = {
|
||||||
"Action": Action,
|
"Action": Action,
|
||||||
"CreateNewObject": CreateNewObject,
|
"CreateNewObject": CreateNewObject,
|
||||||
"DeleteObject": DeleteObject,
|
"DeleteObject": DeleteObject,
|
||||||
"EditedProperty": EditedProperty,
|
"EditedProperty": EditedProperty,
|
||||||
"EditedPosition": EditedPosition,
|
|
||||||
"EditedVisibility": EditedVisibility,
|
"EditedVisibility": EditedVisibility,
|
||||||
"NameChanged": NameChanged,
|
"NameChanged": NameChanged,
|
||||||
"ColorChanged": ColorChanged,
|
"ColorChanged": ColorChanged,
|
|
@ -1,540 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
|
|
||||||
* http://www.undefined.ch/mparser/index.html
|
|
||||||
*
|
|
||||||
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
|
|
||||||
* https://silentmatt.com/javascript-expression-evaluator/
|
|
||||||
*
|
|
||||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*
|
|
||||||
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
|
|
||||||
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
|
|
||||||
* but don't feel like you have to let me know or ask permission.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
Instruction,
|
|
||||||
IOP3, IOP2, IOP1,
|
|
||||||
INUMBER, IARRAY,
|
|
||||||
IVAR, IVARNAME,
|
|
||||||
IEXPR, IEXPREVAL,
|
|
||||||
IMEMBER, IFUNCALL,
|
|
||||||
IENDSTATEMENT,
|
|
||||||
unaryInstruction, binaryInstruction, ternaryInstruction
|
|
||||||
} from "./instruction.mjs"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplifies the given instructions
|
|
||||||
* @param {Instruction[]} tokens
|
|
||||||
* @param {Record.<string, function(any): any>} unaryOps
|
|
||||||
* @param {Record.<string, function(any, any): any>} binaryOps
|
|
||||||
* @param {Record.<string, function(any, any, any): any>} ternaryOps
|
|
||||||
* @param {Record.<string, any>} values
|
|
||||||
* @return {Instruction[]}
|
|
||||||
*/
|
|
||||||
function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
|
|
||||||
const nstack = []
|
|
||||||
const newexpression = []
|
|
||||||
let n1, n2, n3
|
|
||||||
let f
|
|
||||||
for(let i = 0; i < tokens.length; i++) {
|
|
||||||
let item = tokens[i]
|
|
||||||
const type = item.type
|
|
||||||
if(type === INUMBER || type === IVARNAME) {
|
|
||||||
if(Array.isArray(item.value)) {
|
|
||||||
nstack.push.apply(nstack, simplify(item.value.map(function(x) {
|
|
||||||
return new Instruction(INUMBER, x)
|
|
||||||
}).concat(new Instruction(IARRAY, item.value.length)), unaryOps, binaryOps, ternaryOps, values))
|
|
||||||
} else {
|
|
||||||
nstack.push(item)
|
|
||||||
}
|
|
||||||
} else if(type === IVAR && values.hasOwnProperty(item.value)) {
|
|
||||||
item = new Instruction(INUMBER, values[item.value])
|
|
||||||
nstack.push(item)
|
|
||||||
} else if(type === IOP2 && nstack.length > 1) {
|
|
||||||
n2 = nstack.pop()
|
|
||||||
n1 = nstack.pop()
|
|
||||||
f = binaryOps[item.value]
|
|
||||||
item = new Instruction(INUMBER, f(n1.value, n2.value))
|
|
||||||
nstack.push(item)
|
|
||||||
} else if(type === IOP3 && nstack.length > 2) {
|
|
||||||
n3 = nstack.pop()
|
|
||||||
n2 = nstack.pop()
|
|
||||||
n1 = nstack.pop()
|
|
||||||
if(item.value === "?") {
|
|
||||||
nstack.push(n1.value ? n2.value : n3.value)
|
|
||||||
} else {
|
|
||||||
f = ternaryOps[item.value]
|
|
||||||
item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value))
|
|
||||||
nstack.push(item)
|
|
||||||
}
|
|
||||||
} else if(type === IOP1 && nstack.length > 0) {
|
|
||||||
n1 = nstack.pop()
|
|
||||||
f = unaryOps[item.value]
|
|
||||||
item = new Instruction(INUMBER, f(n1.value))
|
|
||||||
nstack.push(item)
|
|
||||||
} else if(type === IEXPR) {
|
|
||||||
while(nstack.length > 0) {
|
|
||||||
newexpression.push(nstack.shift())
|
|
||||||
}
|
|
||||||
newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values)))
|
|
||||||
} else if(type === IMEMBER && nstack.length > 0) {
|
|
||||||
n1 = nstack.pop()
|
|
||||||
if(item.value in n1.value)
|
|
||||||
nstack.push(new Instruction(INUMBER, n1.value[item.value]))
|
|
||||||
else
|
|
||||||
throw new Error(qsTranslate("error", "Cannot find property %1 of object %2.").arg(item.value).arg(n1))
|
|
||||||
} else {
|
|
||||||
while(nstack.length > 0) {
|
|
||||||
newexpression.push(nstack.shift())
|
|
||||||
}
|
|
||||||
newexpression.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while(nstack.length > 0) {
|
|
||||||
newexpression.push(nstack.shift())
|
|
||||||
}
|
|
||||||
return newexpression
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In the given instructions, replaces variable by expr.
|
|
||||||
* @param {Instruction[]} tokens
|
|
||||||
* @param {string} variable
|
|
||||||
* @param {number} expr
|
|
||||||
* @return {Instruction[]}
|
|
||||||
*/
|
|
||||||
function substitute(tokens, variable, expr) {
|
|
||||||
const newexpression = []
|
|
||||||
for(let i = 0; i < tokens.length; i++) {
|
|
||||||
let item = tokens[i]
|
|
||||||
const type = item.type
|
|
||||||
if(type === IVAR && item.value === variable) {
|
|
||||||
for(let j = 0; j < expr.tokens.length; j++) {
|
|
||||||
const expritem = expr.tokens[j]
|
|
||||||
let replitem
|
|
||||||
if(expritem.type === IOP1) {
|
|
||||||
replitem = unaryInstruction(expritem.value)
|
|
||||||
} else if(expritem.type === IOP2) {
|
|
||||||
replitem = binaryInstruction(expritem.value)
|
|
||||||
} else if(expritem.type === IOP3) {
|
|
||||||
replitem = ternaryInstruction(expritem.value)
|
|
||||||
} else {
|
|
||||||
replitem = new Instruction(expritem.type, expritem.value)
|
|
||||||
}
|
|
||||||
newexpression.push(replitem)
|
|
||||||
}
|
|
||||||
} else if(type === IEXPR) {
|
|
||||||
newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr)))
|
|
||||||
} else {
|
|
||||||
newexpression.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newexpression
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Evaluates the given instructions for a given Expression with given values.
|
|
||||||
* @param {Instruction[]} tokens
|
|
||||||
* @param {ExprEvalExpression} expr
|
|
||||||
* @param {Record.<string, number>} values
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
function evaluate(tokens, expr, values) {
|
|
||||||
const nstack = []
|
|
||||||
let n1, n2, n3
|
|
||||||
let f, args, argCount
|
|
||||||
|
|
||||||
if(isExpressionEvaluator(tokens)) {
|
|
||||||
return resolveExpression(tokens, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let i = 0; i < tokens.length; i++) {
|
|
||||||
const item = tokens[i]
|
|
||||||
const type = item.type
|
|
||||||
if(type === INUMBER || type === IVARNAME) {
|
|
||||||
nstack.push(item.value)
|
|
||||||
} else if(type === IOP2) {
|
|
||||||
n2 = nstack.pop()
|
|
||||||
n1 = nstack.pop()
|
|
||||||
if(item.value === "and") {
|
|
||||||
nstack.push(n1 ? !!evaluate(n2, expr, values) : false)
|
|
||||||
} else if(item.value === "or") {
|
|
||||||
nstack.push(n1 ? true : !!evaluate(n2, expr, values))
|
|
||||||
} else if(item.value === "=") {
|
|
||||||
f = expr.binaryOps[item.value]
|
|
||||||
nstack.push(f(n1, evaluate(n2, expr, values), values))
|
|
||||||
} else {
|
|
||||||
f = expr.binaryOps[item.value]
|
|
||||||
nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values)))
|
|
||||||
}
|
|
||||||
} else if(type === IOP3) {
|
|
||||||
n3 = nstack.pop()
|
|
||||||
n2 = nstack.pop()
|
|
||||||
n1 = nstack.pop()
|
|
||||||
if(item.value === "?") {
|
|
||||||
nstack.push(evaluate(n1 ? n2 : n3, expr, values))
|
|
||||||
} else {
|
|
||||||
f = expr.ternaryOps[item.value]
|
|
||||||
nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values), resolveExpression(n3, values)))
|
|
||||||
}
|
|
||||||
} else if(type === IVAR) {
|
|
||||||
// Check for variable value
|
|
||||||
if(/^__proto__|prototype|constructor$/.test(item.value)) {
|
|
||||||
throw new Error("WARNING: Prototype access detected and denied. If you downloaded this file from the internet, this file might be a virus.")
|
|
||||||
} else if(item.value in expr.functions) {
|
|
||||||
nstack.push(expr.functions[item.value])
|
|
||||||
} else if(item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) {
|
|
||||||
nstack.push(expr.unaryOps[item.value])
|
|
||||||
} else {
|
|
||||||
const v = values[item.value]
|
|
||||||
if(v !== undefined) {
|
|
||||||
nstack.push(v)
|
|
||||||
} else {
|
|
||||||
throw new Error(qsTranslate("error", "Undefined variable %1.").arg(item.value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(type === IOP1) {
|
|
||||||
n1 = nstack.pop()
|
|
||||||
f = expr.unaryOps[item.value]
|
|
||||||
nstack.push(f(resolveExpression(n1, values)))
|
|
||||||
} else if(type === IFUNCALL) {
|
|
||||||
argCount = item.value
|
|
||||||
args = []
|
|
||||||
while(argCount-- > 0) {
|
|
||||||
args.unshift(resolveExpression(nstack.pop(), values))
|
|
||||||
}
|
|
||||||
f = nstack.pop()
|
|
||||||
if(f.apply && f.call) {
|
|
||||||
nstack.push(f.apply(undefined, args))
|
|
||||||
} else if(f.execute) {
|
|
||||||
// Objects & expressions execution
|
|
||||||
if(args.length >= 1)
|
|
||||||
nstack.push(f.execute.apply(f, args))
|
|
||||||
else
|
|
||||||
throw new Error(qsTranslate("error", "In order to be executed, object %1 must have at least one argument.").arg(f))
|
|
||||||
} else {
|
|
||||||
throw new Error(qsTranslate("error", "%1 cannot be executed.").arg(f))
|
|
||||||
}
|
|
||||||
} else if(type === IEXPR) {
|
|
||||||
nstack.push(createExpressionEvaluator(item, expr))
|
|
||||||
} else if(type === IEXPREVAL) {
|
|
||||||
nstack.push(item)
|
|
||||||
} else if(type === IMEMBER) {
|
|
||||||
n1 = nstack.pop()
|
|
||||||
if(item.value in n1)
|
|
||||||
if(n1[item.value].execute && n1[item.value].cached)
|
|
||||||
nstack.push(n1[item.value].execute())
|
|
||||||
else
|
|
||||||
nstack.push(n1[item.value])
|
|
||||||
else
|
|
||||||
throw new Error(qsTranslate("error", "Cannot find property %1 of object %2.").arg(item.value).arg(n1))
|
|
||||||
} else if(type === IENDSTATEMENT) {
|
|
||||||
nstack.pop()
|
|
||||||
} else if(type === IARRAY) {
|
|
||||||
argCount = item.value
|
|
||||||
args = []
|
|
||||||
while(argCount-- > 0) {
|
|
||||||
args.unshift(nstack.pop())
|
|
||||||
}
|
|
||||||
nstack.push(args)
|
|
||||||
} else {
|
|
||||||
throw new Error(qsTranslate("error", "Invalid expression."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(nstack.length > 1) {
|
|
||||||
throw new Error(qsTranslate("error", "Invalid expression (parity)."))
|
|
||||||
}
|
|
||||||
// Explicitly return zero to avoid test issues caused by -0
|
|
||||||
return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values)
|
|
||||||
}
|
|
||||||
|
|
||||||
function createExpressionEvaluator(token, expr) {
|
|
||||||
if(isExpressionEvaluator(token)) return token
|
|
||||||
return {
|
|
||||||
type: IEXPREVAL,
|
|
||||||
value: function(scope) {
|
|
||||||
return evaluate(token.value, expr, scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isExpressionEvaluator(n) {
|
|
||||||
return n && n.type === IEXPREVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveExpression(n, values) {
|
|
||||||
return isExpressionEvaluator(n) ? n.value(values) : n
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given instructions to a string
|
|
||||||
* If toJS is active, can be evaluated with eval, otherwise it can be reparsed by the parser.
|
|
||||||
* @param {Instruction[]} tokens
|
|
||||||
* @param {boolean} toJS
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function expressionToString(tokens, toJS) {
|
|
||||||
let nstack = []
|
|
||||||
let n1, n2, n3
|
|
||||||
let f, args, argCount
|
|
||||||
for(let i = 0; i < tokens.length; i++) {
|
|
||||||
const item = tokens[i]
|
|
||||||
const type = item.type
|
|
||||||
if(type === INUMBER) {
|
|
||||||
if(typeof item.value === "number" && item.value < 0) {
|
|
||||||
nstack.push("(" + item.value + ")")
|
|
||||||
} else if(Array.isArray(item.value)) {
|
|
||||||
nstack.push("[" + item.value.map(escapeValue).join(", ") + "]")
|
|
||||||
} else {
|
|
||||||
nstack.push(escapeValue(item.value))
|
|
||||||
}
|
|
||||||
} else if(type === IOP2) {
|
|
||||||
n2 = nstack.pop()
|
|
||||||
n1 = nstack.pop()
|
|
||||||
f = item.value
|
|
||||||
if(toJS) {
|
|
||||||
if(f === "^") {
|
|
||||||
nstack.push("Math.pow(" + n1 + ", " + n2 + ")")
|
|
||||||
} else if(f === "and") {
|
|
||||||
nstack.push("(!!" + n1 + " && !!" + n2 + ")")
|
|
||||||
} else if(f === "or") {
|
|
||||||
nstack.push("(!!" + n1 + " || !!" + n2 + ")")
|
|
||||||
} else if(f === "||") {
|
|
||||||
nstack.push("(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((" + n1 + "),(" + n2 + ")))")
|
|
||||||
} else if(f === "==") {
|
|
||||||
nstack.push("(" + n1 + " === " + n2 + ")")
|
|
||||||
} else if(f === "!=") {
|
|
||||||
nstack.push("(" + n1 + " !== " + n2 + ")")
|
|
||||||
} else if(f === "[") {
|
|
||||||
nstack.push(n1 + "[(" + n2 + ") | 0]")
|
|
||||||
} else {
|
|
||||||
nstack.push("(" + n1 + " " + f + " " + n2 + ")")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(f === "[") {
|
|
||||||
nstack.push(n1 + "[" + n2 + "]")
|
|
||||||
} else {
|
|
||||||
nstack.push("(" + n1 + " " + f + " " + n2 + ")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(type === IOP3) {
|
|
||||||
n3 = nstack.pop()
|
|
||||||
n2 = nstack.pop()
|
|
||||||
n1 = nstack.pop()
|
|
||||||
f = item.value
|
|
||||||
if(f === "?") {
|
|
||||||
nstack.push("(" + n1 + " ? " + n2 + " : " + n3 + ")")
|
|
||||||
} else {
|
|
||||||
throw new Error(qsTranslate("error", "Invalid expression."))
|
|
||||||
}
|
|
||||||
} else if(type === IVAR || type === IVARNAME) {
|
|
||||||
nstack.push(item.value)
|
|
||||||
} else if(type === IOP1) {
|
|
||||||
n1 = nstack.pop()
|
|
||||||
f = item.value
|
|
||||||
if(f === "-" || f === "+") {
|
|
||||||
nstack.push("(" + f + n1 + ")")
|
|
||||||
} else if(toJS) {
|
|
||||||
if(f === "not") {
|
|
||||||
nstack.push("(" + "!" + n1 + ")")
|
|
||||||
} else if(f === "!") {
|
|
||||||
nstack.push("fac(" + n1 + ")")
|
|
||||||
} else {
|
|
||||||
nstack.push(f + "(" + n1 + ")")
|
|
||||||
}
|
|
||||||
} else if(f === "!") {
|
|
||||||
nstack.push("(" + n1 + "!)")
|
|
||||||
} else {
|
|
||||||
nstack.push("(" + f + " " + n1 + ")")
|
|
||||||
}
|
|
||||||
} else if(type === IFUNCALL) {
|
|
||||||
argCount = item.value
|
|
||||||
args = []
|
|
||||||
while(argCount-- > 0) {
|
|
||||||
args.unshift(nstack.pop())
|
|
||||||
}
|
|
||||||
f = nstack.pop()
|
|
||||||
nstack.push(f + "(" + args.join(", ") + ")")
|
|
||||||
} else if(type === IMEMBER) {
|
|
||||||
n1 = nstack.pop()
|
|
||||||
nstack.push(n1 + "." + item.value)
|
|
||||||
} else if(type === IARRAY) {
|
|
||||||
argCount = item.value
|
|
||||||
args = []
|
|
||||||
while(argCount-- > 0) {
|
|
||||||
args.unshift(nstack.pop())
|
|
||||||
}
|
|
||||||
nstack.push("[" + args.join(", ") + "]")
|
|
||||||
} else if(type === IEXPR) {
|
|
||||||
nstack.push("(" + expressionToString(item.value, toJS) + ")")
|
|
||||||
} else if(type === IENDSTATEMENT) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new Error(qsTranslate("error", "Invalid expression."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(nstack.length > 1) {
|
|
||||||
if(toJS) {
|
|
||||||
nstack = [nstack.join(",")]
|
|
||||||
} else {
|
|
||||||
nstack = [nstack.join(";")]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return String(nstack[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
export function escapeValue(v) {
|
|
||||||
if(typeof v === "string") {
|
|
||||||
return JSON.stringify(v).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes all symbols from tokens into the symbols array.
|
|
||||||
* @param {Instruction[]} tokens
|
|
||||||
* @param {string[]} symbols
|
|
||||||
* @param {{withMembers: (boolean|undefined)}}options
|
|
||||||
*/
|
|
||||||
function getSymbols(tokens, symbols, options) {
|
|
||||||
options = options || {}
|
|
||||||
const withMembers = !!options.withMembers
|
|
||||||
let prevVar = null
|
|
||||||
|
|
||||||
for(let i = 0; i < tokens.length; i++) {
|
|
||||||
const item = tokens[i]
|
|
||||||
if(item.type === IVAR || item.type === IVARNAME) {
|
|
||||||
if(!withMembers && !symbols.includes(item.value)) {
|
|
||||||
symbols.push(item.value)
|
|
||||||
} else if(prevVar !== null) {
|
|
||||||
if(!symbols.includes(prevVar)) {
|
|
||||||
symbols.push(prevVar)
|
|
||||||
}
|
|
||||||
prevVar = item.value
|
|
||||||
} else {
|
|
||||||
prevVar = item.value
|
|
||||||
}
|
|
||||||
} else if(item.type === IMEMBER && withMembers && prevVar !== null) {
|
|
||||||
prevVar += "." + item.value
|
|
||||||
} else if(item.type === IEXPR) {
|
|
||||||
getSymbols(item.value, symbols, options)
|
|
||||||
} else if(prevVar !== null) {
|
|
||||||
if(!symbols.includes(prevVar)) {
|
|
||||||
symbols.push(prevVar)
|
|
||||||
}
|
|
||||||
prevVar = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(prevVar !== null && !symbols.includes(prevVar)) {
|
|
||||||
symbols.push(prevVar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ExprEvalExpression {
|
|
||||||
/**
|
|
||||||
* @param {Instruction[]} tokens
|
|
||||||
* @param {Parser} parser
|
|
||||||
*/
|
|
||||||
constructor(tokens, parser) {
|
|
||||||
this.tokens = tokens
|
|
||||||
this.parser = parser
|
|
||||||
this.unaryOps = parser.unaryOps
|
|
||||||
this.binaryOps = parser.binaryOps
|
|
||||||
this.ternaryOps = parser.ternaryOps
|
|
||||||
this.functions = parser.functions
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simplifies the expression.
|
|
||||||
* @param {Object<string, number|ExprEvalExpression>|undefined} values
|
|
||||||
* @returns {ExprEvalExpression}
|
|
||||||
*/
|
|
||||||
simplify(values) {
|
|
||||||
values = values || {}
|
|
||||||
return new ExprEvalExpression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new expression where the variable is substituted by the given expression.
|
|
||||||
* @param {string} variable
|
|
||||||
* @param {string|ExprEvalExpression} expr
|
|
||||||
* @returns {ExprEvalExpression}
|
|
||||||
*/
|
|
||||||
substitute(variable, expr) {
|
|
||||||
if(!(expr instanceof ExprEvalExpression)) {
|
|
||||||
expr = this.parser.parse(String(expr))
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ExprEvalExpression(substitute(this.tokens, variable, expr), this.parser)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the value of the expression by giving all variables and their corresponding values.
|
|
||||||
* @param {Object<string, number>} values
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
evaluate(values) {
|
|
||||||
values = Object.assign({}, values, this.parser.consts)
|
|
||||||
return evaluate(this.tokens, this, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of symbols (string of characters) in the expressions.
|
|
||||||
* Can be functions, constants, or variables.
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
symbols(options) {
|
|
||||||
options = options || {}
|
|
||||||
const vars = []
|
|
||||||
getSymbols(this.tokens, vars, options)
|
|
||||||
return vars
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return expressionToString(this.tokens, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the list of symbols (string of characters) which are not defined
|
|
||||||
* as constants or functions.
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
variables(options) {
|
|
||||||
options = options || {}
|
|
||||||
const vars = []
|
|
||||||
getSymbols(this.tokens, vars, options)
|
|
||||||
const functions = this.functions
|
|
||||||
const consts = this.parser.consts
|
|
||||||
return vars.filter((name) => {
|
|
||||||
return !(name in functions) && !(name in consts)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the expression to a JS function.
|
|
||||||
* @param {string} param - Parsed variables for the function.
|
|
||||||
* @param {Object.<string, (ExprEvalExpression|string)>} variables - Default variables to provide.
|
|
||||||
* @returns {function(...any)}
|
|
||||||
*/
|
|
||||||
toJSFunction(param, variables) {
|
|
||||||
const expr = this
|
|
||||||
const f = new Function(param, "with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return " + expressionToString(this.simplify(variables).tokens, true) + "; }") // eslint-disable-line no-new-func
|
|
||||||
return function() {
|
|
||||||
return f.apply(expr, arguments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
|
|
||||||
* http://www.undefined.ch/mparser/index.html
|
|
||||||
*
|
|
||||||
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
|
|
||||||
* https://silentmatt.com/javascript-expression-evaluator/
|
|
||||||
*
|
|
||||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*
|
|
||||||
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
|
|
||||||
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
|
|
||||||
* but don't feel like you have to let me know or ask permission.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const INUMBER = "INUMBER"
|
|
||||||
export const IOP1 = "IOP1"
|
|
||||||
export const IOP2 = "IOP2"
|
|
||||||
export const IOP3 = "IOP3"
|
|
||||||
export const IVAR = "IVAR"
|
|
||||||
export const IVARNAME = "IVARNAME"
|
|
||||||
export const IFUNCALL = "IFUNCALL"
|
|
||||||
export const IEXPR = "IEXPR"
|
|
||||||
export const IEXPREVAL = "IEXPREVAL"
|
|
||||||
export const IMEMBER = "IMEMBER"
|
|
||||||
export const IENDSTATEMENT = "IENDSTATEMENT"
|
|
||||||
export const IARRAY = "IARRAY"
|
|
||||||
|
|
||||||
|
|
||||||
export class Instruction {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} type
|
|
||||||
* @param {any} value
|
|
||||||
*/
|
|
||||||
constructor(type, value) {
|
|
||||||
this.type = type
|
|
||||||
this.value = (value !== undefined && value !== null) ? value : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
switch(this.type) {
|
|
||||||
case INUMBER:
|
|
||||||
case IOP1:
|
|
||||||
case IOP2:
|
|
||||||
case IOP3:
|
|
||||||
case IVAR:
|
|
||||||
case IVARNAME:
|
|
||||||
case IENDSTATEMENT:
|
|
||||||
return this.value
|
|
||||||
case IFUNCALL:
|
|
||||||
return "CALL " + this.value
|
|
||||||
case IARRAY:
|
|
||||||
return "ARRAY " + this.value
|
|
||||||
case IMEMBER:
|
|
||||||
return "." + this.value
|
|
||||||
default:
|
|
||||||
return "Invalid Instruction"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unaryInstruction(value) {
|
|
||||||
return new Instruction(IOP1, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function binaryInstruction(value) {
|
|
||||||
return new Instruction(IOP2, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ternaryInstruction(value) {
|
|
||||||
return new Instruction(IOP3, value)
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
|
|
||||||
* http://www.undefined.ch/mparser/index.html
|
|
||||||
*
|
|
||||||
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
|
|
||||||
* https://silentmatt.com/javascript-expression-evaluator/
|
|
||||||
*
|
|
||||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*
|
|
||||||
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
|
|
||||||
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
|
|
||||||
* but don't feel like you have to let me know or ask permission.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as Polyfill from "./polyfill.mjs"
|
|
||||||
import { ParserState } from "./parserstate.mjs"
|
|
||||||
import { TEOF, TokenStream } from "./tokens.mjs"
|
|
||||||
import { ExprEvalExpression } from "./expression.mjs"
|
|
||||||
|
|
||||||
const optionNameMap = {
|
|
||||||
"+": "add",
|
|
||||||
"-": "subtract",
|
|
||||||
"*": "multiply",
|
|
||||||
"/": "divide",
|
|
||||||
"%": "remainder",
|
|
||||||
"^": "power",
|
|
||||||
"!": "factorial",
|
|
||||||
"<": "comparison",
|
|
||||||
">": "comparison",
|
|
||||||
"<=": "comparison",
|
|
||||||
">=": "comparison",
|
|
||||||
"==": "comparison",
|
|
||||||
"!=": "comparison",
|
|
||||||
"||": "concatenate",
|
|
||||||
"and": "logical",
|
|
||||||
"or": "logical",
|
|
||||||
"not": "logical",
|
|
||||||
"?": "conditional",
|
|
||||||
":": "conditional",
|
|
||||||
//'=': 'assignment', // Disable assignment
|
|
||||||
"[": "array"
|
|
||||||
//'()=': 'fndef' // Diable function definition
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Parser {
|
|
||||||
constructor(options) {
|
|
||||||
this.options = options || {}
|
|
||||||
this.unaryOps = {
|
|
||||||
sin: Math.sin,
|
|
||||||
cos: Math.cos,
|
|
||||||
tan: Math.tan,
|
|
||||||
asin: Math.asin,
|
|
||||||
acos: Math.acos,
|
|
||||||
atan: Math.atan,
|
|
||||||
sinh: Math.sinh || Polyfill.sinh,
|
|
||||||
cosh: Math.cosh || Polyfill.cosh,
|
|
||||||
tanh: Math.tanh || Polyfill.tanh,
|
|
||||||
asinh: Math.asinh || Polyfill.asinh,
|
|
||||||
acosh: Math.acosh || Polyfill.acosh,
|
|
||||||
atanh: Math.atanh || Polyfill.atanh,
|
|
||||||
sqrt: Math.sqrt,
|
|
||||||
cbrt: Math.cbrt || Polyfill.cbrt,
|
|
||||||
log: Math.log,
|
|
||||||
log2: Math.log2 || Polyfill.log2,
|
|
||||||
ln: Math.log,
|
|
||||||
lg: Math.log10 || Polyfill.log10,
|
|
||||||
log10: Math.log10 || Polyfill.log10,
|
|
||||||
expm1: Math.expm1 || Polyfill.expm1,
|
|
||||||
log1p: Math.log1p || Polyfill.log1p,
|
|
||||||
abs: Math.abs,
|
|
||||||
ceil: Math.ceil,
|
|
||||||
floor: Math.floor,
|
|
||||||
round: Math.round,
|
|
||||||
trunc: Math.trunc || Polyfill.trunc,
|
|
||||||
"-": Polyfill.neg,
|
|
||||||
"+": Number,
|
|
||||||
exp: Math.exp,
|
|
||||||
not: Polyfill.not,
|
|
||||||
length: Polyfill.stringOrArrayLength,
|
|
||||||
"!": Polyfill.factorial,
|
|
||||||
sign: Math.sign || Polyfill.sign
|
|
||||||
}
|
|
||||||
this.unaryOpsList = Object.keys(this.unaryOps)
|
|
||||||
|
|
||||||
this.binaryOps = {
|
|
||||||
"+": Polyfill.add,
|
|
||||||
"-": Polyfill.sub,
|
|
||||||
"*": Polyfill.mul,
|
|
||||||
"/": Polyfill.div,
|
|
||||||
"%": Polyfill.mod,
|
|
||||||
"^": Math.pow,
|
|
||||||
"||": Polyfill.concat,
|
|
||||||
"==": Polyfill.equal,
|
|
||||||
"!=": Polyfill.notEqual,
|
|
||||||
">": Polyfill.greaterThan,
|
|
||||||
"<": Polyfill.lessThan,
|
|
||||||
">=": Polyfill.greaterThanEqual,
|
|
||||||
"<=": Polyfill.lessThanEqual,
|
|
||||||
and: Polyfill.andOperator,
|
|
||||||
or: Polyfill.orOperator,
|
|
||||||
"in": Polyfill.inOperator,
|
|
||||||
"=": Polyfill.setVar,
|
|
||||||
"[": Polyfill.arrayIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ternaryOps = {
|
|
||||||
"?": Polyfill.condition
|
|
||||||
}
|
|
||||||
|
|
||||||
this.functions = {
|
|
||||||
random: Polyfill.random,
|
|
||||||
fac: Polyfill.factorial,
|
|
||||||
min: Polyfill.min,
|
|
||||||
max: Polyfill.max,
|
|
||||||
hypot: Math.hypot || Polyfill.hypot,
|
|
||||||
pyt: Math.hypot || Polyfill.hypot, // backward compat
|
|
||||||
pow: Math.pow,
|
|
||||||
atan2: Math.atan2,
|
|
||||||
"if": Polyfill.condition,
|
|
||||||
gamma: Polyfill.gamma,
|
|
||||||
"Γ": Polyfill.gamma,
|
|
||||||
roundTo: Polyfill.roundTo,
|
|
||||||
map: Polyfill.arrayMap,
|
|
||||||
fold: Polyfill.arrayFold,
|
|
||||||
filter: Polyfill.arrayFilter,
|
|
||||||
indexOf: Polyfill.stringOrArrayIndexOf,
|
|
||||||
join: Polyfill.arrayJoin
|
|
||||||
}
|
|
||||||
|
|
||||||
// These constants will automatically be replaced the MOMENT they are parsed.
|
|
||||||
// (Original consts from the parser)
|
|
||||||
this.builtinConsts = {}
|
|
||||||
// These consts will only be replaced when the expression is evaluated.
|
|
||||||
this.consts = {}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(expr) {
|
|
||||||
const instr = []
|
|
||||||
const parserState = new ParserState(
|
|
||||||
this,
|
|
||||||
new TokenStream(this, expr),
|
|
||||||
{ allowMemberAccess: this.options.allowMemberAccess }
|
|
||||||
)
|
|
||||||
|
|
||||||
parserState.parseExpression(instr)
|
|
||||||
parserState.expect(TEOF, QT_TRANSLATE_NOOP("error", "EOF"))
|
|
||||||
|
|
||||||
return new ExprEvalExpression(instr, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluate(expr, variables) {
|
|
||||||
return this.parse(expr).evaluate(variables)
|
|
||||||
}
|
|
||||||
|
|
||||||
isOperatorEnabled(op) {
|
|
||||||
const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op
|
|
||||||
const operators = this.options.operators || {}
|
|
||||||
|
|
||||||
return !(optionName in operators) || !!operators[optionName]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,398 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
|
|
||||||
* http://www.undefined.ch/mparser/index.html
|
|
||||||
*
|
|
||||||
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
|
|
||||||
* https://silentmatt.com/javascript-expression-evaluator/
|
|
||||||
*
|
|
||||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*
|
|
||||||
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
|
|
||||||
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
|
|
||||||
* but don't feel like you have to let me know or ask permission.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TBRACKET, TCOMMA, TEOF, TNAME, TNUMBER, TOP, TPAREN, TSTRING } from "./tokens.mjs"
|
|
||||||
import {
|
|
||||||
Instruction,
|
|
||||||
IARRAY, IEXPR, IFUNCALL, IMEMBER,
|
|
||||||
INUMBER, IVAR,
|
|
||||||
ternaryInstruction, binaryInstruction, unaryInstruction
|
|
||||||
} from "./instruction.mjs"
|
|
||||||
|
|
||||||
const COMPARISON_OPERATORS = ["==", "!=", "<", "<=", ">=", ">", "in"]
|
|
||||||
const ADD_SUB_OPERATORS = ["+", "-", "||"]
|
|
||||||
const TERM_OPERATORS = ["*", "/", "%"]
|
|
||||||
|
|
||||||
export class ParserState {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Parser} parser
|
|
||||||
* @param {TokenStream} tokenStream
|
|
||||||
* @param {{[operators]: Object.<string, boolean>, [allowMemberAccess]: boolean}} options
|
|
||||||
*/
|
|
||||||
constructor(parser, tokenStream, options) {
|
|
||||||
this.parser = parser
|
|
||||||
this.tokens = tokenStream
|
|
||||||
this.current = null
|
|
||||||
this.nextToken = null
|
|
||||||
this.next()
|
|
||||||
this.savedCurrent = null
|
|
||||||
this.savedNextToken = null
|
|
||||||
this.allowMemberAccess = options.allowMemberAccess !== false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queries the next token for parsing.
|
|
||||||
* @return {Token}
|
|
||||||
*/
|
|
||||||
next() {
|
|
||||||
this.current = this.nextToken
|
|
||||||
this.nextToken = this.tokens.next()
|
|
||||||
return this.nextToken
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a given Token matches a condition (called if function, one of if array, and exact match otherwise)
|
|
||||||
* @param {Token} token
|
|
||||||
* @param {Array|function(Token): boolean|string|number|boolean} [value]
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
tokenMatches(token, value) {
|
|
||||||
if(typeof value === "undefined") {
|
|
||||||
return true
|
|
||||||
} else if(Array.isArray(value)) {
|
|
||||||
return value.includes(token.value)
|
|
||||||
} else if(typeof value === "function") {
|
|
||||||
return value(token)
|
|
||||||
} else {
|
|
||||||
return token.value === value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the current state (current and next token) to be restored later.
|
|
||||||
*/
|
|
||||||
save() {
|
|
||||||
this.savedCurrent = this.current
|
|
||||||
this.savedNextToken = this.nextToken
|
|
||||||
this.tokens.save()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restores a previous state (current and next token) from last save.
|
|
||||||
*/
|
|
||||||
restore() {
|
|
||||||
this.tokens.restore()
|
|
||||||
this.current = this.savedCurrent
|
|
||||||
this.nextToken = this.savedNextToken
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the next token matches the given type and value, and if so, consume the current token.
|
|
||||||
* Returns true if the check matches.
|
|
||||||
* @param {string} type
|
|
||||||
* @param {any} [value]
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
accept(type, value) {
|
|
||||||
if(this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) {
|
|
||||||
this.next()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throws an error if the next token does not match the given type and value. Otherwise, consumes the current token.
|
|
||||||
* @param {string} type
|
|
||||||
* @param {any} [value]
|
|
||||||
*/
|
|
||||||
expect(type, value) {
|
|
||||||
if(!this.accept(type, value)) {
|
|
||||||
throw new Error(qsTranslate("error", "Parse error [position %1]: %2")
|
|
||||||
.arg(this.tokens.pos)
|
|
||||||
.arg(qsTranslate("error", "Expected %1").arg(value || type)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts enough Tokens to form an expression atom (generally the next part of the expression) into an instruction
|
|
||||||
* and pushes it to the instruction list.
|
|
||||||
* Throws an error if an unexpected token gets parsed.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseAtom(instr) {
|
|
||||||
const prefixOperators = this.tokens.unaryOpsList
|
|
||||||
|
|
||||||
if(this.accept(TNAME) || this.accept(TOP, prefixOperators)) {
|
|
||||||
instr.push(new Instruction(IVAR, this.current.value))
|
|
||||||
} else if(this.accept(TNUMBER)) {
|
|
||||||
instr.push(new Instruction(INUMBER, this.current.value))
|
|
||||||
} else if(this.accept(TSTRING)) {
|
|
||||||
instr.push(new Instruction(INUMBER, this.current.value))
|
|
||||||
} else if(this.accept(TPAREN, "(")) {
|
|
||||||
this.parseExpression(instr)
|
|
||||||
this.expect(TPAREN, ")")
|
|
||||||
} else if(this.accept(TBRACKET, "[")) {
|
|
||||||
if(this.accept(TBRACKET, "]")) {
|
|
||||||
instr.push(new Instruction(IARRAY, 0))
|
|
||||||
} else {
|
|
||||||
const argCount = this.parseArrayList(instr)
|
|
||||||
instr.push(new Instruction(IARRAY, argCount))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(qsTranslate("error", "Unexpected %1").arg(this.nextToken))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consumes the next tokens to compile a general expression which should return a value, and compiles
|
|
||||||
* the instructions into the list.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseExpression(instr) {
|
|
||||||
const exprInstr = []
|
|
||||||
this.parseConditionalExpression(exprInstr)
|
|
||||||
instr.push(...exprInstr)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an array indice, and return the number of arguments found at the end.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
parseArrayList(instr) {
|
|
||||||
let argCount = 0
|
|
||||||
|
|
||||||
while(!this.accept(TBRACKET, "]")) {
|
|
||||||
this.parseExpression(instr)
|
|
||||||
++argCount
|
|
||||||
while(this.accept(TCOMMA)) {
|
|
||||||
this.parseExpression(instr)
|
|
||||||
++argCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return argCount
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a tertiary statement (<condition> ? <value if true> : <value if false>) and pushes it into the instruction
|
|
||||||
* list.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseConditionalExpression(instr) {
|
|
||||||
this.parseOrExpression(instr)
|
|
||||||
while(this.accept(TOP, "?")) {
|
|
||||||
const trueBranch = []
|
|
||||||
const falseBranch = []
|
|
||||||
this.parseConditionalExpression(trueBranch)
|
|
||||||
this.expect(TOP, ":")
|
|
||||||
this.parseConditionalExpression(falseBranch)
|
|
||||||
instr.push(new Instruction(IEXPR, trueBranch))
|
|
||||||
instr.push(new Instruction(IEXPR, falseBranch))
|
|
||||||
instr.push(ternaryInstruction("?"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a binary or statement (<condition 1> or <condition 2>) and pushes it into the instruction list.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseOrExpression(instr) {
|
|
||||||
this.parseAndExpression(instr)
|
|
||||||
while(this.accept(TOP, "or")) {
|
|
||||||
const falseBranch = []
|
|
||||||
this.parseAndExpression(falseBranch)
|
|
||||||
instr.push(new Instruction(IEXPR, falseBranch))
|
|
||||||
instr.push(binaryInstruction("or"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a binary and statement (<condition 1> and <condition 2>) and pushes it into the instruction list.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseAndExpression(instr) {
|
|
||||||
this.parseComparison(instr)
|
|
||||||
while(this.accept(TOP, "and")) {
|
|
||||||
const trueBranch = []
|
|
||||||
this.parseComparison(trueBranch)
|
|
||||||
instr.push(new Instruction(IEXPR, trueBranch))
|
|
||||||
instr.push(binaryInstruction("and"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a binary equality statement (<condition 1> == <condition 2> and so on) and pushes it into the instruction list.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseComparison(instr) {
|
|
||||||
this.parseAddSub(instr)
|
|
||||||
while(this.accept(TOP, COMPARISON_OPERATORS)) {
|
|
||||||
const op = this.current
|
|
||||||
this.parseAddSub(instr)
|
|
||||||
instr.push(binaryInstruction(op.value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses add, minus and concat operations and pushes them into the instruction list.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseAddSub(instr) {
|
|
||||||
this.parseTerm(instr)
|
|
||||||
while(this.accept(TOP, ADD_SUB_OPERATORS)) {
|
|
||||||
const op = this.current
|
|
||||||
this.parseTerm(instr)
|
|
||||||
instr.push(binaryInstruction(op.value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses times, divide and modulo operations and pushes them into the instruction list.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseTerm(instr) {
|
|
||||||
this.parseFactor(instr)
|
|
||||||
while(this.accept(TOP, TERM_OPERATORS)) {
|
|
||||||
const op = this.current
|
|
||||||
this.parseFactor(instr)
|
|
||||||
instr.push(binaryInstruction(op.value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses prefix operations (+, -, but also functions like sin or cos which don't need parentheses)
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseFactor(instr) {
|
|
||||||
const prefixOperators = this.tokens.unaryOpsList
|
|
||||||
|
|
||||||
this.save()
|
|
||||||
if(this.accept(TOP, prefixOperators)) {
|
|
||||||
if(this.current.value !== "-" && this.current.value !== "+") {
|
|
||||||
if(this.nextToken.type === TPAREN && this.nextToken.value === "(") {
|
|
||||||
this.restore()
|
|
||||||
this.parseExponential(instr)
|
|
||||||
return
|
|
||||||
} else if(this.nextToken.type === TCOMMA || this.nextToken.type === TEOF || (this.nextToken.type === TPAREN && this.nextToken.value === ")")) {
|
|
||||||
this.restore()
|
|
||||||
this.parseAtom(instr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const op = this.current
|
|
||||||
this.parseFactor(instr)
|
|
||||||
instr.push(unaryInstruction(op.value))
|
|
||||||
} else {
|
|
||||||
this.parseExponential(instr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseExponential(instr) {
|
|
||||||
this.parsePostfixExpression(instr)
|
|
||||||
while(this.accept(TOP, "^")) {
|
|
||||||
this.parseFactor(instr)
|
|
||||||
instr.push(binaryInstruction("^"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses factorial '!' (after the expression to apply it to).
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parsePostfixExpression(instr) {
|
|
||||||
this.parseFunctionCall(instr)
|
|
||||||
while(this.accept(TOP, "!")) {
|
|
||||||
instr.push(unaryInstruction("!"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a function (name + parentheses + arguments).
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
*/
|
|
||||||
parseFunctionCall(instr) {
|
|
||||||
const prefixOperators = this.tokens.unaryOpsList
|
|
||||||
|
|
||||||
if(this.accept(TOP, prefixOperators)) {
|
|
||||||
const op = this.current
|
|
||||||
this.parseAtom(instr)
|
|
||||||
instr.push(unaryInstruction(op.value))
|
|
||||||
} else {
|
|
||||||
this.parseMemberExpression(instr)
|
|
||||||
while(this.accept(TPAREN, "(")) {
|
|
||||||
if(this.accept(TPAREN, ")")) {
|
|
||||||
instr.push(new Instruction(IFUNCALL, 0))
|
|
||||||
} else {
|
|
||||||
const argCount = this.parseArgumentList(instr)
|
|
||||||
instr.push(new Instruction(IFUNCALL, argCount))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a list of arguments, return their quantity.
|
|
||||||
* @param {Instruction[]} instr
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
parseArgumentList(instr) {
|
|
||||||
let argCount = 0
|
|
||||||
|
|
||||||
while(!this.accept(TPAREN, ")")) {
|
|
||||||
this.parseExpression(instr)
|
|
||||||
++argCount
|
|
||||||
while(this.accept(TCOMMA)) {
|
|
||||||
this.parseExpression(instr)
|
|
||||||
++argCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return argCount
|
|
||||||
}
|
|
||||||
|
|
||||||
parseMemberExpression(instr) {
|
|
||||||
this.parseAtom(instr)
|
|
||||||
while(this.accept(TOP, ".") || this.accept(TBRACKET, "[")) {
|
|
||||||
const op = this.current
|
|
||||||
|
|
||||||
if(op.value === ".") {
|
|
||||||
if(!this.allowMemberAccess) {
|
|
||||||
throw new Error(qsTranslate("error", "Unexpected \".\": member access is not permitted"))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.expect(TNAME)
|
|
||||||
instr.push(new Instruction(IMEMBER, this.current.value))
|
|
||||||
} else if(op.value === "[") {
|
|
||||||
if(!this.tokens.isOperatorEnabled("[")) {
|
|
||||||
throw new Error(qsTranslate("error", "Unexpected \"[]\": arrays are disabled."))
|
|
||||||
}
|
|
||||||
|
|
||||||
this.parseExpression(instr)
|
|
||||||
this.expect(TBRACKET, "]")
|
|
||||||
instr.push(binaryInstruction("["))
|
|
||||||
} else {
|
|
||||||
throw new Error(qsTranslate("error", "Unexpected symbol: %1.").arg(op.value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,371 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
|
|
||||||
* http://www.undefined.ch/mparser/index.html
|
|
||||||
*
|
|
||||||
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
|
|
||||||
* https://silentmatt.com/javascript-expression-evaluator/
|
|
||||||
*
|
|
||||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*
|
|
||||||
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
|
|
||||||
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
|
|
||||||
* but don't feel like you have to let me know or ask permission.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function add(a, b) {
|
|
||||||
return Number(a) + Number(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sub(a, b) {
|
|
||||||
return a - b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mul(a, b) {
|
|
||||||
return a * b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function div(a, b) {
|
|
||||||
return a / b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mod(a, b) {
|
|
||||||
return a % b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function concat(a, b) {
|
|
||||||
if(Array.isArray(a) && Array.isArray(b)) {
|
|
||||||
return a.concat(b)
|
|
||||||
}
|
|
||||||
return "" + a + b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function equal(a, b) {
|
|
||||||
return a === b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function notEqual(a, b) {
|
|
||||||
return a !== b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function greaterThan(a, b) {
|
|
||||||
return a > b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lessThan(a, b) {
|
|
||||||
return a < b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function greaterThanEqual(a, b) {
|
|
||||||
return a >= b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function lessThanEqual(a, b) {
|
|
||||||
return a <= b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function andOperator(a, b) {
|
|
||||||
return Boolean(a && b)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function orOperator(a, b) {
|
|
||||||
return Boolean(a || b)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function inOperator(a, b) {
|
|
||||||
return b.includes(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sinh(a) {
|
|
||||||
return ((Math.exp(a) - Math.exp(-a)) / 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function cosh(a) {
|
|
||||||
return ((Math.exp(a) + Math.exp(-a)) / 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function tanh(a) {
|
|
||||||
if(a === Infinity) return 1
|
|
||||||
if(a === -Infinity) return -1
|
|
||||||
return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asinh(a) {
|
|
||||||
if(a === -Infinity) return a
|
|
||||||
return Math.log(a + Math.sqrt((a * a) + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function acosh(a) {
|
|
||||||
return Math.log(a + Math.sqrt((a * a) - 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function atanh(a) {
|
|
||||||
return (Math.log((1 + a) / (1 - a)) / 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function log10(a) {
|
|
||||||
return Math.log(a) * Math.LOG10E
|
|
||||||
}
|
|
||||||
|
|
||||||
export function neg(a) {
|
|
||||||
return -a
|
|
||||||
}
|
|
||||||
|
|
||||||
export function not(a) {
|
|
||||||
return !a
|
|
||||||
}
|
|
||||||
|
|
||||||
export function trunc(a) {
|
|
||||||
return a < 0 ? Math.ceil(a) : Math.floor(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function random(a) {
|
|
||||||
return Math.random() * (a || 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function factorial(a) { // a!
|
|
||||||
return gamma(a + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isInteger(value) {
|
|
||||||
return isFinite(value) && (value === Math.round(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
const GAMMA_G = 4.7421875
|
|
||||||
const GAMMA_P = [
|
|
||||||
0.99999999999999709182,
|
|
||||||
57.156235665862923517, -59.597960355475491248,
|
|
||||||
14.136097974741747174, -0.49191381609762019978,
|
|
||||||
0.33994649984811888699e-4,
|
|
||||||
0.46523628927048575665e-4, -0.98374475304879564677e-4,
|
|
||||||
0.15808870322491248884e-3, -0.21026444172410488319e-3,
|
|
||||||
0.21743961811521264320e-3, -0.16431810653676389022e-3,
|
|
||||||
0.84418223983852743293e-4, -0.26190838401581408670e-4,
|
|
||||||
0.36899182659531622704e-5
|
|
||||||
]
|
|
||||||
|
|
||||||
// Gamma function from math.js
|
|
||||||
export function gamma(n) {
|
|
||||||
let t, x
|
|
||||||
|
|
||||||
if(isInteger(n)) {
|
|
||||||
if(n <= 0) {
|
|
||||||
return isFinite(n) ? Infinity : NaN
|
|
||||||
}
|
|
||||||
|
|
||||||
if(n > 171) {
|
|
||||||
return Infinity // Will overflow
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = n - 2
|
|
||||||
let res = n - 1
|
|
||||||
while(value > 1) {
|
|
||||||
res *= value
|
|
||||||
value--
|
|
||||||
}
|
|
||||||
|
|
||||||
if(res === 0) {
|
|
||||||
res = 1 // 0! is per definition 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
if(n < 0.5) {
|
|
||||||
return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n))
|
|
||||||
}
|
|
||||||
|
|
||||||
if(n >= 171.35) {
|
|
||||||
return Infinity // will overflow
|
|
||||||
}
|
|
||||||
|
|
||||||
if(n > 85.0) { // Extended Stirling Approx
|
|
||||||
const twoN = n * n
|
|
||||||
const threeN = twoN * n
|
|
||||||
const fourN = threeN * n
|
|
||||||
const fiveN = fourN * n
|
|
||||||
return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) *
|
|
||||||
(1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) -
|
|
||||||
(571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) +
|
|
||||||
(5246819 / (75246796800 * fiveN * n)))
|
|
||||||
}
|
|
||||||
|
|
||||||
--n
|
|
||||||
x = GAMMA_P[0]
|
|
||||||
for(let i = 1; i < GAMMA_P.length; ++i) {
|
|
||||||
x += GAMMA_P[i] / (n + i)
|
|
||||||
}
|
|
||||||
|
|
||||||
t = n + GAMMA_G + 0.5
|
|
||||||
return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringOrArrayLength(s) {
|
|
||||||
if(Array.isArray(s)) {
|
|
||||||
return s.length
|
|
||||||
}
|
|
||||||
return String(s).length
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hypot() {
|
|
||||||
let sum = 0
|
|
||||||
let larg = 0
|
|
||||||
for(let i = 0; i < arguments.length; i++) {
|
|
||||||
const arg = Math.abs(arguments[i])
|
|
||||||
let div
|
|
||||||
if(larg < arg) {
|
|
||||||
div = larg / arg
|
|
||||||
sum = (sum * div * div) + 1
|
|
||||||
larg = arg
|
|
||||||
} else if(arg > 0) {
|
|
||||||
div = arg / larg
|
|
||||||
sum += div * div
|
|
||||||
} else {
|
|
||||||
sum += arg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return larg === Infinity ? Infinity : larg * Math.sqrt(sum)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function condition(cond, yep, nope) {
|
|
||||||
return cond ? yep : nope
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decimal adjustment of a number.
|
|
||||||
* From @escopecz.
|
|
||||||
*
|
|
||||||
* @param {number} value - The number.
|
|
||||||
* @param {Integer} exp - The exponent (the 10 logarithm of the adjustment base).
|
|
||||||
* @return {number} - The adjusted value.
|
|
||||||
*/
|
|
||||||
export function roundTo(value, exp) {
|
|
||||||
// If the exp is undefined or zero...
|
|
||||||
if(typeof exp === "undefined" || +exp === 0) {
|
|
||||||
return Math.round(value)
|
|
||||||
}
|
|
||||||
value = +value
|
|
||||||
exp = -(+exp)
|
|
||||||
// If the value is not a number or the exp is not an integer...
|
|
||||||
if(isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
|
|
||||||
return NaN
|
|
||||||
}
|
|
||||||
// Shift
|
|
||||||
value = value.toString().split("e")
|
|
||||||
value = Math.round(+(value[0] + "e" + (value[1] ? (+value[1] - exp) : -exp)))
|
|
||||||
// Shift back
|
|
||||||
value = value.toString().split("e")
|
|
||||||
return +(value[0] + "e" + (value[1] ? (+value[1] + exp) : exp))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setVar(name, value, variables) {
|
|
||||||
if(variables) variables[name] = value
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrayIndex(array, index) {
|
|
||||||
return array[index | 0]
|
|
||||||
}
|
|
||||||
|
|
||||||
export function max(array) {
|
|
||||||
if(arguments.length === 1 && Array.isArray(array)) {
|
|
||||||
return Math.max.apply(Math, array)
|
|
||||||
} else if(arguments.length >= 1) {
|
|
||||||
return Math.max.apply(Math, arguments)
|
|
||||||
} else {
|
|
||||||
throw new EvalError(qsTranslate("error", "Function %1 must have at least one argument.").arg("max"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function min(array) {
|
|
||||||
if(arguments.length === 1 && Array.isArray(array)) {
|
|
||||||
return Math.min.apply(Math, array)
|
|
||||||
} else if(arguments.length >= 1) {
|
|
||||||
return Math.min.apply(Math, arguments)
|
|
||||||
} else {
|
|
||||||
throw new EvalError(qsTranslate("error", "Function %1 must have at least one argument.").arg("min"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrayMap(f, a) {
|
|
||||||
if(typeof f !== "function") {
|
|
||||||
throw new EvalError(qsTranslate("error", "First argument to map is not a function."))
|
|
||||||
}
|
|
||||||
if(!Array.isArray(a)) {
|
|
||||||
throw new EvalError(qsTranslate("error", "Second argument to map is not an array."))
|
|
||||||
}
|
|
||||||
return a.map(function(x, i) {
|
|
||||||
return f(x, i)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrayFold(f, init, a) {
|
|
||||||
if(typeof f !== "function") {
|
|
||||||
throw new EvalError(qsTranslate("error", "First argument to fold is not a function."))
|
|
||||||
}
|
|
||||||
if(!Array.isArray(a)) {
|
|
||||||
throw new EvalError(qsTranslate("error", "Second argument to fold is not an array."))
|
|
||||||
}
|
|
||||||
return a.reduce(function(acc, x, i) {
|
|
||||||
return f(acc, x, i)
|
|
||||||
}, init)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrayFilter(f, a) {
|
|
||||||
if(typeof f !== "function") {
|
|
||||||
throw new EvalError(qsTranslate("error", "First argument to filter is not a function."))
|
|
||||||
}
|
|
||||||
if(!Array.isArray(a)) {
|
|
||||||
throw new EvalError(qsTranslate("error", "Second argument to filter is not an array."))
|
|
||||||
}
|
|
||||||
return a.filter(function(x, i) {
|
|
||||||
return f(x, i)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringOrArrayIndexOf(target, s) {
|
|
||||||
if(!(Array.isArray(s) || typeof s === "string")) {
|
|
||||||
throw new Error(qsTranslate("error", "Second argument to indexOf is not a string or array."))
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.indexOf(target)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function arrayJoin(sep, a) {
|
|
||||||
if(!Array.isArray(a)) {
|
|
||||||
throw new Error(qsTranslate("error", "Second argument to join is not an array."))
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.join(sep)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sign(x) {
|
|
||||||
return ((x > 0) - (x < 0)) || +x
|
|
||||||
}
|
|
||||||
|
|
||||||
const ONE_THIRD = 1 / 3
|
|
||||||
|
|
||||||
export function cbrt(x) {
|
|
||||||
return x < 0 ? -Math.pow(-x, ONE_THIRD) : Math.pow(x, ONE_THIRD)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function expm1(x) {
|
|
||||||
return Math.exp(x) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export function log1p(x) {
|
|
||||||
return Math.log(1 + x)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function log2(x) {
|
|
||||||
return Math.log(x) / Math.LN2
|
|
||||||
}
|
|
|
@ -1,575 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
|
|
||||||
* http://www.undefined.ch/mparser/index.html
|
|
||||||
*
|
|
||||||
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
|
|
||||||
* https://silentmatt.com/javascript-expression-evaluator/
|
|
||||||
*
|
|
||||||
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*
|
|
||||||
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
|
|
||||||
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
|
|
||||||
* but don't feel like you have to let me know or ask permission.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const TEOF = "TEOF"
|
|
||||||
export const TOP = "TOP"
|
|
||||||
export const TNUMBER = "TNUMBER"
|
|
||||||
export const TSTRING = "TSTRING"
|
|
||||||
export const TPAREN = "TPAREN"
|
|
||||||
export const TBRACKET = "TBRACKET"
|
|
||||||
export const TCOMMA = "TCOMMA"
|
|
||||||
export const TNAME = "TNAME"
|
|
||||||
|
|
||||||
|
|
||||||
// Additional variable characters.
|
|
||||||
export const ADDITIONAL_VARCHARS = [
|
|
||||||
"α", "β", "γ", "δ", "ε", "ζ", "η",
|
|
||||||
"π", "θ", "κ", "λ", "μ", "ξ", "ρ",
|
|
||||||
"ς", "σ", "τ", "φ", "χ", "ψ", "ω",
|
|
||||||
"Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ",
|
|
||||||
"Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ",
|
|
||||||
"ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
|
|
||||||
"ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
|
|
||||||
"⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
|
|
||||||
"₄", "₅", "₆", "₇", "₈", "₉", "₀",
|
|
||||||
"∞", "π"
|
|
||||||
]
|
|
||||||
|
|
||||||
export class Token {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} type - Type of the token (see above).
|
|
||||||
* @param {any} value - Value of the token.
|
|
||||||
* @param {number} index - Index in the string of the token.
|
|
||||||
*/
|
|
||||||
constructor(type, value, index) {
|
|
||||||
this.type = type
|
|
||||||
this.value = value
|
|
||||||
this.index = index
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return this.type + ": " + this.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const unicodeCodePointPattern = /^[0-9a-f]{4}$/i
|
|
||||||
|
|
||||||
export class TokenStream {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Parser} parser
|
|
||||||
* @param {string} expression
|
|
||||||
*/
|
|
||||||
constructor(parser, expression) {
|
|
||||||
this.pos = 0
|
|
||||||
this.current = null
|
|
||||||
this.unaryOps = parser.unaryOps
|
|
||||||
this.unaryOpsList = parser.unaryOpsList
|
|
||||||
this.binaryOps = parser.binaryOps
|
|
||||||
this.ternaryOps = parser.ternaryOps
|
|
||||||
this.builtinConsts = parser.builtinConsts
|
|
||||||
this.expression = expression
|
|
||||||
this.savedPosition = 0
|
|
||||||
this.savedCurrent = null
|
|
||||||
this.options = parser.options
|
|
||||||
this.parser = parser
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} type - Type of the token (see above).
|
|
||||||
* @param {any} value - Value of the token.
|
|
||||||
* @param {number} [pos] - Index in the string of the token.
|
|
||||||
*/
|
|
||||||
newToken(type, value, pos) {
|
|
||||||
return new Token(type, value, pos != null ? pos : this.pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the current position and token into the object.
|
|
||||||
*/
|
|
||||||
save() {
|
|
||||||
this.savedPosition = this.pos
|
|
||||||
this.savedCurrent = this.current
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restored the saved position and token into the current.
|
|
||||||
*/
|
|
||||||
restore() {
|
|
||||||
this.pos = this.savedPosition
|
|
||||||
this.current = this.savedCurrent
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consumes the character at the current position and advance it
|
|
||||||
* until it makes a valid token, and returns it.
|
|
||||||
* @returns {Token}
|
|
||||||
*/
|
|
||||||
next() {
|
|
||||||
if(this.pos >= this.expression.length) {
|
|
||||||
return this.newToken(TEOF, "EOF")
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.isWhitespace()) {
|
|
||||||
return this.next()
|
|
||||||
} else if(this.isRadixInteger() ||
|
|
||||||
this.isNumber() ||
|
|
||||||
this.isOperator() ||
|
|
||||||
this.isString() ||
|
|
||||||
this.isParen() ||
|
|
||||||
this.isBracket() ||
|
|
||||||
this.isComma() ||
|
|
||||||
this.isNamedOp() ||
|
|
||||||
this.isConst() ||
|
|
||||||
this.isName()) {
|
|
||||||
return this.current
|
|
||||||
} else {
|
|
||||||
this.parseError(qsTranslate("error", "Unknown character \"%1\".").arg(this.expression.charAt(this.pos)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the character at the current position starts a string, and if so, consumes it as the current token
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isString() {
|
|
||||||
const startPos = this.pos
|
|
||||||
const quote = this.expression.charAt(startPos)
|
|
||||||
let r = false
|
|
||||||
|
|
||||||
if(quote === "'" || quote === "\"") {
|
|
||||||
let index = this.expression.indexOf(quote, startPos + 1)
|
|
||||||
while(index >= 0 && this.pos < this.expression.length) {
|
|
||||||
this.pos = index + 1
|
|
||||||
if(this.expression.charAt(index - 1) !== "\\") {
|
|
||||||
const rawString = this.expression.substring(startPos + 1, index)
|
|
||||||
this.current = this.newToken(TSTRING, this.unescape(rawString), startPos)
|
|
||||||
r = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
index = this.expression.indexOf(quote, index + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the character at the current pos is a parenthesis, and if so consumes it into current
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isParen() {
|
|
||||||
const c = this.expression.charAt(this.pos)
|
|
||||||
if(c === "(" || c === ")") {
|
|
||||||
this.current = this.newToken(TPAREN, c)
|
|
||||||
this.pos++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the character at the current pos is a bracket, and if so consumes it into current
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isBracket() {
|
|
||||||
const c = this.expression.charAt(this.pos)
|
|
||||||
if((c === "[" || c === "]") && this.isOperatorEnabled("[")) {
|
|
||||||
this.current = this.newToken(TBRACKET, c)
|
|
||||||
this.pos++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the character at the current pos is a comma, and if so consumes it into current
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isComma() {
|
|
||||||
const c = this.expression.charAt(this.pos)
|
|
||||||
if(c === ",") {
|
|
||||||
this.current = this.newToken(TCOMMA, ",")
|
|
||||||
this.pos++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current character is an identifier and makes a const, and if so, consumes it as the current token
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isConst() {
|
|
||||||
const startPos = this.pos
|
|
||||||
let i = startPos
|
|
||||||
for(; i < this.expression.length; i++) {
|
|
||||||
const c = this.expression.charAt(i)
|
|
||||||
if(c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
|
|
||||||
if(i === this.pos || (c !== "_" && c !== "." && (c < "0" || c > "9"))) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(i > startPos) {
|
|
||||||
const str = this.expression.substring(startPos, i)
|
|
||||||
if(str in this.builtinConsts) {
|
|
||||||
this.current = this.newToken(TNUMBER, this.builtinConsts[str])
|
|
||||||
this.pos += str.length
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current character is an identifier and makes a function or an operator, and if so, consumes it as the current token
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isNamedOp() {
|
|
||||||
const startPos = this.pos
|
|
||||||
let i = startPos
|
|
||||||
for(; i < this.expression.length; i++) {
|
|
||||||
const c = this.expression.charAt(i)
|
|
||||||
if(c.toUpperCase() === c.toLowerCase()) {
|
|
||||||
if(i === this.pos || (c !== "_" && (c < "0" || c > "9"))) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(i > startPos) {
|
|
||||||
const str = this.expression.substring(startPos, i)
|
|
||||||
if(this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) {
|
|
||||||
this.current = this.newToken(TOP, str)
|
|
||||||
this.pos += str.length
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current character is an identifier and makes a variable, and if so, consumes it as the current token
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isName() {
|
|
||||||
const startPos = this.pos
|
|
||||||
let i = startPos
|
|
||||||
let hasLetter = false
|
|
||||||
for(; i < this.expression.length; i++) {
|
|
||||||
const c = this.expression.charAt(i)
|
|
||||||
if(c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
|
|
||||||
if(i === this.pos && (c === "$" || c === "_")) {
|
|
||||||
if(c === "_") {
|
|
||||||
hasLetter = true
|
|
||||||
}
|
|
||||||
} else if(i === this.pos || !hasLetter || (c !== "_" && (c < "0" || c > "9"))) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
hasLetter = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(hasLetter) {
|
|
||||||
const str = this.expression.substring(startPos, i)
|
|
||||||
this.current = this.newToken(TNAME, str)
|
|
||||||
this.pos += str.length
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the character at the current position is a whitespace, and if so, consumes all consecutive whitespaces
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
isWhitespace() {
|
|
||||||
let r = false
|
|
||||||
let c = this.expression.charAt(this.pos)
|
|
||||||
while(c === " " || c === "\t" || c === "\n" || c === "\r") {
|
|
||||||
r = true
|
|
||||||
this.pos++
|
|
||||||
if(this.pos >= this.expression.length) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c = this.expression.charAt(this.pos)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current character is a zero, and checks whether it forms a radix number, and if so, consumes it as the current token
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isRadixInteger() {
|
|
||||||
let pos = this.pos
|
|
||||||
|
|
||||||
if(pos >= this.expression.length - 2 || this.expression.charAt(pos) !== "0") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
++pos
|
|
||||||
|
|
||||||
let radix
|
|
||||||
let validDigit
|
|
||||||
if(this.expression.charAt(pos) === "x") {
|
|
||||||
radix = 16
|
|
||||||
validDigit = /^[0-9a-f]$/i
|
|
||||||
pos++
|
|
||||||
} else if(this.expression.charAt(pos) === "b") {
|
|
||||||
radix = 2
|
|
||||||
validDigit = /^[01]$/i
|
|
||||||
pos++
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let valid = false
|
|
||||||
const startPos = pos
|
|
||||||
|
|
||||||
while(pos < this.expression.length) {
|
|
||||||
const c = this.expression.charAt(pos)
|
|
||||||
if(validDigit.test(c)) {
|
|
||||||
pos++
|
|
||||||
valid = true
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(valid) {
|
|
||||||
this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix))
|
|
||||||
this.pos = pos
|
|
||||||
}
|
|
||||||
return valid
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current character is a digit, and checks whether it forms a number, and if so, consumes it as the current token
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isNumber() {
|
|
||||||
const startPos = this.pos
|
|
||||||
let valid = false
|
|
||||||
let pos = startPos
|
|
||||||
let resetPos = startPos
|
|
||||||
let foundDot = false
|
|
||||||
let foundDigits = false
|
|
||||||
let c
|
|
||||||
|
|
||||||
// Check for digit with dot.
|
|
||||||
while(pos < this.expression.length) {
|
|
||||||
c = this.expression.charAt(pos)
|
|
||||||
if((c >= "0" && c <= "9") || (!foundDot && c === ".")) {
|
|
||||||
if(c === ".") {
|
|
||||||
foundDot = true
|
|
||||||
} else {
|
|
||||||
foundDigits = true
|
|
||||||
}
|
|
||||||
pos++
|
|
||||||
valid = foundDigits
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(valid) {
|
|
||||||
resetPos = pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for e exponents.
|
|
||||||
if(c === "e" || c === "E") {
|
|
||||||
pos++
|
|
||||||
let acceptSign = true
|
|
||||||
let validExponent = false
|
|
||||||
while(pos < this.expression.length) {
|
|
||||||
c = this.expression.charAt(pos)
|
|
||||||
if(acceptSign && (c === "+" || c === "-")) {
|
|
||||||
acceptSign = false
|
|
||||||
} else if(c >= "0" && c <= "9") {
|
|
||||||
validExponent = true
|
|
||||||
acceptSign = false
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!validExponent) {
|
|
||||||
pos = resetPos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use parseFloat now that we've identified the number.
|
|
||||||
if(valid) {
|
|
||||||
this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos)))
|
|
||||||
this.pos = pos
|
|
||||||
} else {
|
|
||||||
this.pos = resetPos
|
|
||||||
}
|
|
||||||
return valid
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current character is an operator, checks whether it's enabled and if so, consumes it as the current token
|
|
||||||
* and returns true. Otherwise, returns false.
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
isOperator() {
|
|
||||||
const startPos = this.pos
|
|
||||||
const c = this.expression.charAt(this.pos)
|
|
||||||
|
|
||||||
if(c === "+" || c === "-" || c === "*" || c === "/" || c === "%" || c === "^" || c === "?" || c === ":" || c === ".") {
|
|
||||||
this.current = this.newToken(TOP, c)
|
|
||||||
} else if(c === "∙" || c === "•") {
|
|
||||||
this.current = this.newToken(TOP, "*")
|
|
||||||
} else if(c === ">") {
|
|
||||||
if(this.expression.charAt(this.pos + 1) === "=") {
|
|
||||||
this.current = this.newToken(TOP, ">=")
|
|
||||||
this.pos++
|
|
||||||
} else {
|
|
||||||
this.current = this.newToken(TOP, ">")
|
|
||||||
}
|
|
||||||
} else if(c === "<") {
|
|
||||||
if(this.expression.charAt(this.pos + 1) === "=") {
|
|
||||||
this.current = this.newToken(TOP, "<=")
|
|
||||||
this.pos++
|
|
||||||
} else {
|
|
||||||
this.current = this.newToken(TOP, "<")
|
|
||||||
}
|
|
||||||
} else if(c === "|") {
|
|
||||||
if(this.expression.charAt(this.pos + 1) === "|") {
|
|
||||||
this.current = this.newToken(TOP, "||")
|
|
||||||
this.pos++
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if(c === "=") {
|
|
||||||
if(this.expression.charAt(this.pos + 1) === "=") {
|
|
||||||
this.current = this.newToken(TOP, "==")
|
|
||||||
this.pos++
|
|
||||||
} else {
|
|
||||||
this.current = this.newToken(TOP, c)
|
|
||||||
}
|
|
||||||
} else if(c === "!") {
|
|
||||||
if(this.expression.charAt(this.pos + 1) === "=") {
|
|
||||||
this.current = this.newToken(TOP, "!=")
|
|
||||||
this.pos++
|
|
||||||
} else {
|
|
||||||
this.current = this.newToken(TOP, c)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
this.pos++
|
|
||||||
|
|
||||||
if(this.isOperatorEnabled(this.current.value)) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
this.pos = startPos
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces a backslash and a character by its unescaped value.
|
|
||||||
* @param {string} v - string to un escape.
|
|
||||||
*/
|
|
||||||
unescape(v) {
|
|
||||||
let index = v.indexOf("\\")
|
|
||||||
if(index < 0) {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer = v.substring(0, index)
|
|
||||||
while(index >= 0) {
|
|
||||||
const c = v.charAt(++index)
|
|
||||||
switch(c) {
|
|
||||||
case "'":
|
|
||||||
buffer += "'"
|
|
||||||
break
|
|
||||||
case "\"":
|
|
||||||
buffer += "\""
|
|
||||||
break
|
|
||||||
case "\\":
|
|
||||||
buffer += "\\"
|
|
||||||
break
|
|
||||||
case "/":
|
|
||||||
buffer += "/"
|
|
||||||
break
|
|
||||||
case "b":
|
|
||||||
buffer += "\b"
|
|
||||||
break
|
|
||||||
case "f":
|
|
||||||
buffer += "\f"
|
|
||||||
break
|
|
||||||
case "n":
|
|
||||||
buffer += "\n"
|
|
||||||
break
|
|
||||||
case "r":
|
|
||||||
buffer += "\r"
|
|
||||||
break
|
|
||||||
case "t":
|
|
||||||
buffer += "\t"
|
|
||||||
break
|
|
||||||
case "u":
|
|
||||||
// interpret the following 4 characters as the hex of the unicode code point
|
|
||||||
const codePoint = v.substring(index + 1, index + 5)
|
|
||||||
if(!unicodeCodePointPattern.test(codePoint)) {
|
|
||||||
this.parseError(qsTranslate("error", "Illegal escape sequence: %1.").arg("\\u" + codePoint))
|
|
||||||
}
|
|
||||||
buffer += String.fromCharCode(parseInt(codePoint, 16))
|
|
||||||
index += 4
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
throw this.parseError(qsTranslate("error", "Illegal escape sequence: %1.").arg("\\" + c))
|
|
||||||
}
|
|
||||||
++index
|
|
||||||
const backslash = v.indexOf("\\", index)
|
|
||||||
buffer += v.substring(index, backslash < 0 ? v.length : backslash)
|
|
||||||
index = backslash
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shorthand for the parser's method to check if an operator is enabled.
|
|
||||||
* @param {string} op
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
isOperatorEnabled(op) {
|
|
||||||
return this.parser.isOperatorEnabled(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throws a translated error.
|
|
||||||
* @param {string} msg
|
|
||||||
*/
|
|
||||||
parseError(msg) {
|
|
||||||
throw new Error(qsTranslate("error", "Parse error [position %1]: %2").arg(this.pos).arg(msg))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Type polyfills for IDEs.
|
|
||||||
// Never directly imported.
|
|
||||||
|
|
||||||
Modules = Modules || {}
|
|
||||||
/** @type {function(string, string): string} */
|
|
||||||
qsTranslate = qsTranslate || function(category, string) { throw new Error('qsTranslate not implemented.'); }
|
|
||||||
/** @type {function(string): string} */
|
|
||||||
qsTr = qsTr || function(string) { throw new Error('qsTr not implemented.'); }
|
|
||||||
/** @type {function(string, string): string} */
|
|
||||||
QT_TRANSLATE_NOOP = QT_TRANSLATE_NOOP || function(string, string) { throw new Error('QT_TRANSLATE_NOOP not implemented.'); }
|
|
||||||
/** @type {function(string): string} */
|
|
||||||
QT_TR_NOOP = QT_TR_NOOP || function(string) { throw new Error('QT_TR_NOOP not implemented.'); }
|
|
||||||
/** @type {function(string|boolean|int): string} */
|
|
||||||
String.prototype.arg = String.prototype.arg || function(parameter) { throw new Error('arg not implemented.'); }
|
|
||||||
|
|
||||||
const Qt = {
|
|
||||||
/**
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
* @param {number} width
|
|
||||||
* @param {number} height
|
|
||||||
* @returns {{x, width, y, height}}
|
|
||||||
*/
|
|
||||||
rect: function(x, y, width, height) {
|
|
||||||
return {x: x, y: y, width: width, height: height};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Typehints for Helper. */
|
|
||||||
const Helper = {
|
|
||||||
/** @type {function(string): boolean} */
|
|
||||||
getSettingBool: (setting) => true,
|
|
||||||
/** @type {function(string): int} */
|
|
||||||
getSettingInt: (setting) => 0,
|
|
||||||
/** @type {function(string): string} */
|
|
||||||
getSetting: (setting) => '',
|
|
||||||
/** @type {function(string, boolean)} */
|
|
||||||
setSettingBool: (setting, value) => {},
|
|
||||||
/** @type {function(string, int)} */
|
|
||||||
setSettingInt: (setting, value) => 0,
|
|
||||||
/** @type {function(string, string)} */
|
|
||||||
setSetting: (setting, value) => '',
|
|
||||||
/** @type {function(string, string)} */
|
|
||||||
write: (filename, data) => {},
|
|
||||||
/** @type {function(string): string} */
|
|
||||||
load: (filename) => '',
|
|
||||||
}
|
|
||||||
|
|
||||||
const Latex = {
|
|
||||||
/** @type {function(string, number, string): string} */
|
|
||||||
render: (latex_markup, font_size, color) => '',
|
|
||||||
/** @type {function(string, number, string): string} */
|
|
||||||
findPrerendered: (latex_markup, font_size, color) => '',
|
|
||||||
/** @type {function(): boolean} */
|
|
||||||
checkLatexInstallation: () => true,
|
|
||||||
}
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||||
|
* Copyright (C) 2022 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.pragma library
|
||||||
|
|
||||||
|
.import "../expr-eval.js" as ExprEval
|
||||||
|
.import "../utils.js" as Utils
|
||||||
|
.import "latex.js" as Latex
|
||||||
|
|
||||||
|
var evalVariables = { // Variables not provided by expr-eval.js, needs to be provided manualy
|
||||||
|
"pi": Math.PI,
|
||||||
|
"π": Math.PI,
|
||||||
|
"inf": Infinity,
|
||||||
|
"Infinity": Infinity,
|
||||||
|
"∞": Infinity,
|
||||||
|
"e": Math.E,
|
||||||
|
"E": Math.E
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentVars = {}
|
||||||
|
|
||||||
|
const parser = new ExprEval.Parser()
|
||||||
|
parser.functions.integral = function(a, b, f, variable) {
|
||||||
|
// https://en.wikipedia.org/wiki/Simpson%27s_rule
|
||||||
|
f = parser.parse(f).toJSFunction(variable, currentVars)
|
||||||
|
return (b-a)/6*(f(a)+4*f((a+b)/2)+f(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
const DERIVATION_PRECISION = 0.1
|
||||||
|
|
||||||
|
parser.functions.derivative = function(f, variable, x) {
|
||||||
|
f = parser.parse(f).toJSFunction(variable, currentVars)
|
||||||
|
return (f(x+DERIVATION_PRECISION/2)-f(x-DERIVATION_PRECISION/2))/DERIVATION_PRECISION
|
||||||
|
}
|
||||||
|
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,19 +16,21 @@
|
||||||
* 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 { Expression, executeExpression } from "expression.mjs"
|
.pragma library
|
||||||
|
|
||||||
|
.import "expression.js" as Expr
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main abstract domain class
|
* Main abstract domain class
|
||||||
* It doesn't represent any kind of domain and is meant to be extended.
|
* It doesn't represent any kind of domain and is meant to be extended.
|
||||||
*/
|
*/
|
||||||
export class Domain {
|
class Domain {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether x is included in the domain.
|
* Checks whether x is included in the domain.
|
||||||
* @param {number} x - The x value.
|
* @param {number} x - The x value.
|
||||||
* @return {boolean} true if included, false otherwise.
|
* @return {bool} true if included, false otherwise.
|
||||||
*/
|
*/
|
||||||
includes(x) { return false }
|
includes(x) { return false }
|
||||||
|
|
||||||
|
@ -70,13 +72,11 @@ export class Domain {
|
||||||
case "RP":
|
case "RP":
|
||||||
case "R+":
|
case "R+":
|
||||||
case "ℝ⁺":
|
case "ℝ⁺":
|
||||||
case "ℝ+":
|
|
||||||
return Domain.RP
|
return Domain.RP
|
||||||
break;
|
break;
|
||||||
case "RM":
|
case "RM":
|
||||||
case "R-":
|
case "R-":
|
||||||
case "ℝ⁻":
|
case "ℝ⁻":
|
||||||
case "ℝ-":
|
|
||||||
return Domain.RM
|
return Domain.RM
|
||||||
break;
|
break;
|
||||||
case "RPE":
|
case "RPE":
|
||||||
|
@ -85,8 +85,6 @@ export class Domain {
|
||||||
case "R*+":
|
case "R*+":
|
||||||
case "ℝ*⁺":
|
case "ℝ*⁺":
|
||||||
case "ℝ⁺*":
|
case "ℝ⁺*":
|
||||||
case "ℝ*+":
|
|
||||||
case "ℝ+*":
|
|
||||||
return Domain.RPE
|
return Domain.RPE
|
||||||
break;
|
break;
|
||||||
case "RME":
|
case "RME":
|
||||||
|
@ -95,21 +93,16 @@ export class Domain {
|
||||||
case "R*-":
|
case "R*-":
|
||||||
case "ℝ⁻*":
|
case "ℝ⁻*":
|
||||||
case "ℝ*⁻":
|
case "ℝ*⁻":
|
||||||
case "ℝ-*":
|
|
||||||
case "ℝ*-":
|
|
||||||
return Domain.RME
|
return Domain.RME
|
||||||
break;
|
break;
|
||||||
case "ℕ":
|
case "ℕ":
|
||||||
case "N":
|
case "N":
|
||||||
case "ZP":
|
case "ZP":
|
||||||
case "Z+":
|
|
||||||
case "ℤ⁺":
|
case "ℤ⁺":
|
||||||
case "ℤ+":
|
|
||||||
return Domain.N
|
return Domain.N
|
||||||
break;
|
break;
|
||||||
case "NLOG":
|
case "NLOG":
|
||||||
case "ℕˡᵒᵍ":
|
case "ℕˡᵒᵍ":
|
||||||
case "ℕLOG":
|
|
||||||
return Domain.NLog
|
return Domain.NLog
|
||||||
break;
|
break;
|
||||||
case "NE":
|
case "NE":
|
||||||
|
@ -118,15 +111,12 @@ export class Domain {
|
||||||
case "N+":
|
case "N+":
|
||||||
case "ℕ*":
|
case "ℕ*":
|
||||||
case "ℕ⁺":
|
case "ℕ⁺":
|
||||||
case "ℕ+":
|
|
||||||
case "ZPE":
|
case "ZPE":
|
||||||
case "ZEP":
|
case "ZEP":
|
||||||
case "Z+*":
|
case "Z+*":
|
||||||
case "Z*+":
|
case "Z*+":
|
||||||
case "ℤ⁺*":
|
case "ℤ⁺*":
|
||||||
case "ℤ*⁺":
|
case "ℤ*⁺":
|
||||||
case "ℤ+*":
|
|
||||||
case "ℤ*+":
|
|
||||||
return Domain.NE
|
return Domain.NE
|
||||||
break;
|
break;
|
||||||
case "Z":
|
case "Z":
|
||||||
|
@ -136,7 +126,6 @@ export class Domain {
|
||||||
case "ZM":
|
case "ZM":
|
||||||
case "Z-":
|
case "Z-":
|
||||||
case "ℤ⁻":
|
case "ℤ⁻":
|
||||||
case "ℤ-":
|
|
||||||
return Domain.ZM
|
return Domain.ZM
|
||||||
break;
|
break;
|
||||||
case "ZME":
|
case "ZME":
|
||||||
|
@ -145,8 +134,6 @@ export class Domain {
|
||||||
case "Z*-":
|
case "Z*-":
|
||||||
case "ℤ⁻*":
|
case "ℤ⁻*":
|
||||||
case "ℤ*⁻":
|
case "ℤ*⁻":
|
||||||
case "ℤ-*":
|
|
||||||
case "ℤ*-":
|
|
||||||
return Domain.ZME
|
return Domain.ZME
|
||||||
break;
|
break;
|
||||||
case "ZE":
|
case "ZE":
|
||||||
|
@ -164,7 +151,7 @@ export class Domain {
|
||||||
/**
|
/**
|
||||||
* Represents an empty set.
|
* Represents an empty set.
|
||||||
*/
|
*/
|
||||||
export class EmptySet extends Domain {
|
class EmptySet extends Domain {
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.displayName = "∅"
|
this.displayName = "∅"
|
||||||
|
@ -185,12 +172,12 @@ export class EmptySet extends Domain {
|
||||||
/**
|
/**
|
||||||
* Domain classes for ranges (e.g ]0;3[, [1;2[ ...)
|
* Domain classes for ranges (e.g ]0;3[, [1;2[ ...)
|
||||||
*/
|
*/
|
||||||
export class Range extends Domain {
|
class Range extends Domain {
|
||||||
constructor(begin, end, openBegin, openEnd) {
|
constructor(begin, end, openBegin, openEnd) {
|
||||||
super()
|
super()
|
||||||
if(typeof begin == 'number' || typeof begin == 'string') begin = new Expression(begin.toString())
|
if(typeof begin == 'number' || typeof begin == 'string') begin = new Expr.Expression(begin.toString())
|
||||||
this.begin = begin
|
this.begin = begin
|
||||||
if(typeof end == 'number' || typeof end == 'string') end = new Expression(end.toString())
|
if(typeof end == 'number' || typeof end == 'string') end = new Expr.Expression(end.toString())
|
||||||
this.end = end
|
this.end = end
|
||||||
this.openBegin = openBegin
|
this.openBegin = openBegin
|
||||||
this.openEnd = openEnd
|
this.openEnd = openEnd
|
||||||
|
@ -199,8 +186,7 @@ export class Range extends Domain {
|
||||||
}
|
}
|
||||||
|
|
||||||
includes(x) {
|
includes(x) {
|
||||||
if(x instanceof Expression) x = x.execute()
|
if(typeof x == 'string') x = Expr.executeExpression(x)
|
||||||
if(typeof x == 'string') x = executeExpression(x)
|
|
||||||
return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) &&
|
return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) &&
|
||||||
((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute()))
|
((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute()))
|
||||||
}
|
}
|
||||||
|
@ -228,9 +214,9 @@ export class Range extends Domain {
|
||||||
}
|
}
|
||||||
|
|
||||||
static import(frm) {
|
static import(frm) {
|
||||||
let openBegin = frm.trim().charAt(0) === "]"
|
var openBegin = frm.trim().charAt(0) == "]"
|
||||||
let openEnd = frm.trim().charAt(frm.length -1) === "["
|
var openEnd = frm.trim().charAt(frm.length -1) == "["
|
||||||
let [begin, end] = frm.substr(1, frm.length-2).split(";")
|
var [begin, end] = frm.substr(1, frm.length-2).split(";")
|
||||||
return new Range(begin.trim(), end.trim(), openBegin, openEnd)
|
return new Range(begin.trim(), end.trim(), openBegin, openEnd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,16 +224,17 @@ export class Range extends Domain {
|
||||||
/**
|
/**
|
||||||
* Domain classes for special domains (N, Z, ...)
|
* Domain classes for special domains (N, Z, ...)
|
||||||
*/
|
*/
|
||||||
export class SpecialDomain extends Domain {
|
class SpecialDomain extends Domain {
|
||||||
/**
|
/**
|
||||||
* @constructs SpecialDomain
|
* @constructs SpecialDomain
|
||||||
* @param {string} displayName
|
* @param {string} displayName
|
||||||
* @param {function} isValid - function returning true when number is in domain false when it isn't.
|
* @param {function} isValid - function returning true when number is in domain false when it isn't.
|
||||||
* @param {function} next - function provides the next positive value in the domain after the one given.
|
* @param {function} next - function provides the next positive value in the domain after the one given.
|
||||||
* @param {function} previous - function provides the previous positive value in the domain before the one given.
|
* @param {function} previous - function provides the previous positive value in the domain before the one given.
|
||||||
* @param {boolean} moveSupported - Only true if next and previous functions are valid.
|
* @param {bool} moveSupported - Only true if next and previous functions are valid.
|
||||||
|
* @param items
|
||||||
*/
|
*/
|
||||||
constructor(displayName, isValid, next = () => true, previous = () => true,
|
constructor(displayName, isValid, next = x => true, previous = x => true,
|
||||||
moveSupported = true) {
|
moveSupported = true) {
|
||||||
super()
|
super()
|
||||||
this.displayName = displayName
|
this.displayName = displayName
|
||||||
|
@ -258,20 +245,17 @@ export class SpecialDomain extends Domain {
|
||||||
}
|
}
|
||||||
|
|
||||||
includes(x) {
|
includes(x) {
|
||||||
if(x instanceof Expression) x = x.execute()
|
if(typeof x == 'string') x = Expr.executeExpression(x)
|
||||||
if(typeof x == 'string') x = executeExpression(x)
|
|
||||||
return this.isValid(x)
|
return this.isValid(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
next(x) {
|
next(x) {
|
||||||
if(x instanceof Expression) x = x.execute()
|
if(typeof x == 'string') x = Expr.executeExpression(x)
|
||||||
if(typeof x == 'string') x = executeExpression(x)
|
|
||||||
return this.nextValue(x)
|
return this.nextValue(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
previous(x) {
|
previous(x) {
|
||||||
if(x instanceof Expression) x = x.execute()
|
if(typeof x == 'string') x = Expr.executeExpression(x)
|
||||||
if(typeof x == 'string') x = executeExpression(x)
|
|
||||||
return this.prevValue(x)
|
return this.prevValue(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,14 +285,14 @@ export class SpecialDomain extends Domain {
|
||||||
/**
|
/**
|
||||||
* Domain classes for sets (e.g {0;3}, {0;1;2;pi} ...)
|
* Domain classes for sets (e.g {0;3}, {0;1;2;pi} ...)
|
||||||
*/
|
*/
|
||||||
export class DomainSet extends SpecialDomain {
|
class DomainSet extends SpecialDomain {
|
||||||
constructor(values) {
|
constructor(values) {
|
||||||
super('', x => true, x => x, true)
|
super('', x => true, x => x, true)
|
||||||
let newVals = {}
|
var newVals = {}
|
||||||
this.executedValues = []
|
this.executedValues = []
|
||||||
for(let value of values) {
|
for(var value of values) {
|
||||||
let expr = new Expression(value.toString())
|
var expr = new Expr.Expression(value.toString())
|
||||||
let ex = expr.execute()
|
var ex = expr.execute()
|
||||||
newVals[ex] = expr
|
newVals[ex] = expr
|
||||||
this.executedValues.push(ex)
|
this.executedValues.push(ex)
|
||||||
}
|
}
|
||||||
|
@ -319,33 +303,30 @@ export class DomainSet extends SpecialDomain {
|
||||||
}
|
}
|
||||||
|
|
||||||
includes(x) {
|
includes(x) {
|
||||||
if(x instanceof Expression) x = x.execute()
|
if(typeof x == 'string') x = Expr.executeExpression(x)
|
||||||
if(typeof x == 'string') x = executeExpression(x)
|
for(var value of this.values)
|
||||||
for(let value of this.values)
|
if(x == value.execute()) return true
|
||||||
if(x === value.execute()) return true
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
next(x) {
|
next(x) {
|
||||||
if(x instanceof Expression) x = x.execute()
|
if(typeof x == 'string') x = Expr.executeExpression(x)
|
||||||
if(typeof x == 'string') x = executeExpression(x)
|
|
||||||
if(x < this.executedValues[0]) return this.executedValues[0]
|
if(x < this.executedValues[0]) return this.executedValues[0]
|
||||||
for(let i = 1; i < this.values.length; i++) {
|
for(var i = 1; i < this.values.length; i++) {
|
||||||
let prevValue = this.executedValues[i-1]
|
var prevValue = this.executedValues[i-1]
|
||||||
let value = this.executedValues[i]
|
var value = this.executedValues[i]
|
||||||
if(x >= prevValue && x < value) return value
|
if(x >= prevValue && x < value) return value
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
previous(x) {
|
previous(x) {
|
||||||
if(x instanceof Expression) x = x.execute()
|
if(typeof x == 'string') x = Expr.executeExpression(x)
|
||||||
if(typeof x == 'string') x = executeExpression(x)
|
|
||||||
if(x > this.executedValues[this.executedValues.length-1])
|
if(x > this.executedValues[this.executedValues.length-1])
|
||||||
return this.executedValues[this.executedValues.length-1]
|
return this.executedValues[this.executedValues.length-1]
|
||||||
for(let i = 1; i < this.values.length; i++) {
|
for(var i = 1; i < this.values.length; i++) {
|
||||||
let prevValue = this.executedValues[i-1]
|
var prevValue = this.executedValues[i-1]
|
||||||
let value = this.executedValues[i]
|
var value = this.executedValues[i]
|
||||||
if(x > prevValue && x <= value) return prevValue
|
if(x > prevValue && x <= value) return prevValue
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
@ -358,56 +339,56 @@ export class DomainSet extends SpecialDomain {
|
||||||
union(domain) {
|
union(domain) {
|
||||||
if(domain instanceof EmptySet) return this
|
if(domain instanceof EmptySet) return this
|
||||||
if(domain instanceof DomainSet) {
|
if(domain instanceof DomainSet) {
|
||||||
let newValues = []
|
var newValues = []
|
||||||
let values = this.values.concat(domain.values).filter(function(val){
|
var values = this.values.concat(domain.values).filter(function(val){
|
||||||
newValues.push(val.execute())
|
newValues.push(val.execute())
|
||||||
return newValues.indexOf(val.execute()) === newValues.length - 1
|
return newValues.indexOf(val.execute()) == newValues.length - 1
|
||||||
})
|
})
|
||||||
return new DomainSet(values)
|
return new DomainSet(values)
|
||||||
}
|
}
|
||||||
let notIncludedValues = []
|
var notIncludedValues = []
|
||||||
for(let i = 0; i < this.values.length; i++) {
|
for(var value in this.values) {
|
||||||
let value = this.executedValues[i]
|
var value = this.executedValues[i]
|
||||||
if(domain instanceof Range) {
|
if(domain instanceof Range) {
|
||||||
if(domain.begin.execute() === value && domain.openBegin) {
|
if(domain.begin.execute() == value && domain.openBegin) {
|
||||||
domain.openBegin = false
|
domain.openBegin = false
|
||||||
}
|
}
|
||||||
if(domain.end.execute() === value && domain.openEnd) {
|
if(domain.end.execute() == value && domain.openEnd) {
|
||||||
domain.openEnd = false
|
domain.openEnd = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!domain.includes(value))
|
if(!domain.includes(value))
|
||||||
notIncludedValues.push(this.values[i].toEditableString())
|
notIncludedValues.push(this.values[i].toEditableString())
|
||||||
}
|
}
|
||||||
if(notIncludedValues.length === 0) return domain
|
if(notIncludedValues.length == 0) return domain
|
||||||
return new UnionDomain(domain, new DomainSet(notIncludedValues))
|
return new UnionDomain(domain, new DomainSet(notIncludedValues))
|
||||||
}
|
}
|
||||||
|
|
||||||
intersection(domain) {
|
intersection(domain) {
|
||||||
if(domain instanceof EmptySet) return domain
|
if(domain instanceof EmptySet) return domain
|
||||||
if(domain instanceof DomainSet) {
|
if(domain instanceof DomainSet) {
|
||||||
let domValues = domain.values.map(expr => expr.execute())
|
var domValues = domain.values.map(expr => expr.execute())
|
||||||
this.values = this.values.filter(function(val){
|
this.values = this.values.filter(function(val){
|
||||||
return domValues.indexOf(val.execute()) >= 0
|
return domValues.indexOf(val.execute()) >= 0
|
||||||
})
|
})
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
let includedValues = []
|
var includedValues = []
|
||||||
for(let i in this.values) {
|
for(var i in this.values) {
|
||||||
let value = this.executedValues[i]
|
var value = this.executedValues[i]
|
||||||
if(domain instanceof Range) {
|
if(domain instanceof Range) {
|
||||||
if(domain.begin.execute() === value && !domain.openBegin) {
|
if(domain.begin.execute() == value && !domain.openBegin) {
|
||||||
domain.openBegin = false
|
domain.openBegin = false
|
||||||
}
|
}
|
||||||
if(domain.end.execute() === value && !domain.openEnd) {
|
if(domain.end.execute() == value && !domain.openEnd) {
|
||||||
domain.openEnd = false
|
domain.openEnd = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(domain.includes(value))
|
if(domain.includes(value))
|
||||||
includedValues.push(this.values[i].toEditableString())
|
includedValues.push(this.values[i].toEditableString())
|
||||||
}
|
}
|
||||||
if(includedValues.length === 0) return new EmptySet()
|
if(includedValues.length == 0) return new EmptySet()
|
||||||
if(includedValues.length === this.values.length) return this
|
if(includedValues.length == this.values.length) return this
|
||||||
return new IntersectionDomain(domain, new DomainSet(includedValues))
|
return new IntersectionDomain(domain, new DomainSet(includedValues))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,7 +400,7 @@ export class DomainSet extends SpecialDomain {
|
||||||
/**
|
/**
|
||||||
* Domain representing the union between two domains.
|
* Domain representing the union between two domains.
|
||||||
*/
|
*/
|
||||||
export class UnionDomain extends Domain {
|
class UnionDomain extends Domain {
|
||||||
constructor(dom1, dom2) {
|
constructor(dom1, dom2) {
|
||||||
super()
|
super()
|
||||||
this.dom1 = dom1
|
this.dom1 = dom1
|
||||||
|
@ -454,10 +435,10 @@ export class UnionDomain extends Domain {
|
||||||
}
|
}
|
||||||
|
|
||||||
static import(frm) {
|
static import(frm) {
|
||||||
let domains = frm.trim().split("∪")
|
var domains = frm.trim().split("∪")
|
||||||
if(domains.length === 1) domains = frm.trim().split("U") // Fallback
|
if(domains.length == 1) domains = frm.trim().split("U") // Fallback
|
||||||
let dom2 = parseDomain(domains.pop())
|
var dom1 = parseDomain(domains.pop())
|
||||||
let dom1 = parseDomain(domains.join('∪'))
|
var dom2 = parseDomain(domains.join('∪'))
|
||||||
return dom1.union(dom2)
|
return dom1.union(dom2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -465,7 +446,7 @@ export class UnionDomain extends Domain {
|
||||||
/**
|
/**
|
||||||
* Domain representing the intersection between two domains.
|
* Domain representing the intersection between two domains.
|
||||||
*/
|
*/
|
||||||
export class IntersectionDomain extends Domain {
|
class IntersectionDomain extends Domain {
|
||||||
constructor(dom1, dom2) {
|
constructor(dom1, dom2) {
|
||||||
super()
|
super()
|
||||||
this.dom1 = dom1
|
this.dom1 = dom1
|
||||||
|
@ -500,9 +481,9 @@ export class IntersectionDomain extends Domain {
|
||||||
}
|
}
|
||||||
|
|
||||||
static import(frm) {
|
static import(frm) {
|
||||||
let domains = frm.trim().split("∩")
|
var domains = frm.trim().split("∩")
|
||||||
let dom1 = parseDomain(domains.pop())
|
var dom1 = parseDomain(domains.pop())
|
||||||
let dom2 = parseDomain(domains.join('∩'))
|
var dom2 = parseDomain(domains.join('∩'))
|
||||||
return dom1.intersection(dom2)
|
return dom1.intersection(dom2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,7 +491,7 @@ export class IntersectionDomain extends Domain {
|
||||||
/**
|
/**
|
||||||
* Domain representing the minus between two domains.
|
* Domain representing the minus between two domains.
|
||||||
*/
|
*/
|
||||||
export class MinusDomain extends Domain {
|
class MinusDomain extends Domain {
|
||||||
constructor(dom1, dom2) {
|
constructor(dom1, dom2) {
|
||||||
super()
|
super()
|
||||||
this.dom1 = dom1
|
this.dom1 = dom1
|
||||||
|
@ -528,10 +509,10 @@ export class MinusDomain extends Domain {
|
||||||
}
|
}
|
||||||
|
|
||||||
static import(frm) {
|
static import(frm) {
|
||||||
let domains = frm.trim().split("∖")
|
var domains = frm.trim().split("∖")
|
||||||
if(domains.length === 1) domains = frm.trim().split("\\") // Fallback
|
if(domains.length == 1) domains = frm.trim().split("\\") // Fallback
|
||||||
let dom1 = parseDomain(domains.shift())
|
var dom1 = parseDomain(domains.shift())
|
||||||
let dom2 = parseDomain(domains.join('∪'))
|
var dom2 = parseDomain(domains.join('∪'))
|
||||||
return new MinusDomain(dom1, dom2)
|
return new MinusDomain(dom1, dom2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -555,53 +536,53 @@ Domain.RPE.latexMarkup = "\\mathbb{R}^{+*}"
|
||||||
Domain.RME = new Range(-Infinity,0,true,true)
|
Domain.RME = new Range(-Infinity,0,true,true)
|
||||||
Domain.RME.displayName = "ℝ⁻*"
|
Domain.RME.displayName = "ℝ⁻*"
|
||||||
Domain.RME.latexMarkup = "\\mathbb{R}^{+*}"
|
Domain.RME.latexMarkup = "\\mathbb{R}^{+*}"
|
||||||
Domain.N = new SpecialDomain('ℕ', x => x%1===0 && x >= 0,
|
Domain.N = new SpecialDomain('ℕ', x => x%1==0 && x >= 0,
|
||||||
x => Math.max(Math.floor(x)+1, 0),
|
x => Math.max(Math.floor(x)+1, 0),
|
||||||
x => Math.max(Math.ceil(x)-1, 0))
|
x => Math.max(Math.ceil(x)-1, 0))
|
||||||
Domain.N.latexMarkup = "\\mathbb{N}"
|
Domain.N.latexMarkup = "\\mathbb{N}"
|
||||||
Domain.NE = new SpecialDomain('ℕ*', x => x%1===0 && x > 0,
|
Domain.NE = new SpecialDomain('ℕ*', x => x%1==0 && x > 0,
|
||||||
x => Math.max(Math.floor(x)+1, 1),
|
x => Math.max(Math.floor(x)+1, 1),
|
||||||
x => Math.max(Math.ceil(x)-1, 1))
|
x => Math.max(Math.ceil(x)-1, 1))
|
||||||
Domain.NE.latexMarkup = "\\mathbb{N}^{*}"
|
Domain.NE.latexMarkup = "\\mathbb{N}^{*}"
|
||||||
Domain.Z = new SpecialDomain('ℤ', x => x%1===0, x => Math.floor(x)+1, x => Math.ceil(x)-1)
|
Domain.Z = new SpecialDomain('ℤ', x => x%1==0, x => Math.floor(x)+1, x => Math.ceil(x)-1)
|
||||||
Domain.Z.latexMarkup = "\\mathbb{Z}"
|
Domain.Z.latexMarkup = "\\mathbb{Z}"
|
||||||
Domain.ZE = new SpecialDomain('ℤ*', x => x%1===0 && x !== 0,
|
Domain.ZE = new SpecialDomain('ℤ*', x => x%1==0 && x != 0,
|
||||||
x => Math.floor(x)+1 === 0 ? Math.floor(x)+2 : Math.floor(x)+1,
|
x => Math.floor(x)+1 == 0 ? Math.floor(x)+2 : Math.floor(x)+1,
|
||||||
x => Math.ceil(x)-1 === 0 ? Math.ceil(x)-2 : Math.ceil(x)-1)
|
x => Math.ceil(x)-1 == 0 ? Math.ceil(x)-2 : Math.ceil(x)-1)
|
||||||
Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}"
|
Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}"
|
||||||
Domain.ZM = new SpecialDomain('ℤ⁻', x => x%1===0 && x <= 0,
|
Domain.ZM = new SpecialDomain('ℤ⁻', x => x%1==0 && x <= 0,
|
||||||
x => Math.min(Math.floor(x)+1, 0),
|
x => Math.min(Math.floor(x)+1, 0),
|
||||||
x => Math.min(Math.ceil(x)-1, 0))
|
x => Math.min(Math.ceil(x)-1, 0))
|
||||||
Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}"
|
Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}"
|
||||||
Domain.ZME = new SpecialDomain('ℤ⁻*', x => x%1===0 && x < 0,
|
Domain.ZME = new SpecialDomain('ℤ⁻*', x => x%1==0 && x < 0,
|
||||||
x => Math.min(Math.floor(x)+1, -1),
|
x => Math.min(Math.floor(x)+1, -1),
|
||||||
x => Math.min(Math.ceil(x)-1, -1))
|
x => Math.min(Math.ceil(x)-1, -1))
|
||||||
Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}"
|
Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}"
|
||||||
Domain.NLog = new SpecialDomain('ℕˡᵒᵍ',
|
Domain.NLog = new SpecialDomain('ℕˡᵒᵍ',
|
||||||
x => x/Math.pow(10, x.toString().length-1) % 1 === 0 && x > 0,
|
x => x/Math.pow(10, x.toString().length-1) % 1 == 0 && x > 0,
|
||||||
function(x) {
|
function(x) {
|
||||||
let x10pow = Math.pow(10, x.toString().length-1)
|
var x10pow = Math.pow(10, x.toString().length-1)
|
||||||
return Math.max(1, (Math.floor(x/x10pow)+1)*x10pow)
|
return Math.max(1, (Math.floor(x/x10pow)+1)*x10pow)
|
||||||
},
|
},
|
||||||
function(x) {
|
function(x) {
|
||||||
let x10pow = Math.pow(10, x.toString().length-1)
|
var x10pow = Math.pow(10, x.toString().length-1)
|
||||||
return Math.max(1, (Math.ceil(x/x10pow)-1)*x10pow)
|
return Math.max(1, (Math.ceil(x/x10pow)-1)*x10pow)
|
||||||
})
|
})
|
||||||
Domain.NLog.latexMarkup = "\\mathbb{N}^{log}"
|
Domain.NLog.latexMarkup = "\\mathbb{N}^{log}"
|
||||||
|
|
||||||
let refedDomains = []
|
var refedDomains = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a domain, that can use parentheses.
|
* Parses a domain, that can use parenthesises.
|
||||||
* e.g (N ∪ [-1;0[) ∩ (Z \ {0;3})
|
* e.g (N ∪ [-1;0[) ∩ (Z \ {0;3})
|
||||||
* @param {string} domain - string of the domain to be parsed.
|
* @param {string} domain - string of the domain to be parsed.
|
||||||
* @returns {Domain} Parsed domain.
|
* @returns {Domain} Parsed domain.
|
||||||
*/
|
*/
|
||||||
export function parseDomain(domain) {
|
function parseDomain(domain) {
|
||||||
if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain)
|
if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain)
|
||||||
let domStr
|
var domStr
|
||||||
while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) {
|
while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) {
|
||||||
let dom = parseDomainSimple(domStr[1].trim());
|
var dom = parseDomainSimple(domStr[1].trim());
|
||||||
domain = domain.replace(domStr[0], 'D' + refedDomains.length)
|
domain = domain.replace(domStr[0], 'D' + refedDomains.length)
|
||||||
refedDomains.push(dom)
|
refedDomains.push(dom)
|
||||||
}
|
}
|
||||||
|
@ -609,20 +590,20 @@ export function parseDomain(domain) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a domain, without parentheses.
|
* Parses a domain, without parenthesises.
|
||||||
* e.g N ∪ [-1;0[, Z \ {0;3}, N+*...
|
* e.g N ∪ [-1;0[, Z \ {0;3}, N+*...
|
||||||
* @param {string} domain - string of the domain to be parsed.
|
* @param {string} domain - string of the domain to be parsed.
|
||||||
* @returns {Domain} Parsed domain.
|
* @returns {Domain} Parsed domain.
|
||||||
*/
|
*/
|
||||||
export function parseDomainSimple(domain) {
|
function parseDomainSimple(domain) {
|
||||||
domain = domain.trim()
|
domain = domain.trim()
|
||||||
if(domain.includes("U") || domain.includes("∪")) return UnionDomain.import(domain)
|
if(domain.includes("U") || domain.includes("∪")) return UnionDomain.import(domain)
|
||||||
if(domain.includes("∩")) return IntersectionDomain.import(domain)
|
if(domain.includes("∩")) return IntersectionDomain.import(domain)
|
||||||
if(domain.includes("∖") || domain.includes("\\")) return MinusDomain.import(domain)
|
if(domain.includes("∖") || domain.includes("\\")) return MinusDomain.import(domain)
|
||||||
if(domain.charAt(0) === "{" && domain.charAt(domain.length -1) === "}") return DomainSet.import(domain)
|
if(domain.charAt(0) == "{" && domain.charAt(domain.length -1) == "}") return DomainSet.import(domain)
|
||||||
if(domain.includes("]") || domain.includes("[")) return Range.import(domain)
|
if(domain.includes("]") || domain.includes("[")) return Range.import(domain)
|
||||||
if(["R", "ℝ", "N", "ℕ", "Z", "ℤ"].some(str => domain.toUpperCase().includes(str)))
|
if(["R", "ℝ", "N", "ℕ", "Z", "ℤ"].some(str => domain.toUpperCase().includes(str)))
|
||||||
return Domain.import(domain)
|
return Domain.import(domain)
|
||||||
if(domain[0] === 'D') return refedDomains[parseInt(domain.substr(1))]
|
if(domain[0] == 'D') return refedDomains[parseInt(domain.substr(1))]
|
||||||
return new EmptySet()
|
return new EmptySet()
|
||||||
}
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||||
|
* Copyright (C) 2022 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.pragma library
|
||||||
|
|
||||||
|
.import "common.js" as C
|
||||||
|
.import "latex.js" as Latex
|
||||||
|
.import "../utils.js" as Utils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents any kind of x-based or non variable based expression.
|
||||||
|
*/
|
||||||
|
class Expression {
|
||||||
|
constructor(expr) {
|
||||||
|
this.expr = expr
|
||||||
|
this.calc = C.parser.parse(expr).simplify()
|
||||||
|
this.cached = this.isConstant()
|
||||||
|
this.cachedValue = this.cached ? this.calc.evaluate(C.evalVariables) : null
|
||||||
|
this.latexMarkup = Latex.expression(this.calc.tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
isConstant() {
|
||||||
|
return !this.expr.includes("x") && !this.expr.includes("n")
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(x = 1) {
|
||||||
|
if(this.cached) return this.cachedValue
|
||||||
|
C.currentVars = Object.assign({'x': x}, C.evalVariables)
|
||||||
|
return this.calc.evaluate(C.currentVars)
|
||||||
|
}
|
||||||
|
|
||||||
|
simplify(x) {
|
||||||
|
var expr = this.calc.substitute('x', x).simplify()
|
||||||
|
if(expr.evaluate(C.evalVariables) == 0) return '0'
|
||||||
|
var str = Utils.makeExpressionReadable(expr.toString());
|
||||||
|
if(str != undefined && str.match(/^\d*\.\d+$/)) {
|
||||||
|
if(str.split('.')[1].split('0').length > 7) {
|
||||||
|
// Likely rounding error
|
||||||
|
str = parseFloat(str.substring(0, str.length-1)).toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
duplicate() {
|
||||||
|
return new Expression(this.toEditableString())
|
||||||
|
}
|
||||||
|
|
||||||
|
toEditableString() {
|
||||||
|
return this.calc.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(forceSign=false) {
|
||||||
|
var str = Utils.makeExpressionReadable(this.calc.toString())
|
||||||
|
if(str[0] != '-' && forceSign) str = '+' + str
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeExpression(expr){
|
||||||
|
return (new Expression(expr.toString())).execute()
|
||||||
|
}
|
|
@ -1,106 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import * as Utils from "../utils.mjs"
|
|
||||||
import Latex from "../module/latex.mjs"
|
|
||||||
import ExprParser from "../module/expreval.mjs"
|
|
||||||
import Objects from "../module/objects.mjs"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents any kind of x-based or non variable based expression.
|
|
||||||
*/
|
|
||||||
export class Expression {
|
|
||||||
constructor(expr) {
|
|
||||||
if(typeof expr === "string") {
|
|
||||||
this.expr = Utils.exponentsToExpression(expr)
|
|
||||||
this.calc = ExprParser.parse(this.expr).simplify()
|
|
||||||
} else {
|
|
||||||
// Passed an expression here directly.
|
|
||||||
this.calc = expr.simplify()
|
|
||||||
this.expr = expr.toString()
|
|
||||||
}
|
|
||||||
this.cached = this.isConstant()
|
|
||||||
this.cachedValue = null
|
|
||||||
if(this.cached && this.allRequirementsFullfilled())
|
|
||||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
|
||||||
this.latexMarkup = Latex.expression(this.calc.tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
variables() {
|
|
||||||
return this.calc.variables()
|
|
||||||
}
|
|
||||||
|
|
||||||
isConstant() {
|
|
||||||
let vars = this.calc.variables()
|
|
||||||
return !vars.includes("x") && !vars.includes("n")
|
|
||||||
}
|
|
||||||
|
|
||||||
requiredObjects() {
|
|
||||||
return this.calc.variables().filter(objName => objName !== "x" && objName !== "n")
|
|
||||||
}
|
|
||||||
|
|
||||||
allRequirementsFullfilled() {
|
|
||||||
return this.requiredObjects().every(objName => objName in Objects.currentObjectsByName)
|
|
||||||
}
|
|
||||||
|
|
||||||
undefinedVariables() {
|
|
||||||
return this.requiredObjects().filter(objName => !(objName in Objects.currentObjectsByName))
|
|
||||||
}
|
|
||||||
|
|
||||||
recache() {
|
|
||||||
if(this.cached)
|
|
||||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
|
||||||
}
|
|
||||||
|
|
||||||
execute(x = 1) {
|
|
||||||
if(this.cached) {
|
|
||||||
if(this.cachedValue == null)
|
|
||||||
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
|
|
||||||
return this.cachedValue
|
|
||||||
}
|
|
||||||
ExprParser.currentVars = Object.assign({'x': x}, Objects.currentObjectsByName)
|
|
||||||
return this.calc.evaluate(ExprParser.currentVars)
|
|
||||||
}
|
|
||||||
|
|
||||||
simplify(x) {
|
|
||||||
let expr = this.calc.substitute('x', x).simplify()
|
|
||||||
if(expr.evaluate() === 0) expr = '0'
|
|
||||||
return new Expression(expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
toEditableString() {
|
|
||||||
return this.calc.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(forceSign=false) {
|
|
||||||
let str = Utils.makeExpressionReadable(this.calc.toString())
|
|
||||||
if(str !== undefined && str.match(/^\d*\.\d+$/)) {
|
|
||||||
if(str.split('.')[1].split('0').length > 7) {
|
|
||||||
// Likely rounding error
|
|
||||||
str = parseFloat(str.substring(0, str.length-1)).toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(str[0] !== '-' && forceSign) str = '+' + str
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeExpression(expr){
|
|
||||||
return (new Expression(expr.toString())).execute()
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
import * as Expr from "./expression.mjs"
|
|
||||||
import * as Seq from "./sequence.mjs"
|
|
||||||
import * as Dom from "./domain.mjs"
|
|
||||||
|
|
||||||
|
|
||||||
export const Expression = Expr.Expression
|
|
||||||
export const executeExpression = Expr.executeExpression
|
|
||||||
export const Sequence = Seq.Sequence
|
|
||||||
|
|
||||||
// Domains
|
|
||||||
export const Domain = Dom.Domain
|
|
||||||
export const EmptySet = Dom.EmptySet
|
|
||||||
export const Range = Dom.Range
|
|
||||||
export const SpecialDomain = Dom.SpecialDomain
|
|
||||||
export const DomainSet = Dom.DomainSet
|
|
||||||
export const UnionDomain = Dom.UnionDomain
|
|
||||||
export const IntersectionDomain = Dom.IntersectionDomain
|
|
||||||
export const MinusDomain = Dom.MinusDomain
|
|
||||||
|
|
||||||
export const parseDomain = Dom.parseDomain
|
|
||||||
export const parseDomainSimple = Dom.parseDomainSimple
|
|
260
LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
/**
|
||||||
|
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
||||||
|
* Copyright (C) 2022 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.pragma library
|
||||||
|
|
||||||
|
.import "../expr-eval.js" as ExprEval
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* true if latex has been enabled by the user, false otherwise.
|
||||||
|
*/
|
||||||
|
var enabled = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts element within parenthesis.
|
||||||
|
*
|
||||||
|
* @param {string} elem - element to put within parenthesis.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function par(elem) {
|
||||||
|
return '(' + elem + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the element contains at least one of the elements of
|
||||||
|
* the string array contents, but not at the first position of the string,
|
||||||
|
* and returns the parenthesis version if so.
|
||||||
|
*
|
||||||
|
* @param {string} elem - element to put within parenthesis.
|
||||||
|
* @param {Array} contents - Array of elements to put within parenthesis.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function parif(elem, contents) {
|
||||||
|
elem = elem.toString()
|
||||||
|
if(elem[0] != "(" && elem[elem.length-1] != ")" && contents.some(x => elem.indexOf(x) > 0))
|
||||||
|
return par(elem)
|
||||||
|
if(elem[0] == "(" && elem[elem.length-1] == ")")
|
||||||
|
return elem.substr(1, elem.length-2)
|
||||||
|
return elem
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a latex expression for a function.
|
||||||
|
*
|
||||||
|
* @param {string} f - Function to convert
|
||||||
|
* @param {Array} args - Arguments of the function
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function functionToLatex(f, args) {
|
||||||
|
switch(f) {
|
||||||
|
case "derivative":
|
||||||
|
return '\\frac{d' + args[0].substr(1, args[0].length-2).replace(new RegExp(args[1].substr(1, args[1].length-2), 'g'), 'x') + '}{dx}';
|
||||||
|
break;
|
||||||
|
case "integral":
|
||||||
|
return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2].substr(1, args[2].length-2) + ' d' + args[3].substr(1, args[3].length-2);
|
||||||
|
break;
|
||||||
|
case "sqrt":
|
||||||
|
return '\\sqrt\\left(' + args.join(', ') + '\\right)';
|
||||||
|
break;
|
||||||
|
case "abs":
|
||||||
|
return '\\left|' + args.join(', ') + '\\right|';
|
||||||
|
break;
|
||||||
|
case "floor":
|
||||||
|
return '\\left\\lfloor' + args.join(', ') + '\\right\\rfloor';
|
||||||
|
break;
|
||||||
|
case "ceil":
|
||||||
|
return '\\left\\lceil' + args.join(', ') + '\\right\\rceil';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return '\\mathrm{' + f + '}\\left(' + args.join(', ') + '\\right)';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a latex variable from a variable.
|
||||||
|
*
|
||||||
|
* @param {string} vari - variable to convert
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function variable(vari) {
|
||||||
|
let unicodechars = ["α","β","γ","δ","ε","ζ","η",
|
||||||
|
"π","θ","κ","λ","μ","ξ","ρ",
|
||||||
|
"ς","σ","τ","φ","χ","ψ","ω",
|
||||||
|
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
|
||||||
|
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
|
||||||
|
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
|
||||||
|
"ₜ","¹","²","³","⁴","⁵","⁶",
|
||||||
|
"⁷","⁸","⁹","⁰","₁","₂","₃",
|
||||||
|
"₄","₅","₆","₇","₈","₉","₀",
|
||||||
|
"pi"]
|
||||||
|
let equivalchars = ["\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta","\\eta",
|
||||||
|
"\\pi","\\theta","\\kappa","\\lambda","\\mu","\\xi","\\rho",
|
||||||
|
"\\sigma","\\sigma","\\tau","\\phi","\\chi","\\psi","\\omega",
|
||||||
|
"\\Gamma","\\Delta","\\Theta","\\Lambda","\\Xi","\\Pi","\\Sigma",
|
||||||
|
"\\Phy","\\Psi","\\Omega","{}_{a}","{}_{e}","{}_{o}","{}_{x}",
|
||||||
|
"{}_{h}","{}_{k}","{}_{l}","{}_{m}","{}_{n}","{}_{p}","{}_{s}",
|
||||||
|
"{}_{t}","{}^{1}","{}^{2}","{}^{3}","{}^{4}","{}^{5}","{}^{6}",
|
||||||
|
"{}^{7}","{}^{8}","{}^{9}","{}^{0}","{}_{1}","{}_{2}","{}_{3}",
|
||||||
|
"{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}",
|
||||||
|
"\\pi"]
|
||||||
|
for(let i = 0; i < unicodechars.length; i++) {
|
||||||
|
//console.log(vari, unicodechars[i], equivalchars[i]);
|
||||||
|
if(vari.includes(unicodechars[i]))
|
||||||
|
vari = vari.replace(new RegExp(unicodechars[i], 'g'), equivalchars[i])
|
||||||
|
}
|
||||||
|
return vari;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts expr-eval tokens to a latex string.
|
||||||
|
*
|
||||||
|
* @param {Array} tokens - expr-eval tokens list
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function expression(tokens) {
|
||||||
|
var nstack = [];
|
||||||
|
var n1, n2, n3;
|
||||||
|
var f, args, argCount;
|
||||||
|
for (var i = 0; i < tokens.length; i++) {
|
||||||
|
var item = tokens[i];
|
||||||
|
var type = item.type;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case ExprEval.INUMBER:
|
||||||
|
if (typeof item.value === 'number' && item.value < 0) {
|
||||||
|
nstack.push(par(item.value));
|
||||||
|
} else if (Array.isArray(item.value)) {
|
||||||
|
nstack.push('[' + item.value.map(ExprEval.escapeValue).join(', ') + ']');
|
||||||
|
} else {
|
||||||
|
nstack.push(ExprEval.escapeValue(item.value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ExprEval.IOP2:
|
||||||
|
n2 = nstack.pop();
|
||||||
|
n1 = nstack.pop();
|
||||||
|
f = item.value;
|
||||||
|
switch(f) {
|
||||||
|
case '-':
|
||||||
|
case '+':
|
||||||
|
nstack.push(n1 + f + n2);
|
||||||
|
break;
|
||||||
|
case '||':
|
||||||
|
case 'or':
|
||||||
|
case '&&':
|
||||||
|
case 'and':
|
||||||
|
case '==':
|
||||||
|
case '!=':
|
||||||
|
nstack.push(par(n1) + f + par(n2));
|
||||||
|
break;
|
||||||
|
case '*':
|
||||||
|
if(n2 == "\\pi" || n2 == "e" || n2 == "x" || n2 == "n")
|
||||||
|
nstack.push(parif(n1,['+','-']) + n2)
|
||||||
|
else
|
||||||
|
nstack.push(parif(n1,['+','-']) + " \\times " + parif(n2,['+','-']));
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
nstack.push("\\frac{" + n1 + "}{" + n2 + "}");
|
||||||
|
break;
|
||||||
|
case '^':
|
||||||
|
nstack.push(parif(n1,['+','-','*','/','!']) + "^{" + n2 + "}");
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
nstack.push(parif(n1,['+','-','*','/','!','^']) + " \\mathrm{mod} " + parif(n2,['+','-','*','/','!','^']));
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
nstack.push(n1 + '[' + n2 + ']');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new EvalError("Unknown operator " + ope + ".");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ExprEval.IOP3: // Thirdiary operator
|
||||||
|
n3 = nstack.pop();
|
||||||
|
n2 = nstack.pop();
|
||||||
|
n1 = nstack.pop();
|
||||||
|
f = item.value;
|
||||||
|
if (f === '?') {
|
||||||
|
nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')');
|
||||||
|
} else {
|
||||||
|
throw new EvalError('Unknown operator ' + ope + '.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ExprEval.IVAR:
|
||||||
|
case ExprEval.IVARNAME:
|
||||||
|
nstack.push(variable(item.value.toString()));
|
||||||
|
break;
|
||||||
|
case ExprEval.IOP1: // Unary operator
|
||||||
|
n1 = nstack.pop();
|
||||||
|
f = item.value;
|
||||||
|
switch(f) {
|
||||||
|
case '-':
|
||||||
|
case '+':
|
||||||
|
nstack.push(par(f + n1));
|
||||||
|
break;
|
||||||
|
case '!':
|
||||||
|
nstack.push(parif(n1,['+','-','*','/','^']) + '!');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
nstack.push(f + parif(n1,['+','-','*','/','^']));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ExprEval.IFUNCALL:
|
||||||
|
argCount = item.value;
|
||||||
|
args = [];
|
||||||
|
while (argCount-- > 0) {
|
||||||
|
args.unshift(nstack.pop());
|
||||||
|
}
|
||||||
|
f = nstack.pop();
|
||||||
|
// Handling various functions
|
||||||
|
nstack.push(functionToLatex(f, args))
|
||||||
|
break;
|
||||||
|
case ExprEval.IFUNDEF:
|
||||||
|
nstack.push(par(n1 + '(' + args.join(', ') + ') = ' + n2));
|
||||||
|
break;
|
||||||
|
case ExprEval.IMEMBER:
|
||||||
|
n1 = nstack.pop();
|
||||||
|
nstack.push(n1 + '.' + item.value);
|
||||||
|
break;
|
||||||
|
case ExprEval.IARRAY:
|
||||||
|
argCount = item.value;
|
||||||
|
args = [];
|
||||||
|
while (argCount-- > 0) {
|
||||||
|
args.unshift(nstack.pop());
|
||||||
|
}
|
||||||
|
nstack.push('[' + args.join(', ') + ']');
|
||||||
|
break;
|
||||||
|
case ExprEval.IEXPR:
|
||||||
|
nstack.push('(' + expression(item.value) + ')');
|
||||||
|
break;
|
||||||
|
case ExprEval.IENDSTATEMENT:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new EvalError('invalid Expression');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nstack.length > 1) {
|
||||||
|
nstack = [ nstack.join(';') ]
|
||||||
|
}
|
||||||
|
return String(nstack[0]);
|
||||||
|
}
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,16 +16,18 @@
|
||||||
* 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 * as Expr from "expression.mjs"
|
.pragma library
|
||||||
import * as Utils from "../utils.mjs"
|
|
||||||
import Latex from "../module/latex.mjs"
|
.import "common.js" as C
|
||||||
import Objects from "../module/objects.mjs"
|
.import "expression.js" as Expr
|
||||||
import ExprParser from "../module/expreval.mjs"
|
.import "../utils.js" as Utils
|
||||||
|
.import "../math/latex.js" as Latex
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents mathematical object for sequences.
|
* Represents mathematical object for sequences.
|
||||||
*/
|
*/
|
||||||
export class Sequence extends Expr.Expression {
|
class Sequence extends Expr.Expression {
|
||||||
constructor(name, baseValues = {}, valuePlus = 1, expr = "") {
|
constructor(name, baseValues = {}, valuePlus = 1, expr = "") {
|
||||||
// u[n+valuePlus] = expr
|
// u[n+valuePlus] = expr
|
||||||
super(expr)
|
super(expr)
|
||||||
|
@ -33,17 +35,17 @@ export class Sequence extends Expr.Expression {
|
||||||
this.baseValues = baseValues
|
this.baseValues = baseValues
|
||||||
this.calcValues = Object.assign({}, baseValues)
|
this.calcValues = Object.assign({}, baseValues)
|
||||||
this.latexValues = Object.assign({}, baseValues)
|
this.latexValues = Object.assign({}, baseValues)
|
||||||
for(let n in this.calcValues)
|
for(var n in this.calcValues)
|
||||||
if(['string', 'number'].includes(typeof this.calcValues[n])) {
|
if(['string', 'number'].includes(typeof this.calcValues[n])) {
|
||||||
let parsed = ExprParser.parse(this.calcValues[n].toString()).simplify()
|
let parsed = C.parser.parse(this.calcValues[n].toString()).simplify()
|
||||||
this.latexValues[n] = Latex.expression(parsed.tokens)
|
this.latexValues[n] = Latex.expression(parsed.tokens)
|
||||||
this.calcValues[n] = parsed.evaluate()
|
this.calcValues[n] = parsed.evaluate(C.evalVariables)
|
||||||
}
|
}
|
||||||
this.valuePlus = parseInt(valuePlus)
|
this.valuePlus = parseInt(valuePlus)
|
||||||
}
|
}
|
||||||
|
|
||||||
isConstant() {
|
isConstant() {
|
||||||
return this.expr.indexOf("n") === -1
|
return this.expr.indexOf("n") == -1
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(n = 1) {
|
execute(n = 1) {
|
||||||
|
@ -54,31 +56,26 @@ export class Sequence extends Expr.Expression {
|
||||||
}
|
}
|
||||||
|
|
||||||
simplify(n = 1) {
|
simplify(n = 1) {
|
||||||
if(!(n in this.calcValues))
|
if(n in this.calcValues)
|
||||||
this.cache(n)
|
return Utils.makeExpressionReadable(this.calcValues[n].toString())
|
||||||
return this.calcValues[n].toString()
|
this.cache(n)
|
||||||
|
return Utils.makeExpressionReadable(this.calcValues[n].toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
cache(n = 1) {
|
cache(n = 1) {
|
||||||
let str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString())
|
var str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString())
|
||||||
let expr = ExprParser.parse(str).simplify()
|
var expr = C.parser.parse(str).simplify()
|
||||||
// Cache values required for this one.
|
var l = {'n': n-this.valuePlus} // Just in case, add n (for custom functions)
|
||||||
if(!this.calcValues[n-this.valuePlus] && n-this.valuePlus > 0)
|
l[this.name] = this.calcValues
|
||||||
this.cache(n-this.valuePlus)
|
C.currentVars = Object.assign(l, C.evalVariables)
|
||||||
// Setting current variables
|
this.calcValues[n] = expr.evaluate(C.currentVars)
|
||||||
ExprParser.currentVars = Object.assign(
|
|
||||||
{'n': n-this.valuePlus}, // Just in case, add n (for custom functions)
|
|
||||||
Objects.currentObjectsByName,
|
|
||||||
{[this.name]: this.calcValues}
|
|
||||||
)
|
|
||||||
this.calcValues[n] = expr.evaluate(ExprParser.currentVars)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(forceSign=false) {
|
toString(forceSign=false) {
|
||||||
let str = Utils.makeExpressionReadable(this.calc.toString())
|
var str = Utils.makeExpressionReadable(this.calc.toString())
|
||||||
if(str[0] !== '-' && forceSign) str = '+' + str
|
if(str[0] != '-' && forceSign) str = '+' + str
|
||||||
let subtxt = this.valuePlus === 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus)
|
var subtxt = this.valuePlus == 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus)
|
||||||
let ret = `${this.name}${subtxt} = ${str}${this.baseValues.length === 0 ? '' : "\n"}`
|
var ret = `${this.name}${subtxt} = ${str}${this.baseValues.length == 0 ? '' : "\n"}`
|
||||||
ret += Object.keys(this.baseValues).map(
|
ret += Object.keys(this.baseValues).map(
|
||||||
n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}`
|
n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}`
|
||||||
).join('; ')
|
).join('; ')
|
||||||
|
@ -86,10 +83,10 @@ export class Sequence extends Expr.Expression {
|
||||||
}
|
}
|
||||||
|
|
||||||
toLatexString(forceSign=false) {
|
toLatexString(forceSign=false) {
|
||||||
let str = this.latexMarkup
|
var str = this.latexMarkup
|
||||||
if(str[0] !== '-' && forceSign) str = '+' + str
|
if(str[0] != '-' && forceSign) str = '+' + str
|
||||||
let subtxt = '_{n' + (this.valuePlus === 0 ? '' : '+' + this.valuePlus) + '}'
|
var subtxt = '_{n' + (this.valuePlus == 0 ? '' : '+' + this.valuePlus) + '}'
|
||||||
let ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length === 0 ? '' : "\n"}\\\\`
|
var ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length == 0 ? '' : "\n"}\\\\`
|
||||||
ret += Object.keys(this.latexValues).map(
|
ret += Object.keys(this.latexValues).map(
|
||||||
n => `${this.name}_{${n}} = ${this.latexValues[n]}`
|
n => `${this.name}_{${n}} = ${this.latexValues[n]}`
|
||||||
).join('; ') + "\\end{array}"
|
).join('; ') + "\\end{array}"
|
|
@ -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-2024 Ad5001
|
* Copyright (C) 2022 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,17 +16,26 @@
|
||||||
* 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 * as Reference from "reference.mjs"
|
.pragma library
|
||||||
import * as T from "./tokenizer.mjs"
|
|
||||||
import InputExpression from "common.mjs"
|
|
||||||
|
|
||||||
export const Input = InputExpression
|
.import "math/expression.js" as Expr
|
||||||
export const TokenType = T.TokenType
|
.import "math/sequence.js" as Seq
|
||||||
export const Token = T.Token
|
|
||||||
export const Tokenizer = T.ExpressionTokenizer
|
|
||||||
|
|
||||||
export const FUNCTIONS_LIST = Reference.FUNCTIONS_LIST
|
.import "math/domain.js" as Dom
|
||||||
export const FUNCTIONS = Reference.FUNCTIONS
|
|
||||||
export const FUNCTIONS_USAGE = Reference.FUNCTIONS_USAGE
|
var Expression = Expr.Expression
|
||||||
export const CONSTANTS_LIST = Reference.CONSTANTS_LIST
|
var executeExpression = Expr.executeExpression
|
||||||
export const CONSTANTS = Reference.CONSTANTS
|
var Sequence = Seq.Sequence
|
||||||
|
|
||||||
|
// Domains
|
||||||
|
var Domain = Dom.Domain
|
||||||
|
var EmptySet = Dom.EmptySet
|
||||||
|
var Range = Dom.Range
|
||||||
|
var SpecialDomain = Dom.SpecialDomain
|
||||||
|
var DomainSet = Dom.DomainSet
|
||||||
|
var UnionDomain = Dom.UnionDomain
|
||||||
|
var IntersectionDomain = Dom.IntersectionDomain
|
||||||
|
var MinusDomain = Dom.MinusDomain
|
||||||
|
|
||||||
|
var parseDomain = Dom.parseDomain
|
||||||
|
var parseDomainSimple = Dom.parseDomainSimple
|
|
@ -1,594 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Module } from "./common.mjs"
|
|
||||||
import { FUNCTION, Interface, CanvasInterface, DialogInterface } from "./interface.mjs"
|
|
||||||
import { textsup } from "../utils.mjs"
|
|
||||||
import { Expression } from "../math/index.mjs"
|
|
||||||
import Latex from "./latex.mjs"
|
|
||||||
import Objects from "./objects.mjs"
|
|
||||||
import History from "./history.mjs"
|
|
||||||
|
|
||||||
class CanvasAPI extends Module {
|
|
||||||
constructor() {
|
|
||||||
super("Canvas", {
|
|
||||||
canvas: CanvasInterface,
|
|
||||||
drawingErrorDialog: DialogInterface
|
|
||||||
})
|
|
||||||
|
|
||||||
/** @type {CanvasInterface} */
|
|
||||||
this._canvas = null
|
|
||||||
|
|
||||||
/** @type {CanvasRenderingContext2D} */
|
|
||||||
this._ctx = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {{show(string, string, string)}}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._drawingErrorDialog = null
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Object.<string, {expression: Expression, value: number, maxDraw: number}>}
|
|
||||||
*/
|
|
||||||
this.axesSteps = {
|
|
||||||
x: {
|
|
||||||
expression: null,
|
|
||||||
value: -1,
|
|
||||||
maxDraw: -1
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
expression: null,
|
|
||||||
value: -1,
|
|
||||||
maxDraw: -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the module.
|
|
||||||
* @param {CanvasInterface} canvas
|
|
||||||
* @param {{show(string, string, string)}} drawingErrorDialog
|
|
||||||
*/
|
|
||||||
initialize({ canvas, drawingErrorDialog }) {
|
|
||||||
super.initialize({ canvas, drawingErrorDialog })
|
|
||||||
this._canvas = canvas
|
|
||||||
this._drawingErrorDialog = drawingErrorDialog
|
|
||||||
}
|
|
||||||
|
|
||||||
get width() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting width before initialize!")
|
|
||||||
return this._canvas.width
|
|
||||||
}
|
|
||||||
|
|
||||||
get height() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting height before initialize!")
|
|
||||||
return this._canvas.height
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum x of the diagram, provided from settings.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get xmin() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting xmin before initialize!")
|
|
||||||
return this._canvas.xmin
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zoom on the x-axis of the diagram, provided from settings.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get xzoom() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting xzoom before initialize!")
|
|
||||||
return this._canvas.xzoom
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum y of the diagram, provided from settings.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get ymax() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting ymax before initialize!")
|
|
||||||
return this._canvas.ymax
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Zoom on the y-axis of the diagram, provided from settings.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get yzoom() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting yzoom before initialize!")
|
|
||||||
return this._canvas.yzoom
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label used on the x-axis, provided from settings.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get xlabel() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting xlabel before initialize!")
|
|
||||||
return this._canvas.xlabel
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Label used on the y-axis, provided from settings.
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
get ylabel() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting ylabel before initialize!")
|
|
||||||
return this._canvas.ylabel
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Width of lines that will be drawn into the canvas, provided from settings.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get linewidth() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting linewidth before initialize!")
|
|
||||||
return this._canvas.linewidth
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Font size of the text that will be drawn into the canvas, provided from settings.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get textsize() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting textsize before initialize!")
|
|
||||||
return this._canvas.textsize
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the canvas should be in logarithmic mode, false otherwise.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get logscalex() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting logscalex before initialize!")
|
|
||||||
return this._canvas.logscalex
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the x graduation should be shown, false otherwise.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get showxgrad() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting showxgrad before initialize!")
|
|
||||||
return this._canvas.showxgrad
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the y graduation should be shown, false otherwise.
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
get showygrad() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting showygrad before initialize!")
|
|
||||||
return this._canvas.showygrad
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Max power of the logarithmic scaled on the x axis in logarithmic mode.
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
get maxgradx() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting maxgradx before initialize!")
|
|
||||||
return Math.min(
|
|
||||||
309, // 10e309 = Infinity (beyond this land be dragons)
|
|
||||||
Math.max(
|
|
||||||
Math.ceil(Math.abs(Math.log10(this.xmin))),
|
|
||||||
Math.ceil(Math.abs(Math.log10(this.px2x(this.width))))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Methods to draw the canvas
|
|
||||||
//
|
|
||||||
|
|
||||||
requestPaint() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting requestPaint before initialize!")
|
|
||||||
this._canvas.requestPaint()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redraws the entire canvas
|
|
||||||
*/
|
|
||||||
redraw() {
|
|
||||||
if(!this.initialized) throw new Error("Attempting redraw before initialize!")
|
|
||||||
this._ctx = this._canvas.getContext("2d")
|
|
||||||
this._computeAxes()
|
|
||||||
this._reset()
|
|
||||||
this._drawGrid()
|
|
||||||
this._drawAxes()
|
|
||||||
this._drawLabels()
|
|
||||||
this._ctx.lineWidth = this.linewidth
|
|
||||||
for(let objType in Objects.currentObjects) {
|
|
||||||
for(let obj of Objects.currentObjects[objType]) {
|
|
||||||
this._ctx.strokeStyle = obj.color
|
|
||||||
this._ctx.fillStyle = obj.color
|
|
||||||
if(obj.visible)
|
|
||||||
try {
|
|
||||||
obj.draw(this)
|
|
||||||
} catch(e) {
|
|
||||||
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
|
|
||||||
console.error(e)
|
|
||||||
console.log(e.stack)
|
|
||||||
this._drawingErrorDialog.showDialog(objType, obj.name, e.message)
|
|
||||||
History.undo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._ctx.lineWidth = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates information for drawing gradations for axes.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_computeAxes() {
|
|
||||||
let exprY = new Expression(`x*(${this._canvas.yaxisstep})`)
|
|
||||||
let y1 = exprY.execute(1)
|
|
||||||
let exprX = new Expression(`x*(${this._canvas.xaxisstep})`)
|
|
||||||
let x1 = exprX.execute(1)
|
|
||||||
this.axesSteps = {
|
|
||||||
x: {
|
|
||||||
expression: exprX,
|
|
||||||
value: x1,
|
|
||||||
maxDraw: Math.ceil(Math.max(Math.abs(this.xmin), Math.abs(this.px2x(this.width))) / x1)
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
expression: exprY,
|
|
||||||
value: y1,
|
|
||||||
maxDraw: Math.ceil(Math.max(Math.abs(this.ymax), Math.abs(this.px2y(this.height))) / y1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the canvas to a blank one with default setting.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_reset() {
|
|
||||||
// Reset
|
|
||||||
this._ctx.fillStyle = "#FFFFFF"
|
|
||||||
this._ctx.strokeStyle = "#000000"
|
|
||||||
this._ctx.font = `${this.textsize}px sans-serif`
|
|
||||||
this._ctx.fillRect(0, 0, this.width, this.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the grid.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_drawGrid() {
|
|
||||||
this._ctx.strokeStyle = "#C0C0C0"
|
|
||||||
if(this.logscalex) {
|
|
||||||
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) {
|
|
||||||
for(let xmulti = 1; xmulti < 10; xmulti++) {
|
|
||||||
this.drawXLine(Math.pow(10, xpow) * xmulti)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(let x = 0; x < this.axesSteps.x.maxDraw; x += 1) {
|
|
||||||
this.drawXLine(x * this.axesSteps.x.value)
|
|
||||||
this.drawXLine(-x * this.axesSteps.x.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
|
|
||||||
this.drawYLine(y * this.axesSteps.y.value)
|
|
||||||
this.drawYLine(-y * this.axesSteps.y.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws the graph axes.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_drawAxes() {
|
|
||||||
this._ctx.strokeStyle = "#000000"
|
|
||||||
let axisypos = this.logscalex ? 1 : 0
|
|
||||||
this.drawXLine(axisypos)
|
|
||||||
this.drawYLine(0)
|
|
||||||
let axisypx = this.x2px(axisypos) // X coordinate of Y axis
|
|
||||||
let axisxpx = this.y2px(0) // Y coordinate of X axis
|
|
||||||
// Drawing arrows
|
|
||||||
this.drawLine(axisypx, 0, axisypx - 10, 10)
|
|
||||||
this.drawLine(axisypx, 0, axisypx + 10, 10)
|
|
||||||
this.drawLine(this.width, axisxpx, this.width - 10, axisxpx - 10)
|
|
||||||
this.drawLine(this.width, axisxpx, this.width - 10, axisxpx + 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the canvas to a blank one with default setting.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_drawLabels() {
|
|
||||||
let axisypx = this.x2px(this.logscalex ? 1 : 0) // X coordinate of Y axis
|
|
||||||
let axisxpx = this.y2px(0) // Y coordinate of X axis
|
|
||||||
// Labels
|
|
||||||
this._ctx.fillStyle = "#000000"
|
|
||||||
this._ctx.font = `${this.textsize}px sans-serif`
|
|
||||||
this._ctx.fillText(this.ylabel, axisypx + 10, 24)
|
|
||||||
let textWidth = this._ctx.measureText(this.xlabel).width
|
|
||||||
this._ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5)
|
|
||||||
// Axis graduation labels
|
|
||||||
this._ctx.font = `${this.textsize - 4}px sans-serif`
|
|
||||||
|
|
||||||
let txtMinus = this._ctx.measureText("-").width
|
|
||||||
if(this.showxgrad) {
|
|
||||||
if(this.logscalex) {
|
|
||||||
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow += 1) {
|
|
||||||
textWidth = this._ctx.measureText("10" + textsup(xpow)).width
|
|
||||||
if(xpow !== 0)
|
|
||||||
this.drawVisibleText("10" + textsup(xpow), this.x2px(Math.pow(10, xpow)) - textWidth / 2, axisxpx + 16 + (6 * (xpow === 1)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(let x = 1; x < this.axesSteps.x.maxDraw; x += 1) {
|
|
||||||
let drawX = x * this.axesSteps.x.value
|
|
||||||
let txtX = this.axesSteps.x.expression.simplify(x).toString().replace(/^\((.+)\)$/, "$1")
|
|
||||||
let textHeight = this.measureText(txtX).height
|
|
||||||
this.drawVisibleText(txtX, this.x2px(drawX) - 4, axisxpx + this.textsize / 2 + textHeight)
|
|
||||||
this.drawVisibleText("-" + txtX, this.x2px(-drawX) - 4, axisxpx + this.textsize / 2 + textHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(this.showygrad) {
|
|
||||||
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
|
|
||||||
let drawY = y * this.axesSteps.y.value
|
|
||||||
let txtY = this.axesSteps.y.expression.simplify(y).toString().replace(/^\((.+)\)$/, "$1")
|
|
||||||
textWidth = this._ctx.measureText(txtY).width
|
|
||||||
this.drawVisibleText(txtY, axisypx - 6 - textWidth, this.y2px(drawY) + 4 + (10 * (y === 0)))
|
|
||||||
if(y !== 0)
|
|
||||||
this.drawVisibleText("-" + txtY, axisypx - 6 - textWidth - txtMinus, this.y2px(-drawY) + 4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._ctx.fillStyle = "#FFFFFF"
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Public functions
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws an horizontal line at x plot coordinate.
|
|
||||||
* @param {number} x
|
|
||||||
*/
|
|
||||||
drawXLine(x) {
|
|
||||||
if(this.isVisible(x, this.ymax)) {
|
|
||||||
this.drawLine(this.x2px(x), 0, this.x2px(x), this.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws an vertical line at y plot coordinate
|
|
||||||
* @param {number} y
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
drawYLine(y) {
|
|
||||||
if(this.isVisible(this.xmin, y)) {
|
|
||||||
this.drawLine(0, this.y2px(y), this.width, this.y2px(y))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes multiline text onto the canvas.
|
|
||||||
* NOTE: The x and y properties here are relative to the canvas, not the plot.
|
|
||||||
* @param {string} text
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
*/
|
|
||||||
drawVisibleText(text, x, y) {
|
|
||||||
if(x > 0 && x < this.width && y > 0 && y < this.height) {
|
|
||||||
text.toString().split("\n").forEach((txt, i) => {
|
|
||||||
this._ctx.fillText(txt, x, y + (this.textsize * i))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws an image onto the canvas.
|
|
||||||
* NOTE: The x, y width and height properties here are relative to the canvas, not the plot.
|
|
||||||
* @param {CanvasImageSource} image
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
* @param {number} width
|
|
||||||
* @param {number} height
|
|
||||||
*/
|
|
||||||
drawVisibleImage(image, x, y, width, height) {
|
|
||||||
this._canvas.markDirty(Qt.rect(x, y, width, height))
|
|
||||||
this._ctx.drawImage(image, x, y, width, height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Measures the width and height of a multiline text that would be drawn onto the canvas.
|
|
||||||
* @param {string} text
|
|
||||||
* @returns {{width: number, height: number}}
|
|
||||||
*/
|
|
||||||
measureText(text) {
|
|
||||||
let theight = 0
|
|
||||||
let twidth = 0
|
|
||||||
let defaultHeight = this.textsize * 1.2 // Approximate but good enough!
|
|
||||||
for(let txt of text.split("\n")) {
|
|
||||||
theight += defaultHeight
|
|
||||||
if(this._ctx.measureText(txt).width > twidth) twidth = this._ctx.measureText(txt).width
|
|
||||||
}
|
|
||||||
return { "width": twidth, "height": theight }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an x coordinate to its relative position on the canvas.
|
|
||||||
* It supports both logarithmic and non-logarithmic scale depending on the currently selected mode.
|
|
||||||
* @param {number} x
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
x2px(x) {
|
|
||||||
if(this.logscalex) {
|
|
||||||
const logxmin = Math.log(this.xmin)
|
|
||||||
return (Math.log(x) - logxmin) * this.xzoom
|
|
||||||
} else
|
|
||||||
return (x - this.xmin) * this.xzoom
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an y coordinate to it's relative position on the canvas.
|
|
||||||
* The y-axis not supporting logarithmic scale, it only supports linear conversion.
|
|
||||||
* @param {number} y
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
y2px(y) {
|
|
||||||
return (this.ymax - y) * this.yzoom
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an x 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.
|
|
||||||
* @param {number} px
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
px2x(px) {
|
|
||||||
if(this.logscalex) {
|
|
||||||
return Math.exp(px / this.xzoom + Math.log(this.xmin))
|
|
||||||
} else
|
|
||||||
return (px / this.xzoom + this.xmin)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an x 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.
|
|
||||||
* @param {number} px
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
px2y(px) {
|
|
||||||
return -(px / this.yzoom - this.ymax)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a plot point (x, y) is visible or not on the canvas.
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isVisible(x, y) {
|
|
||||||
return (this.x2px(x) >= 0 && this.x2px(x) <= this.width) && (this.y2px(y) >= 0 && this.y2px(y) <= this.height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a line from plot point (x1, y1) to plot point (x2, y2).
|
|
||||||
* @param {number} x1
|
|
||||||
* @param {number} y1
|
|
||||||
* @param {number} x2
|
|
||||||
* @param {number} y2
|
|
||||||
*/
|
|
||||||
drawLine(x1, y1, x2, y2) {
|
|
||||||
this._ctx.beginPath()
|
|
||||||
this._ctx.moveTo(x1, y1)
|
|
||||||
this._ctx.lineTo(x2, y2)
|
|
||||||
this._ctx.stroke()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a dashed line from plot point (x1, y1) to plot point (x2, y2).
|
|
||||||
* @param {number} x1
|
|
||||||
* @param {number} y1
|
|
||||||
* @param {number} x2
|
|
||||||
* @param {number} y2
|
|
||||||
* @param {number} dashPxSize
|
|
||||||
*/
|
|
||||||
drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) {
|
|
||||||
this._ctx.setLineDash([dashPxSize / 2, dashPxSize])
|
|
||||||
this.drawLine(x1, y1, x2, y2)
|
|
||||||
this._ctx.setLineDash([])
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders latex markup ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
|
|
||||||
* @param {string} ltxText
|
|
||||||
* @param {string} color
|
|
||||||
* @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback
|
|
||||||
*/
|
|
||||||
renderLatexImage(ltxText, color, callback) {
|
|
||||||
const onRendered = (imgData) => {
|
|
||||||
if(!this._canvas.isImageLoaded(imgData.source) && !this._canvas.isImageLoading(imgData.source)) {
|
|
||||||
// Wait until the image is loaded to callback.
|
|
||||||
this._canvas.loadImage(imgData.source)
|
|
||||||
this._canvas.imageLoaders[imgData.source] = [callback, imgData]
|
|
||||||
} else {
|
|
||||||
// Callback directly
|
|
||||||
callback(imgData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const prerendered = Latex.findPrerendered(ltxText, this.textsize, color)
|
|
||||||
if(prerendered !== null)
|
|
||||||
onRendered(prerendered)
|
|
||||||
else
|
|
||||||
Latex.requestAsyncRender(ltxText, this.textsize, color).then(onRendered)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Context methods
|
|
||||||
//
|
|
||||||
|
|
||||||
get font() {
|
|
||||||
return this._ctx.font
|
|
||||||
}
|
|
||||||
|
|
||||||
set font(value) {
|
|
||||||
return this._ctx.font = value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws an act on the canvas centered on a point.
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
* @param {number} radius
|
|
||||||
* @param {number} startAngle
|
|
||||||
* @param {number} endAngle
|
|
||||||
* @param {boolean} counterclockwise
|
|
||||||
*/
|
|
||||||
arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
|
|
||||||
this._ctx.beginPath()
|
|
||||||
this._ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
|
|
||||||
this._ctx.stroke()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a filled circle centered on a point.
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
* @param {number} radius
|
|
||||||
*/
|
|
||||||
disc(x, y, radius) {
|
|
||||||
this._ctx.beginPath()
|
|
||||||
this._ctx.arc(x, y, radius, 0, 2 * Math.PI)
|
|
||||||
this._ctx.fill()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a filled rectangle onto the canvas.
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
* @param {number} w
|
|
||||||
* @param {number} h
|
|
||||||
*/
|
|
||||||
fillRect(x, y, w, h) {
|
|
||||||
this._ctx.fillRect(x, y, w, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {CanvasAPI} */
|
|
||||||
Modules.Canvas = Modules.Canvas || new CanvasAPI()
|
|
||||||
export default Modules.Canvas
|
|
|
@ -1,55 +0,0 @@
|
||||||
/**
|
|
||||||
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
|
|
||||||
* Copyright (C) 2021-2024 Ad5001
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Interface } from "./interface.mjs"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for global APIs in runtime.
|
|
||||||
*/
|
|
||||||
export class Module {
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} name - Name of the API
|
|
||||||
* @param {Object.<string, (Interface|string)>} initializationParameters - List of parameters for the initialize function.
|
|
||||||
*/
|
|
||||||
constructor(name, initializationParameters = {}) {
|
|
||||||
console.log(`Loading module ${name}...`)
|
|
||||||
this.__name = name
|
|
||||||
this.__initializationParameters = initializationParameters
|
|
||||||
this.initialized = false
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if all requirements are defined.
|
|
||||||
* @param {Object.<string, any>} options
|
|
||||||
*/
|
|
||||||
initialize(options) {
|
|
||||||
if(this.initialized)
|
|
||||||
throw new Error(`Cannot reinitialize module ${this.__name}.`)
|
|
||||||
for(const [name, value] of Object.entries(this.__initializationParameters)) {
|
|
||||||
if(!options.hasOwnProperty(name))
|
|
||||||
throw new Error(`Option '${name}' of initialize of module ${this.__name} does not exist.`)
|
|
||||||
if(typeof value === "function" && value.prototype instanceof Interface)
|
|
||||||
Interface.check_implementation(value, options[name])
|
|
||||||
else if(typeof value !== typeof options[name])
|
|
||||||
throw new Error(`Option '${name}' of initialize of module ${this.__name} is not a '${value}' (${typeof options[name]}).`)
|
|
||||||
}
|
|
||||||
this.initialized = true
|
|
||||||
}
|
|
||||||
}
|
|