Compare commits

..

1 commit

Author SHA1 Message Date
Ad5001 ff10b87b8b
Translated using Weblate (English)
Some checks reported errors
continuous-integration/drone/push Build was killed
Currently translated at 100.0% (187 of 187 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2022-04-16 19:31:27 +02:00
162 changed files with 7928 additions and 14118 deletions

3
.gitignore vendored
View file

@ -12,11 +12,8 @@ linux/flatpak/.flatpak-builder
**/**.jsc
**/**.pyc
**/**.qm
**/**.log
*.jsc
*.qmlc
*.log
**/*.dxvk-cache
.DS_Store
**/.DS_Store
**/__pycache__/

3
.gitmodules vendored
View file

@ -1,3 +1,6 @@
[submodule "LogarithmPlotter/qml/eu/ad5001/MixedMenu"]
path = LogarithmPlotter/qml/eu/ad5001/MixedMenu
url = https://git.ad5001.eu/Ad5001/MixedMenu
[submodule "linux/flatpak"]
path = linux/flatpak
url = https://github.com/Ad5001/eu.ad5001.LogarithmPlotter

View file

@ -1,162 +1,5 @@
# 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)
**New**
* (EXPERIMENTAL) LogarithmPlotter now has an optional LaTeX integration.
* It requires a LaTeX installation, including `latexmk` and `dvipng` available in the PATH.
* NOTE: LaTeX support is disabled by default and is only for working for the rendering on the graph.
* NOTE: The objects and history tab still use the legacy text based expression rendering.
* Thanks and contributions dialog, showing included libraries and translations, their license and author(s).
* LaTeX rendering can be disabled for texts, even if LaTeX is enabled.
**Changes**
* History re/undos only redraw the graph every 4 change at most in order to speed up the process when re/undoing a lot of changes.
* Gradients are no longer hidden when filtered out in the history tab.
**Added translations**
* LaTeX options and error messages
* Thanks and contribution dialog
* New option for text.
* Fixed translation of "repartition" which should be "distribution" in certain remaining strings.
**Fixed bugs**
* (macos) #1 - Opening files don't work on compiled versions of LogarithmPlotter on MacOS
* (snapcraft) Fixed bug preventing from launching LogarithmPlotter. This fix has been backported to v0.1.8.
* (snapcraft) Files are now properly opened.
* (snapcraft) Added changelog support.
**Internal changes**
* Moved python modules to "util" directory for more clarity.
* Moved flatpak metainfo to eu.ad5001.LogarithmPlotter repository.
* Componented the Mathlib library in order to have a more readable source.
* Added documentation for most internal JavaScript modules.
* Merge label drawing methods due to it's complexity.
* (flatpak) Updated SDK version to v5.15-21.08.
## v0.1.8 (19 Feb 2022)
**New**

View file

@ -1,6 +1,6 @@
"""
* 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
* it under the terms of the GNU General Public License as published by
@ -17,7 +17,7 @@
"""
from shutil import which
__VERSION__ = "0.6.0"
__VERSION__ = "0.2.0"
is_release = False

View file

@ -1,6 +1,6 @@
"""
* 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
* it under the terms of the GNU General Public License as published by

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,6 +1,6 @@
"""
* 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
* it under the terms of the GNU General Public License as published by
@ -18,10 +18,10 @@
from time import time
from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import Qt, QTranslator, QLocale
from PySide6.QtGui import QIcon
from PySide2.QtWidgets import QApplication
from PySide2.QtQml import QQmlApplicationEngine
from PySide2.QtCore import Qt, QTranslator, QLocale
from PySide2.QtGui import QIcon
from tempfile import TemporaryDirectory
from os import getcwd, chdir, environ, path, remove, close
@ -41,42 +41,40 @@ chdir(path.dirname(path.realpath(__file__)))
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
sys_path.append(path.realpath(path.join(getcwd(), "..")))
from LogarithmPlotter import __VERSION__
from LogarithmPlotter.util import config, native
from LogarithmPlotter.util.update import check_for_updates
from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex
from LogarithmPlotter.util.js import PyJSValue
config.init()
def get_linux_theme():
des = {
"KDE": "Fusion",
"gnome": "Basic",
"lxqt": "Fusion",
"mate": "Fusion",
"KDE": "org.kde.desktop",
"gnome": "default",
"lxqt": "fusion",
"mate": "fusion",
}
if "XDG_SESSION_DESKTOP" in environ:
return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "Fusion"
return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "fusion"
else:
# Android
return "Material"
def run():
if not 'QT_QUICK_CONTROLS_STYLE' in environ:
environ["QT_QUICK_CONTROLS_STYLE"] = {
"linux": get_linux_theme(),
"freebsd": get_linux_theme(),
"win32": "Universal" if os_release == "10" else "Fusion",
"cygwin": "Fusion",
"darwin": "macOS"
}[platform]
environ["QT_QUICK_CONTROLS_STYLE"] = {
"linux": get_linux_theme(),
"freebsd": get_linux_theme(),
"win32": "universal" if os_release == "10" else "fusion",
"cygwin": "fusion",
"darwin": "default"
}[platform]
dep_time = time()
print("Loaded dependencies in " + str((dep_time - start_time) * 1000) + "ms.")
print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.")
icon_fallbacks = QIcon.fallbackSearchPaths();
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
@ -86,45 +84,42 @@ def run():
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
QIcon.setFallbackSearchPaths(icon_fallbacks);
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(argv)
app.setApplicationName("LogarithmPlotter")
app.setDesktopFileName("eu.ad5001.LogarithmPlotter.desktop")
app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
# Installing translators
translator = QTranslator()
# Check if lang is forced.
forcedlang = [p for p in argv if p[:7] == "--lang="]
forcedlang = [p for p in argv if p[:7]=="--lang="]
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
if translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n"))):
if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))):
app.installTranslator(translator);
# Installing macOS file handler.
macOSFileOpenHandler = None
if platform == "darwin":
macOSFileOpenHandler = native.MacOSFileOpenHandler()
app.installEventFilter(macOSFileOpenHandler)
engine = QQmlApplicationEngine()
global tmpfile
helper = Helper(pwd, tmpfile)
latex = Latex(tempdir)
js_globals = PyJSValue(engine.globalObject())
js_globals.Modules = engine.newObject()
js_globals.Helper = engine.newQObject(helper)
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("StartTime", dep_time)
app.translate("About", "About LogarithmPlotter")
# FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
engine.addImportPath(path.realpath(path.join(getcwd(), "qml")))
engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
if not engine.rootObjects():
print("No root object", path.realpath(path.join(getcwd(), "qml")))
@ -134,26 +129,24 @@ def run():
# Open the current diagram
chdir(pwd)
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__)))
if platform == "darwin":
macOSFileOpenHandler.init_io(js_globals.Modules.IO)
# Check for LaTeX installation if LaTeX support is enabled
if config.getSetting("enable_latex"):
latex.check_latex_install()
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
latex.check_latex_install()
# Check for updates
if config.getSetting("check_for_updates"):
check_for_updates(__VERSION__, engine.rootObjects()[0])
exit_code = app.exec()
exit_code = app.exec_()
tempdir.cleanup()
config.save()
exit(exit_code)
if __name__ == "__main__":
run()

View file

@ -1,64 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24.0px"
height="24.0px"
viewBox="0 0 24.0 24.0"
version="1.1"
id="SVGRoot"
xml:space="preserve"
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/"><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>
<svg id="SVGRoot" width="48" height="48" version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<title>LogarithmPlotter Icon v1.0</title>
<g fill-rule="evenodd" stroke-width="2">
<rect width="48" height="48" ry="6" fill="#fff"/>
<rect x="2" y="38" width="44" height="4" stroke-opacity="0"/>
<rect x="18" y="2" width="4" height="44" stroke-opacity="0"/>
</g>
<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"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 488 B

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import Qt.labs.platform as Native
import QtQuick 2.12
import QtQuick.Dialogs 1.3
//import QtQuick.Controls 2.15
import eu.ad5001.MixedMenu 1.1
import "js/historylib.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
enabled: history.redoCount > 0
}
MenuSeparator { }
Action {
text: qsTr("&Copy plot")
shortcut: StandardKey.Copy
onTriggered: root.copyDiagramToClipboard()
icon.name: 'edit-copy'
}
MenuSeparator { }
Action {
text: qsTr("&Preferences")
shortcut: StandardKey.Copy
onTriggered: preferences.open()
icon.name: 'settings'
}
}
Menu {
title: qsTr("&Create")
// Services repeater
Repeater {
model: Object.keys(Modules.Objects.types)
model: Object.keys(Objects.types)
MenuItem {
text: Modules.Objects.types[modelData].displayType()
visible: Modules.Objects.types[modelData].createable()
text: Objects.types[modelData].displayType()
visible: Objects.types[modelData].createable()
height: visible ? implicitHeight : 0
icon.name: modelData
icon.source: './icons/objects/' + modelData + '.svg'
icon.color: sysPalette.buttonText
onTriggered: {
var newObj = Modules.Objects.createNewRegisteredObject(modelData)
var newObj = Objects.createNewRegisteredObject(modelData)
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
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 {
title: qsTr("&Help")
Action {
@ -154,11 +186,6 @@ MenuBar {
onTriggered: Qt.openUrlExternally("https://hosted.weblate.org/engage/logarithmplotter/")
}
MenuSeparator { }
Action {
text: qsTr("&Thanks")
icon.name: 'about'
onTriggered: thanksTo.open()
}
Action {
text: qsTr("&About")
shortcut: StandardKey.HelpContents
@ -167,17 +194,16 @@ MenuBar {
}
}
Native.MessageDialog {
MessageDialog {
id: saveUnsavedChangesDialog
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?")
buttons: Native.MessageDialog.Save | Native.MessageDialog.Discard | Native.MessageDialog.Cancel
onSaveClicked: settings.save()
onDiscardClicked: Qt.quit()
standardButtons: StandardButton.Yes | StandardButton.No
onYes: Qt.quit()
}
function openSaveUnsavedChangesDialog() {
saveUnsavedChangesDialog.open()
function showSaveUnsavedChangesDialog() {
saveUnsavedChangesDialog.visible = true
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* it under the terms of the GNU General Public License as published by
@ -16,10 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQml
import QtQuick.Window
import "../js/historylib.mjs" as HistoryLib
import QtQuick 2.12
import QtQml 2.12
import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import "../js/history/common.js" as HistoryCommon
/*!
\qmltype History
@ -212,8 +213,7 @@ Item {
}
Component.onCompleted: {
Modules.History.history = historyObj
Modules.History.themeTextColor = sysPalette.windowText
Modules.History.imageDepth = Screen.devicePixelRatio
HistoryLib.history = historyObj
HistoryCommon.themeTextColor = sysPalette.windowText
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick.Controls
import QtQuick
import QtQuick.Controls 2.12
import QtQuick 2.12
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/utils.mjs" as Utils
import "../js/utils.js" as Utils
/*!
@ -54,7 +54,6 @@ Item {
anchors.right: parent.right
anchors.top: parent.top
placeholderText: qsTr("Filter...")
category: "all"
}
ScrollView {

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick.Controls
import QtQuick
import Qt5Compat.GraphicalEffects
import "../js/utils.mjs" as Utils
import QtQuick.Controls 2.12
import QtQuick 2.12
import QtGraphicalEffects 1.15
import "../js/utils.js" as Utils
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
@ -132,6 +132,8 @@ Button {
color: sysPalette.windowText
}
//text: content
ToolTip.visible: hovered
ToolTip.delay: 200
ToolTip.text: content

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import Qt.labs.platform as Native
import "js/utils.mjs" as Utils
import "js/mathlib.mjs" as MathLib
import QtQuick 2.12
import "js/objects.js" as Objects
import "js/utils.js" as Utils
import "js/mathlib.js" as MathLib
/*!
\qmltype LogGraphCanvas
@ -126,6 +126,37 @@ Canvas {
*/
property int maxgradx: 20
/*!
\qmlproperty var LogGraphCanvas::yaxisstepExpr
Expression for the y axis step (used to create labels).
*/
property var yaxisstepExpr: (new MathLib.Expression(`x*(${yaxisstep})`))
/*!
\qmlproperty double LogGraphCanvas::yaxisstep1
Value of the for the y axis step.
*/
property double yaxisstep1: yaxisstepExpr.execute(1)
/*!
\qmlproperty int LogGraphCanvas::drawMaxY
Minimum value of y that should be drawn onto the canvas.
*/
property int drawMaxY: Math.ceil(Math.max(Math.abs(ymax), Math.abs(px2y(canvasSize.height)))/yaxisstep1)
/*!
\qmlproperty var LogGraphCanvas::xaxisstepExpr
Expression for the x axis step (used to create labels).
*/
property var xaxisstepExpr: (new MathLib.Expression(`x*(${xaxisstep})`))
/*!
\qmlproperty double LogGraphCanvas::xaxisstep1
Value of the for the x axis step.
*/
property double xaxisstep1: xaxisstepExpr.execute(1)
/*!
\qmlproperty int LogGraphCanvas::drawMaxX
Maximum value of x that should be drawn onto the canvas.
*/
property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1)
/*!
\qmlproperty var LogGraphCanvas::imageLoaders
Dictionary of format {image: [callback.image data]} containing data for defered image loading.
@ -137,25 +168,25 @@ Canvas {
*/
property var ctx
Component.onCompleted: {
imageLoaders = {}
Modules.Canvas.initialize(canvas, drawingErrorDialog)
}
Native.MessageDialog {
id: drawingErrorDialog
title: qsTranslate("expression", "LogarithmPlotter - Drawing error")
text: ""
function showDialog(objType, objName, error) {
text = qsTranslate("error", "Error while attempting to draw %1 %2:\n%3\n\nUndoing last change.").arg(objType).arg(objName).arg(error)
open()
}
}
Component.onCompleted: imageLoaders = {}
onPaint: function(rect) {
//console.log('Redrawing')
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
}
}
@ -169,7 +200,172 @@ 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.
@ -209,4 +405,71 @@ Canvas {
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)
}
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQml
import QtQuick.Controls
import QtQml 2.12
import QtQuick.Controls 2.12
import eu.ad5001.MixedMenu 1.1
import QtQuick.Layouts 1.12
import QtQuick
// Auto loading all modules.
import "js/autoload.js" as ModulesAutoload
import QtQuick 2.12
// Auto loading all objects.
import "js/objs/autoload.js" as ALObjects
import "js/objects.js" as Objects
import eu.ad5001.LogarithmPlotter.History 1.0
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
@ -55,14 +55,10 @@ ApplicationWindow {
Popup.GreetScreen {}
Popup.Preferences {id: preferences}
Popup.Changelog {id: changelog}
Popup.About {id: about}
Popup.ThanksTo {id: thanksTo}
Popup.Alert {
id: alert
anchors.bottom: parent.bottom
@ -161,7 +157,7 @@ ApplicationWindow {
property bool firstDrawDone: false
onPainted: if(!firstDrawDone) {
onPainted: if(!firstDrawDone) {
firstDrawDone = true;
console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms")
if(TestBuild == true) {
@ -170,13 +166,6 @@ ApplicationWindow {
}
}
ViewPositionChangeOverlay {
id: viewPositionChanger
anchors.fill: parent
canvas: parent
settingsInstance: settings
}
PickLocationOverlay {
id: positionPicker
anchors.fill: parent
@ -184,6 +173,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 {
id: delayRefreshTimer
repeat: false
@ -198,26 +301,10 @@ ApplicationWindow {
onTriggered: Qt.quit() // Quit after paint on test build
}
onClosing: function(close) {
onClosing: {
if(!history.saved) {
close.accepted = false
appMenu.openSaveUnsavedChangesDialog()
}
}
/*!
\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()
appMenu.showSaveUnsavedChangesDialog()
}
}
@ -248,7 +335,7 @@ ApplicationWindow {
Action {
text: qsTr("&Update LogarithmPlotter")
icon.name: 'update'
onTriggered: Qt.openUrlExternally("https://apps.ad5001.eu/logarithmplotter/")
onTriggered: Qt.openUrlExternally("https://dev.apps.ad5001.eu/logarithmplotter")
}
}

View file

@ -1,336 +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/historylib.mjs" as HistoryLib
import "../../js/utils.mjs" as Utils
import "../../js/mathlib.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']
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`
isDouble: propertyType == "number"
defValue: obj[propertyName] == null ? '' : obj[propertyName].toString()
category: {
return {
"Domain": "domain",
"string": "all",
"number": "all"
}[propertyType]
}
onChanged: function(newValue) {
try {
var newValueParsed = {
"Domain": () => MathLib.parseDomain(newValue),
"string": () => newValue,
"number": () => parseFloat(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(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()
}
}
}
}
}

View file

@ -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/historylib.mjs" as HistoryLib
import "../../js/utils.mjs" as Utils
import "../../js/mathlib.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()
}
}

View file

@ -1,5 +0,0 @@
module eu.ad5001.LogarithmPlotter.ObjectLists.Editor
Dialog 1.0 Dialog.qml
CustomPropertyList 1.0 CustomPropertyList.qml

View file

@ -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()
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import "../js/historylib.mjs" as HistoryLib
import QtQuick 2.12
import QtQuick.Controls 2.12
import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
@ -33,21 +34,6 @@ Column {
id: createRow
property var objectEditor
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 {
id: createTitle
@ -60,12 +46,12 @@ Column {
width: parent.width
columns: 3
Repeater {
model: Object.keys(Modules.Objects.types)
model: Object.keys(Objects.types)
Button {
id: createBtn
width: 96
visible: Modules.Objects.types[modelData].createable()
visible: Objects.types[modelData].createable()
height: visible ? width*0.8 : 0
// The KDE SDK is kinda buggy, so it respects neither specified color nor display propreties.
//display: AbstractButton.TextUnderIcon
@ -93,7 +79,7 @@ Column {
anchors.rightMargin: 4
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 14
text: Modules.Objects.types[modelData].displayType()
text: Objects.types[modelData].displayType()
wrapMode: Text.WordWrap
clip: true
}
@ -103,26 +89,13 @@ Column {
ToolTip.text: label.text
onClicked: {
let newObj = Modules.Objects.createNewRegisteredObject(modelData)
var newObj = Objects.createNewRegisteredObject(modelData)
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
objectLists.update()
let hasXProp = newObj.constructor.properties().hasOwnProperty('x')
let hasYProp = newObj.constructor.properties().hasOwnProperty('y')
if(hasXProp || hasYProp) {
// 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)
}
objectEditor.obj = Objects.currentObjects[modelData][Objects.currentObjects[modelData].length - 1]
objectEditor.objType = modelData
objectEditor.objIndex = Objects.currentObjects[modelData].length - 1
objectEditor.show()
}
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
// import QtQuick.Dialogs 1.3 as D
import QtQuick.Controls
import QtQuick 2.12
import QtQuick.Dialogs 1.3 as D
import QtQuick.Controls 2.12
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
\inqmlmodule eu.ad5001.LogarithmPlotter.ObjectLists
\inqmlmodule eu.ad5001.LogarithmPlotter
\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
for each object.
@ -46,15 +47,15 @@ ScrollView {
ListView {
id: objectsListView
model: Object.keys(Modules.Objects.types)
//width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0)
implicitHeight: contentItem.childrenRect.height + footerItem.height + 10
model: Object.keys(Objects.types)
width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0)
implicitHeight: contentItem.childrenRect.height + footer.height + 10
delegate: ListView {
id: objTypeList
property string objType: objectsListView.model[index]
property var editingRows: []
model: Modules.Objects.currentObjects[objType]
model: Objects.currentObjects[objType]
width: objectsListView.width
implicitHeight: contentItem.childrenRect.height
visible: model != undefined && model.length > 0
@ -69,39 +70,166 @@ ScrollView {
CheckBox {
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: {
for(var obj of Modules.Objects.currentObjects[objType]) obj.visible = this.checked
for(var obj of Objects.currentObjects[objType]) obj.visible = this.checked
for(var obj of objTypeList.editingRows) obj.objVisible = this.checked
objectListList.changed()
}
ToolTip.visible: hovered
ToolTip.text: checked ?
qsTr("Hide all %1").arg(Modules.Objects.types[objType].displayTypeMultiple()) :
qsTr("Show all %1").arg(Modules.Objects.types[objType].displayTypeMultiple())
ToolTip.text: checked ? qsTr("Hide all %1").arg(Objects.types[objType].displayTypeMultiple()) : qsTr("Show all %1").arg(Objects.types[objType].displayTypeMultiple())
}
Label {
id: typeHeaderText
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
}
}
delegate: ObjectRow {
delegate: Item {
id: controlRow
property var obj: Objects.currentObjects[objType][index]
property alias objVisible: objVisibilityCheckBox.checked
height: 40
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)
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
objectEditor: objEditor
objectLists: objectListList
posPicker: positionPicker
}
}
// Object editor
Editor.Dialog {
EditorDialog {
id: objEditor
posPicker: positionPicker
}
/*!
@ -129,7 +254,7 @@ ScrollView {
function update() {
objectListList.changed()
for(var objType in objectListList.listViews) {
objectListList.listViews[objType].model = Modules.Objects.currentObjects[objType]
objectListList.listViews[objType].model = Objects.currentObjects[objType]
}
}

View file

@ -1,224 +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/historylib.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
property var ltxInfo: visible ? Latex.render(obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color).split(",") : ["","0","0"]
source: visible ? ltxInfo[0] : ""
width: parseInt(ltxInfo[1])/depth
height: parseInt(ltxInfo[2])/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)
}
}
}

View file

@ -2,4 +2,5 @@ module eu.ad5001.LogarithmPlotter.ObjectLists
ObjectLists 1.0 ObjectLists.qml
ObjectCreationGrid 1.0 ObjectCreationGrid.qml
ObjectRow 1.0 ObjectRow.qml
EditorDialog 1.0 EditorDialog.qml

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "js/mathlib.mjs" as MathLib
import "js/historylib.mjs" as HistoryLib
import QtQuick 2.12
import QtQuick.Controls 2.12
import "js/objects.js" as Objects
import "js/mathlib.js" as MathLib
import "js/historylib.js" as HistoryLib
/*!
\qmltype PickLocationOverlay
@ -36,15 +36,6 @@ import "js/historylib.mjs" as HistoryLib
Item {
id: pickerRoot
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
@ -63,12 +54,12 @@ Item {
property string objName: 'A'
/*!
\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
/*!
\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
/*!
@ -86,16 +77,6 @@ Item {
Precision of the picked value (post-dot precision).
*/
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 {
color: sysPalette.window
@ -109,150 +90,67 @@ Item {
hoverEnabled: parent.visible
cursorShape: Qt.CrossCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function(mouse) {
onClicked: {
if(mouse.button == Qt.LeftButton) { // Validate
let newValueX = !parent.userPickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX)
let newValueY = !parent.userPickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY)
let obj = Modules.Objects.currentObjectsByName[objName]
// Set values
if(parent.userPickX && parent.userPickY) {
history.addToHistory(new HistoryLib.EditedPosition(
objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY
))
obj[propertyX] = newValueX
obj[propertyY] = newValueY
obj.update()
objectLists.update()
pickerRoot.picked(obj)
} else if(parent.userPickX) {
if(parent.pickX) {
let newValue = picked.mouseX.toString()
newValue = {
'Expression': () => new MathLib.Expression(newValue),
'number': () => parseFloat(newValue)
}[Objects.types[objType].properties()[propertyX]]()
let obj = Objects.getObjectByName(objName, objType)
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()
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(
objName, objType, propertyY, obj[propertyY], newValueY
objName, objType, propertyY, obj[propertyY], newValue
))
obj[propertyY] = newValueY
obj[propertyY] = newValue
obj.update()
objectLists.update()
pickerRoot.picked(obj)
}
}
pickerRoot.visible = false;
}
}
Rectangle {
id: pickerSettings
radius: 15
color: sysPalette.window
width: pickerSettingsColumn.width + 30;
height: pickerSettingsColumn.height + 20
property bool folded: false;
x: -15 - ((width-55) * folded);
y: 10
z: 2
Row {
height: precisionSlider.height
Text {
text: " "+ qsTr("Pointer precision:") + " "
color: 'black'
anchors.verticalCenter: parent.verticalCenter
}
Row {
id: pickerSettingsColumn
anchors {
left: parent.left
top: parent.top
leftMargin: 20
topMargin: 10
}
spacing: 15
property int cellHeight: 15
Column {
spacing: 5
// width: 100
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`
}
Slider {
id: precisionSlider
from: 0
value: 2
to: 10
stepSize: 1
ToolTip {
parent: precisionSlider.handle
visible: precisionSlider.pressed
text: precisionSlider.value.toFixed(0)
}
}
CheckBox {
id: snapToGridCheckbox
text: qsTr("Snap to grid")
checked: false
}
}
Rectangle {
@ -262,8 +160,8 @@ Item {
color: 'black'
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: Modules.Canvas.x2px(picked.mouseX)
visible: parent.userPickX
anchors.leftMargin: canvas.x2px(picked.mouseX)
visible: parent.pickX
}
Rectangle {
@ -273,60 +171,47 @@ Item {
color: 'black'
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: Modules.Canvas.y2px(picked.mouseY)
visible: parent.userPickY
anchors.topMargin: canvas.y2px(picked.mouseY)
visible: parent.pickY
}
Text {
id: picked
x: picker.mouseX - width - 5
y: picker.mouseY - height - 5
color: 'black'
property double axisX: Modules.Canvas.axesStep.x.value
property double axisY: Modules.Canvas.axesStep.y.value
property double axisX: canvas.xaxisstep1
property double mouseX: {
let xpos = Modules.Canvas.px2x(picker.mouseX)
let xpos = canvas.px2x(picker.mouseX)
if(snapToGridCheckbox.checked) {
if(canvas.logscalex) {
// Calculate the logged power
let pow = Math.pow(10, Math.floor(Math.log10(xpos)))
return pow*Math.round(xpos/pow)
} else {
return axisX*Math.round(xpos/axisX)
return canvas.xaxisstep1*Math.round(xpos/canvas.xaxisstep1)
}
} else {
return xpos.toFixed(parent.precision)
}
}
property double mouseY: {
let ypos = Modules.Canvas.px2y(picker.mouseY)
let ypos = canvas.px2y(picker.mouseY)
if(snapToGridCheckbox.checked) {
return axisY*Math.round(ypos/axisY)
return canvas.yaxisstep1*Math.round(ypos/canvas.yaxisstep1)
} else {
return ypos.toFixed(parent.precision)
}
}
color: 'black'
text: {
if(parent.userPickX && parent.userPickY)
if(parent.pickX && parent.pickY)
return `(${mouseX}, ${mouseY})`
else if(parent.userPickX)
if(parent.pickX)
return `X = ${mouseX}`
else if(parent.userPickY)
if(parent.pickY)
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)
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick 2.12
import QtQuick.Dialogs 1.3 as D
import QtQuick.Controls 2.12
/*!
\qmltype About
@ -26,112 +27,98 @@ import QtQuick.Controls
\sa LogarithmPlotter
*/
BaseDialog {
D.Dialog {
id: about
title: qsTr("About LogarithmPlotter")
width: 400
minimumHeight: 600
height: 600
Item {
anchors {
top: parent.top;
left: parent.left;
bottom: parent.bottom;
right: parent.right;
topMargin: margin;
leftMargin: margin;
bottomMargin: margin;
rightMargin: margin;
}
Image {
id: logo
source: "../icons/logarithmplotter.svg"
sourceSize.width: 64
sourceSize.height: 64
width: 64
height: 64
anchors.horizontalCenter: parent.horizontalCenter
anchors.rightMargin: width/2
anchors.top: parent.top
anchors.topMargin: 10
}
Label {
id: appName
anchors.top: logo.bottom
anchors.left: parent.left
anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 25
text: qsTr("LogarithmPlotter v%1").arg(Helper.getVersion())
}
Label {
id: description
anchors.top: appName.bottom
anchors.left: parent.left
anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 18
text: qsTr("2D plotter software to make BODE plots, sequences and repartition functions.")
}
Label {
id: debugInfos
anchors.top: description.bottom
anchors.left: parent.left
anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 14
text: Helper.getDebugInfos()
}
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 &lt;mail@ad5001.eu&gt;<br>
Image {
id: logo
source: "../icons/logarithmplotter.svg"
sourceSize.width: 64
sourceSize.height: 64
width: 64
height: 64
anchors.horizontalCenter: parent.horizontalCenter
anchors.rightMargin: width/2
anchors.top: parent.top
anchors.topMargin: 10
}
Label {
id: appName
anchors.top: logo.bottom
anchors.left: parent.left
anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 25
text: qsTr("LogarithmPlotter v%1").arg(Helper.getVersion())
}
Label {
id: description
anchors.top: appName.bottom
anchors.left: parent.left
anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 18
text: qsTr("2D plotter software to make BODE plots, sequences and repartition functions.")
}
Label {
id: debugInfos
anchors.top: description.bottom
anchors.left: parent.left
anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter
width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 14
text: Helper.getDebugInfos()
}
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 © 2022 Ad5001 &lt;mail@ad5001.eu&gt;<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>
<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>
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 {
id: buttonsRow
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')
}
Button {
id: officialWebsiteButton
text: qsTr('Official website')
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally('https://apps.ad5001.eu/logarithmplotter/')
}
Button {
id: officialWebsiteButton
text: qsTr('Official website')
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally('https://apps.ad5001.eu/logarithmplotter/')
}
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import QtQuick 2.12
/*!
\qmltype Alert
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup

View file

@ -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()
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick 2.12
import QtQuick.Controls 2.12
/*!
\qmltype Changelog
@ -32,7 +32,7 @@ Popup {
id: changelogPopup
x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2)
width: 800
width: changelog.width+40
height: Math.min(parent.height-40, 500)
modal: true
focus: true
@ -44,62 +44,42 @@ Popup {
*/
property bool changelogNeedsFetching: true
onAboutToShow: if(changelogNeedsFetching) {
Helper.fetchChangelog()
}
onAboutToShow: if(changelogNeedsFetching) Helper.fetchChangelog()
Connections {
target: Helper
function onChangelogFetched(chl) {
changelogNeedsFetching = false;
changelog.text = chl
changelogView.contentItem.implicitHeight = changelog.height
// console.log(changelog.height, changelogView.contentItem.implicitHeight)
}
}
ScrollView {
id: changelogView
anchors.top: parent.top
anchors.topMargin: 10
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
anchors.bottom: doneBtn.top
anchors.bottomMargin: 10
clip: true
Label {
id: changelog
color: sysPalette.windowText
width: 760
wrapMode: Text.WordWrap
textFormat: TextEdit.MarkdownText
text: qsTr("Fetching changelog...")
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 {
id: doneBtn
text: qsTr("Close")
text: qsTr("Done")
font.pixelSize: 18
anchors.bottom: parent.bottom
anchors.bottomMargin: 7
anchors.bottomMargin: 10
anchors.horizontalCenter: parent.horizontalCenter
onClicked: changelogPopup.close()
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import Qt.labs.platform
import QtQuick.Dialogs 1.3 as D
/*!
\qmltype FileDialog
@ -25,7 +25,7 @@ import Qt.labs.platform
\sa LogarithmPlotter, Settings
*/
FileDialog {
D.FileDialog {
id: fileDialog
property bool exportMode: false
@ -33,6 +33,6 @@ FileDialog {
title: exportMode ? qsTr("Export Logarithm Plot file") : qsTr("Import Logarithm Plot file")
nameFilters: ["Logarithm Plot File (*.lpf)", "All files (*)"]
defaultSuffix: 'lpf'
fileMode: exportMode ? FileDialog.SaveFile : FileDialog.OpenFile
folder: shortcuts.documents
selectExisting: !exportMode
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import QtQuick 2.12
import QtQuick.Controls 2.12
import "../js/math/latex.js" as Latex
/*!
\qmltype GreetScreen
@ -33,122 +33,133 @@ Popup {
id: greetingPopup
x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2)
width: greetingLayout.width+20
height: Math.min(parent.height-40, 700)
width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20
height: Math.min(parent.height-40, 500)
modal: true
focus: true
clip: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
Column {
id: greetingLayout
width: 600
spacing: 10
clip: true
topPadding: 35
Row {
id: welcome
height: logo.height
spacing: 10
anchors.horizontalCenter: parent.horizontalCenter
Image {
id: logo
source: "../icons/logarithmplotter.svg"
sourceSize.width: 48
sourceSize.height: 48
width: 48
height: 48
}
Label {
id: welcomeText
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 32
text: qsTr("Welcome to LogarithmPlotter")
}
}
Label {
id: versionText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: implicitWidth
font.pixelSize: 18
font.italic: true
text: qsTr("Version %1").arg(Helper.getVersion())
}
}
Grid {
Item {
id: welcome
height: logo.height
width: logo.width + 10 + welcomeText.width
anchors.top: parent.top
anchors.topMargin: (parent.width-width)/2
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: greetingLayout.bottom
anchors.topMargin: 50
columns: 2
spacing: 10
Repeater {
model: [{
name: qsTr("Changelog"),
icon: 'common/new.svg',
onClicked: () => changelog.open()
},{
name: qsTr("Preferences"),
icon: 'common/settings.svg',
onClicked: () => preferences.open()
},{
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 {
id: createBtn
width: 96
height: 96
onClicked: modelData.onClicked()
Setting.Icon {
id: icon
width: 24
height: 24
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
}
}
Image {
id: logo
source: "../icons/logarithmplotter.svg"
sourceSize.width: 48
sourceSize.height: 48
width: 48
height: 48
}
Label {
id: welcomeText
anchors.verticalCenter: parent.verticalCenter
anchors.left: logo.right
anchors.leftMargin: 10
//width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 32
text: qsTr("Welcome to LogarithmPlotter")
}
}
Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()+1) {
Label {
id: versionText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: welcome.bottom
anchors.topMargin: 10
//width: parent.width
wrapMode: Text.WordWrap
width: implicitWidth
font.pixelSize: 18
font.italic: true
text: qsTr("Version %1").arg(Helper.getVersion())
}
Label {
id: helpText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: versionText.bottom
anchors.topMargin: 40
//width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 14
width: parent.width - 50
text: qsTr("Take a few seconds to configure LogarithmPlotter.\nThese settings can be changed at any time from the \"Settings\" menu.")
}
CheckBox {
id: checkForUpdatesSetting
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: helpText.bottom
anchors.topMargin: 10
checked: Helper.getSettingBool("check_for_updates")
text: qsTr('Check for updates on startup (requires online connectivity)')
onClicked: {
Helper.setSettingBool("check_for_updates", checked)
//checkForUpdatesMenuSetting.checked = checked
}
}
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()
}

View file

@ -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)
}
}
}
}
}

View file

@ -1,257 +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.displayName
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.displayName
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.displayName
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.displayName
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.displayName
icon: `settings/${setting.icon}.svg`
defValue: Utils.simplifyExpression(setting.value())
variables: setting.variables
allowGraphObjects: false
property string propertyName: setting.displayName
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.model = Modules.Preferences.categories[modelData]
settingView.name = text
}
}
}
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.general
anchors {
top: parent.top
bottom: parent.bottom
}
ScrollBar.vertical: ScrollBar { }
property string name: qsTranslate('settingCategory', 'general')
header: Text {
id: settingCategoryName
font.pixelSize: 32
height: 48
color: sysPalette.windowText
text: settingView.name
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 * 2 / 3
property var setting: modelData
sourceComponent: {
if(setting instanceof S.BoolSetting)
return boolSettingComponent
else if(setting instanceof S.EnumIntSetting)
return enumIntSettingComponent
else if(setting instanceof S.NumberSetting)
return numberSettingComponent
else if(setting instanceof S.ExpressionSetting)
return expressionSettingComponent
else if(setting instanceof S.StringSetting)
return stringSettingComponent
else
console.log('Unknown setting type!', modelData.constructor)
}
}
}
}
}
// Component.onCompleted: open()
}

View file

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

View file

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

View file

@ -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
}
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick 2.12
import QtQuick.Controls 2.12
/*!
\qmltype ComboBoxSetting
@ -114,7 +114,6 @@ Item {
anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5
height: 30
width: Math.max(85, implicitWidth)
anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter
text: qsTranslate("control", "%1: ").arg(control.label)

View file

@ -1,634 +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/mathlib.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(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 ? '' : 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, '&nbsp;')
break;
}
}
return parsedText
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Window
import QtQuick.Controls.impl
import QtQuick 2.7
import QtGraphicalEffects 1.0
/*!
\qmltype Icon
@ -41,16 +40,19 @@ Item {
\qmlproperty string Icon::source
Path of the icon image source.
*/
property alias sourceSize: img.sourceS
property alias sourceSize: img.sourceSize.width
ColorImage {
Image {
id: img
height: parent.height
width: parent.width
// visible: false
property int sourceS: width*Screen.devicePixelRatio
sourceSize.width: sourceS
sourceSize.height: sourceS
//smooth: true
visible: false
sourceSize.height: sourceSize.width
}
ColorOverlay {
anchors.fill: img
source: img
color: parent.color
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* it under the terms of the GNU General Public License as published by
@ -16,17 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as Reference from "reference.mjs"
import * as T from "./tokenizer.mjs"
import InputExpression from "common.mjs"
export const Input = InputExpression
export const TokenType = T.TokenType
export const Token = T.Token
export const Tokenizer = T.ExpressionTokenizer
import QtQuick 2.12
import QtQuick.Controls 2.12
export const FUNCTIONS_LIST = Reference.FUNCTIONS_LIST
export const FUNCTIONS = Reference.FUNCTIONS
export const FUNCTIONS_USAGE = Reference.FUNCTIONS_USAGE
export const CONSTANTS_LIST = Reference.CONSTANTS_LIST
export const CONSTANTS = Reference.CONSTANTS
Image {
id: expr
property string expression
src: Latex.render(expression).split(',')[0]
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick
import QtQuick.Controls
import QtQml.Models
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQml.Models 2.12
/*!
\qmltype ListSetting
@ -140,8 +140,8 @@ Column {
visible: control.dictionaryMode
height: parent.height
width: visible ? 50 : 0
validator: RegularExpressionValidator {
regularExpression: control.keyRegexp
validator: RegExpValidator {
regExp: control.keyRegexp
}
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignHCenter
@ -180,8 +180,8 @@ Column {
id: valueInput
height: parent.height
width: parent.width - x - deleteButton.width - 5
validator: RegularExpressionValidator {
regularExpression: control.valueRegexp
validator: RegExpValidator {
regExp: control.valueRegexp
}
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignHCenter

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import QtQuick.Controls
import QtQuick
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
import QtQuick.Controls 2.12
import QtQuick 2.12
/*!
\qmltype TextSetting
@ -49,12 +48,6 @@ Item {
If true, the input is being parsed an double before being emitting the \a changed signal.
*/
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
Minimum value for numbers that can be entered into the input.
@ -100,14 +93,14 @@ Item {
id: labelItem
anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5
anchors.top: parent.top
height: parent.height
width: Math.max(85, implicitWidth)
anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
visible: control.label != ""
}
TextField {
id: input
@ -119,19 +112,16 @@ Item {
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
color: sysPalette.windowText
validator: RegularExpressionValidator {
regularExpression: control.isInt ? /-?[0-9]+/ : control.isDouble ? /-?[0-9]+(\.[0-9]+)?/ : /.+/
validator: RegExpValidator {
regExp: control.isInt ? /-?[0-9]+/ : control.isDouble ? /-?[0-9]+(\.[0-9]+)?/ : /.+/
}
focus: true
text: control.defValue
selectByMouse: true
onEditingFinished: function() {
if(insertButton.focus || insertPopup.focus) return
onEditingFinished: {
var value = text
if(control.isInt)
value = isNaN(parseInt(value)) ? control.min : Math.max(control.min,parseInt(value))
if(control.isDouble)
value = isNaN(parseFloat(value)) ? control.min : Math.max(control.min,parseFloat(value))
if(control.isInt) value = Math.max(control.min,parseInt(value).toString()=="NaN"?control.min:parseInt(value))
if(control.isDouble) value = Math.max(control.min,parseFloat(value).toString()=="NaN"?control.min:parseFloat(value))
if(value != "" && value.toString() != defValue) {
control.changed(value)
defValue = value.toString()
@ -140,7 +130,6 @@ Item {
}
Button {
id: insertButton
text: "α"
anchors.right: parent.right
anchors.rightMargin: 5
@ -148,23 +137,54 @@ Item {
width: 20
height: width
visible: !isInt && !isDouble
onClicked: {
insertPopup.open()
insertPopup.focus = true
}
onClicked: insertPopup.open()
}
Popup.InsertCharacter {
Popup {
id: insertPopup
x: Math.round((parent.width - width) / 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) {
input.insert(input.cursorPosition, c)
insertPopup.close()
focus = false
input.focus = true
Grid {
id: insertGrid
width: parent.width
columns: 7
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
}
}
}
}
}
}

View file

@ -4,5 +4,4 @@ ComboBoxSetting 1.0 ComboBoxSetting.qml
Icon 1.0 Icon.qml
ListSetting 1.0 ListSetting.qml
TextSetting 1.0 TextSetting.qml
ExpressionEditor 1.0 ExpressionEditor.qml
AutocompletionCategory 1.0 AutocompletionCategory.qml

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
* 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
@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Controls 2.12
import QtQuick 2.12
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
import "js/utils.mjs" as Utils
import "js/utils.js" as Utils
/*!
\qmltype Settings
@ -44,93 +44,89 @@ ScrollView {
Zoom on the x axis of the diagram, provided from settings.
\sa Settings
*/
property double xzoom: Helper.getSettingInt('default_graph.xzoom')
property double xzoom: 100
/*!
\qmlproperty double Settings::yzoom
Zoom on the y axis of the diagram, provided from settings.
\sa Settings
*/
property double yzoom: Helper.getSettingInt('default_graph.yzoom')
property double yzoom: 10
/*!
\qmlproperty double Settings::xmin
Minimum x of the diagram, provided from settings.
\sa Settings
*/
property double xmin: Helper.getSettingInt('default_graph.xmin')
property double xmin: 5/10
/*!
\qmlproperty double Settings::ymax
Maximum y of the diagram, provided from settings.
\sa Settings
*/
property double ymax: Helper.getSettingInt('default_graph.ymax')
property double ymax: 25
/*!
\qmlproperty string Settings::xaxisstep
Step of the x axis graduation, provided from settings.
\note: Only available in non-logarithmic mode.
\sa Settings
*/
property string xaxisstep: Helper.getSetting('default_graph.xaxisstep')
property string xaxisstep: "4"
/*!
\qmlproperty string Settings::yaxisstep
Step of the y axis graduation, provided from settings.
\sa Settings
*/
property string yaxisstep: Helper.getSetting('default_graph.yaxisstep')
property string yaxisstep: "4"
/*!
\qmlproperty string Settings::xlabel
Label used on the x axis, provided from settings.
\sa Settings
*/
property string xlabel: Helper.getSetting('default_graph.xlabel')
property string xlabel: ""
/*!
\qmlproperty string Settings::ylabel
Label used on the y axis, provided from settings.
\sa Settings
*/
property string ylabel: Helper.getSetting('default_graph.ylabel')
property string ylabel: ""
/*!
\qmlproperty double Settings::linewidth
Width of lines that will be drawn into the canvas, provided from settings.
\sa Settings
*/
property double linewidth: Helper.getSettingInt('default_graph.linewidth')
property double linewidth: 1
/*!
\qmlproperty double Settings::textsize
Font size of the text that will be drawn into the canvas, provided from settings.
\sa Settings
*/
property double textsize: Helper.getSettingInt('default_graph.textsize')
property double textsize: 18
/*!
\qmlproperty bool Settings::logscalex
true if the canvas should be in logarithmic mode, false otherwise.
Provided from settings.
\sa Settings
*/
property bool logscalex: Helper.getSettingBool('default_graph.logscalex')
property bool logscalex: true
/*!
\qmlproperty bool Settings::showxgrad
true if the x graduation should be shown, false otherwise.
Provided from settings.
\sa Settings
*/
property bool showxgrad: Helper.getSettingBool('default_graph.showxgrad')
property bool showxgrad: true
/*!
\qmlproperty bool Settings::showygrad
true if the y graduation should be shown, false otherwise.
Provided from settings.
\sa Settings
*/
property bool showygrad: Helper.getSettingBool('default_graph.showygrad')
property bool showygrad: true
/*!
\qmlproperty bool Settings::saveFilename
Path of the currently opened file. Empty if no file is opened.
*/
property string saveFilename: ""
Component.onCompleted: {
Modules.IO.initialize(root, settings, alert)
}
Column {
spacing: 10
width: parent.width
@ -139,12 +135,12 @@ ScrollView {
Popup.FileDialog {
id: fdiag
onAccepted: {
var filePath = fdiag.currentFile.toString().substr(7)
var filePath = fileUrl.toString().substr(7)
settings.saveFilename = filePath
if(exportMode) {
Modules.IO.saveDiagram(filePath)
root.saveDiagram(filePath)
} else {
Modules.IO.loadDiagram(filePath)
root.loadDiagram(filePath)
if(xAxisLabel.find(settings.xlabel) == -1) xAxisLabel.model.append({text: settings.xlabel})
xAxisLabel.editText = settings.xlabel
if(yAxisLabel.find(settings.ylabel) == -1) yAxisLabel.model.append({text: settings.ylabel})
@ -159,7 +155,7 @@ ScrollView {
height: 30
isDouble: true
label: qsTr("X Zoom")
min: 0.1
min: 1
icon: "settings/xzoom.svg"
width: settings.settingWidth
value: settings.xzoom.toFixed(2)
@ -173,7 +169,6 @@ ScrollView {
id: zoomY
height: 30
isDouble: true
min: 0.1
label: qsTr("Y Zoom")
icon: "settings/yzoom.svg"
width: settings.settingWidth
@ -227,10 +222,10 @@ ScrollView {
label: qsTr("Max X")
icon: "settings/xmax.svg"
width: settings.settingWidth
defValue: canvas.px2x(canvas.width).toFixed(2)
value: canvas.px2x(canvas.canvasSize.width).toFixed(2)
onChanged: function(xvaluemax) {
if(xvaluemax > settings.xmin) {
settings.xzoom = settings.xzoom * canvas.width/(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()
} else {
alert.show("Maximum x value must be superior to minimum.")
@ -246,10 +241,10 @@ ScrollView {
label: qsTr("Min Y")
icon: "settings/ymin.svg"
width: settings.settingWidth
defValue: canvas.px2y(canvas.height).toFixed(2)
defValue: canvas.px2y(canvas.canvasSize.height).toFixed(2)
onChanged: function(yvaluemin) {
if(yvaluemin < settings.ymax) {
settings.yzoom = settings.yzoom * canvas.height/(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()
} else {
alert.show("Minimum y value must be inferior to maximum.")
@ -260,7 +255,6 @@ ScrollView {
Setting.TextSetting {
id: xAxisStep
height: 30
category: "expression"
label: qsTr("X Axis Step")
icon: "settings/xaxisstep.svg"
width: settings.settingWidth
@ -275,7 +269,6 @@ ScrollView {
Setting.TextSetting {
id: yAxisStep
height: 30
category: "expression"
label: qsTr("Y Axis Step")
icon: "settings/yaxisstep.svg"
width: settings.settingWidth
@ -447,7 +440,7 @@ ScrollView {
if(settings.saveFilename == "") {
saveAs()
} else {
Modules.IO.saveDiagram(settings.saveFilename)
root.saveDiagram(settings.saveFilename)
}
}

View file

@ -1,153 +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/mathlib.mjs" as MathLib
import "js/historylib.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) {
settingsInstance.xmin = (Modules.Canvas.px2x(Modules.Canvas.x2px(settingsInstance.xmin)-deltaX))
settingsInstance.ymax += deltaY/canvas.yzoom
settingsInstance.ymax = settingsInstance.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)
if(newXZoom == settingsInstance.xzoom) // No change, allow more precision.
newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(4)
if(newYZoom == settingsInstance.yzoom) // No change, allow more precision.
newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(4)
settingsInstance.xzoom = newXZoom
settingsInstance.yzoom = newYZoom
settingsInstance.changed()
}
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

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

Before

Width:  |  Height:  |  Size: 19 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -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

View file

@ -1,17 +1,44 @@
<?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"
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/">
sodipodi:docname="Function.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
<defs
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
id="metadata10">
<rdf:RDF>
@ -20,7 +47,8 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2021-2023</dc:date>
<dc:title />
<dc:date>2021</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
@ -28,7 +56,7 @@
</dc:creator>
<dc:rights>
<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>
</dc:rights>
<cc:license
@ -50,46 +78,33 @@
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<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="0.012050295"
y="17.985596"
id="text1"><tspan
id="tspan1"
x="0.012050295"
y="17.985596"
style="font-size:17.3333px">f</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="10.913334"
y="18.134649"
id="text1-3"><tspan
id="tspan1-6"
x="10.913334"
y="18.134649"
style="font-size:17.3333px">x</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="6.3066678"
y="17.646639"
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>
<path
id="rect1415"
style="fill:#000000;fill-rule:evenodd"
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"
sodipodi:nodetypes="ccccccccccccccccc" />
<path
id="rect839"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
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"
sodipodi:nodetypes="ccccccc" />
<path
id="rect857"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="m 12,10 h 2 l 6,8 h -2 z"
sodipodi:nodetypes="ccccc" />
<path
id="rect857-7"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="m 12,18 h 2 l 6,-8 h -2 z"
sodipodi:nodetypes="ccccc" />
<path
id="rect839-3"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
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"
sodipodi:nodetypes="ccccccc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -1,17 +1,47 @@
<?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"
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/">
sodipodi:docname="Gain 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="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
id="metadata1472">
<rdf:RDF>
@ -20,40 +50,13 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<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/" />
<dc:title />
</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
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<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"
@ -64,14 +67,15 @@
y="17" />
<text
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"
x="-0.105"
y="11.959"
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.12666447"
y="12.134649"
id="text839"><tspan
sodipodi:role="line"
id="tspan837"
x="-0.105"
y="11.959"
style="font-size:17px">ω</tspan></text>
x="-0.12666447"
y="12.134649"
style="font-size:17.3333px">ω</tspan></text>
<circle
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
id="path837"
@ -82,6 +86,7 @@
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" />
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z"
sodipodi:nodetypes="ccccc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,17 +1,44 @@
<?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"
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/">
sodipodi:docname="Phase 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: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
id="metadata13">
<rdf:RDF>
@ -20,40 +47,13 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<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/" />
<dc:title />
</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
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;stroke-width:1.1547"
@ -65,7 +65,8 @@
<path
id="rect26-3"
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
style="fill:#000000;stroke-width:2.09999"
id="path45"
@ -74,13 +75,14 @@
r="4" />
<text
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"
x="6.1339936"
y="11.163"
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.4359932"
y="11.702"
id="text49"><tspan
sodipodi:role="line"
id="tspan47"
x="6.1339936"
y="11.163"
style="font-size:17px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text>
x="6.4359932"
y="11.702"
style="font-size:18px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,67 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>
<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>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 772 B

View file

@ -1,17 +1,45 @@
<?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"
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/">
sodipodi:docname="Sequence.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="-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
id="metadata13">
<rdf:RDF>
@ -20,62 +48,56 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<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>
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<text
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"
y="16.134649"
id="text908"><tspan
sodipodi:role="line"
id="tspan906"
x="7.483983"
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
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"
y="18.663307"
id="text912"
transform="scale(1.0000324,0.9999676)"><tspan
sodipodi:role="line"
id="tspan910"
x="16.365566"
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>
<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;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal"
x="-0.69333196"
y="17.646639"
id="text2"><tspan
id="tspan2"
x="-0.69333196"
y="17.646639"
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>
<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;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal"
x="18.806667"
y="17.646639"
id="text2-7"><tspan
id="tspan2-5"
x="18.806667"
y="17.646639"
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>
style="font-size:17px;stroke-width:1.00003">n</tspan></text>
<g
aria-label="("
id="text852"
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"
transform="matrix(1.0022756,0,0,1.2616817,-0.26079098,-9.0560687)">
<path
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"
style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path854" />
</g>
<g
aria-label="("
id="text852-3"
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"
transform="matrix(-1.0030304,0,0,1.2658306,24.414952,-9.1000412)">
<path
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"
style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path854-6" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,19 +1,19 @@
<?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="Text.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
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/">
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
<defs
id="defs1469" />
<sodipodi:namedview
@ -24,38 +24,23 @@
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="13.772321"
inkscape:cy="8.4598214"
inkscape:cx="13.763421"
inkscape:cy="16.975675"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1010"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2039"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
id="grid2058" />
</sodipodi:namedview>
<metadata
id="metadata1472">
@ -65,37 +50,8 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<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/" />
<dc:title />
</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
@ -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"
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" />
<text
xml:space="preserve"
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"
x="13.844"
y="17.387978"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
x="13.844"
y="17.387978"
style="font-size:17px">t</tspan></text>
<path
id="rect837"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
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"
sodipodi:nodetypes="ccccccccccccccc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,17 +1,47 @@
<?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"
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/">
sodipodi:docname="X Cursor.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="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
id="metadata1472">
<rdf:RDF>
@ -20,40 +50,13 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<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/" />
<dc:title />
</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
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<g
aria-label="X"
@ -65,18 +68,18 @@
id="rect12"
width="2"
height="24"
x="5"
x="17"
y="0"
ry="2.14841e-13" />
<text
xml:space="preserve"
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"
x="10.915"
y="13.713"
id="text1"><tspan
id="tspan1"
x="10.915"
y="13.713"
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>
<path
id="rect835"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="m 5,2 h 2 l 8,14 h -2 z"
sodipodi:nodetypes="ccccc" />
<path
id="rect835-6"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="M 15,2 H 13 L 5,16 h 2 z"
sodipodi:nodetypes="ccccc" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

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

View file

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

View file

@ -1,10 +0,0 @@
// Loading modules in order
.import "objects.mjs" as Objects
.import "lib/expr-eval/integration.js" as ExprParser
.import "objs/autoload.mjs" as Autoload
.import "math/latex.mjs" as Latex
.import "history/common.mjs" as HistoryCommon
.import "canvas.mjs" as CanvasAPI
.import "io.mjs" as IOAPI
.import "preferences.mjs" as PreferencesAPI

View file

@ -1,522 +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 "./modules.mjs"
import {textsup} from "./utils.mjs"
import {Expression} from "./mathlib.mjs"
class CanvasAPI extends Module {
constructor() {
super('Canvas', [
Modules.Objects,
Modules.History
])
/** @type {HTMLCanvasElement} */
this._canvas = null
/** @type {CanvasRenderingContext2D} */
this._ctx = null
/**
* @type {Object}
* @property {function(string, string, string)} showDialog
* @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(canvasObject, drawingErrorDialog) {
this._canvas = canvasObject
this._drawingErrorDialog = drawingErrorDialog
}
get width() { return this._canvas.width }
get height() { return this._canvas.height }
/**
* Minimum x of the diagram, provided from settings.
* @returns {number}
*/
get xmin() { return this._canvas.xmin }
/**
* Zoom on the x-axis of the diagram, provided from settings.
* @returns {number}
*/
get xzoom() { return this._canvas.xzoom }
/**
* Maximum y of the diagram, provided from settings.
* @returns {number}
*/
get ymax() { return this._canvas.ymax }
/**
* Zoom on the y-axis of the diagram, provided from settings.
* @returns {number}
*/
get yzoom() { return this._canvas.yzoom }
/**
* Label used on the x-axis, provided from settings.
* @returns {string}
*/
get xlabel() { return this._canvas.xlabel }
/**
* Label used on the y-axis, provided from settings.
* @returns {string}
*/
get ylabel() { return this._canvas.ylabel }
/**
* Width of lines that will be drawn into the canvas, provided from settings.
* @returns {number}
*/
get linewidth() { return this._canvas.linewidth }
/**
* Font size of the text that will be drawn into the canvas, provided from settings.
* @returns {number}
*/
get textsize() { return this._canvas.textsize }
/**
* True if the canvas should be in logarithmic mode, false otherwise.
* @returns {boolean}
*/
get logscalex() { return this._canvas.logscalex }
/**
* True if the x graduation should be shown, false otherwise.
* @returns {boolean}
*/
get showxgrad() { return this._canvas.showxgrad }
/**
* True if the y graduation should be shown, false otherwise.
* @returns {boolean}
*/
get showygrad() { return this._canvas.showygrad }
/**
* Max power of the logarithmic scaled on the x axis in logarithmic mode.
* @returns {number}
*/
get maxgradx() { return this._canvas.maxgradx }
//
// Methods to draw the canvas
//
requestPaint() {
this._canvas.requestPaint()
}
/**
* Redraws the entire canvas
*/
redraw() {
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 Modules.Objects.currentObjects) {
for(let obj of Modules.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)
this._drawingErrorDialog.showDialog(objType, obj.name, e.message)
Modules.History.undo()
}
}
}
this._ctx.lineWidth = 1
}
/**
* Calculates informations 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).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).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) {
return this._canvas.x2px(x)
}
/**
* 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._canvas.y2px(y)
}
/**
* 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) {
return this._canvas.px2x(px)
}
/**
* 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 this._canvas.px2y(px)
}
/**
* 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({width: number, height: number, source: string})} callback
*/
renderLatexImage(ltxText, color, callback) {
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, this.textsize, color).split(",")
let imgData = {
"source": ltxSrc,
"width": parseFloat(ltxWidth),
"height": parseFloat(ltxHeight)
};
if(!this._canvas.isImageLoaded(ltxSrc) && !this._canvas.isImageLoading(ltxSrc)){
// Wait until the image is loaded to callback.
this._canvas.loadImage(ltxSrc)
this._canvas.imageLoaders[ltxSrc] = [callback, imgData]
} else {
// Callback directly
callback(imgData)
}
}
//
// 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 const API = Modules.Canvas

View file

@ -16,20 +16,6 @@ var IMEMBER = 'IMEMBER';
var IENDSTATEMENT = 'IENDSTATEMENT';
var IARRAY = 'IARRAY';
// Additional variable characters.
var ADDITIONAL_VARCHARS = [
"α","β","γ","δ","ε","ζ","η",
"π","θ","κ","λ","μ","ξ","ρ",
"ς","σ","τ","φ","χ","ψ","ω",
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀",
"∞","π"
]
function Instruction(type, value) {
this.type = type;
this.value = (value !== undefined && value !== null) ? value : 0;
@ -118,11 +104,7 @@ function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values)));
} else if (type === IMEMBER && nstack.length > 0) {
n1 = nstack.pop();
//console.log("Getting property ", item.value, "of", n1)
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))
nstack.push(new Instruction(INUMBER, n1.value[item.value]));
} /* else if (type === IARRAY && nstack.length >= item.value) {
var length = item.value;
while (length-- > 0) {
@ -212,10 +194,7 @@ function evaluate(tokens, expr, values) {
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) {
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]);
@ -224,7 +203,7 @@ function evaluate(tokens, expr, values) {
if (v !== undefined) {
nstack.push(v);
} else {
throw new Error(qsTranslate('error', 'Undefined variable %1.').arg(item.value));
throw new Error('undefined variable: ' + item.value);
}
}
} else if (type === IOP1) {
@ -240,14 +219,8 @@ function evaluate(tokens, expr, 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));
throw new Error(f + ' is not a function');
}
} else if (type === IFUNDEF) {
// Create closure to keep references to arguments and expression
@ -280,14 +253,7 @@ function evaluate(tokens, expr, values) {
nstack.push(item);
} else if (type === IMEMBER) {
n1 = nstack.pop();
//console.log("Getting property", item.value, "of", n1,":",n1[item.value])
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))
nstack.push(n1[item.value]);
} else if (type === IENDSTATEMENT) {
nstack.pop();
} else if (type === IARRAY) {
@ -298,11 +264,11 @@ function evaluate(tokens, expr, values) {
}
nstack.push(args);
} else {
throw new Error(qsTranslate('error', 'Invalid expression.'));
throw new Error('invalid Expression');
}
}
if (nstack.length > 1) {
throw new Error(qsTranslate('error', 'Invalid expression (parity).'));
throw new Error('invalid Expression (parity)');
}
// Explicitly return zero to avoid test issues caused by -0
return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values);
@ -378,7 +344,7 @@ function expressionToString(tokens, toJS) {
if (f === '?') {
nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')');
} else {
throw new Error(qsTranslate('error', 'Invalid expression.'));
throw new Error('invalid Expression');
}
} else if (type === IVAR || type === IVARNAME) {
nstack.push(item.value);
@ -434,7 +400,7 @@ function expressionToString(tokens, toJS) {
} else if (type === IEXPR) {
nstack.push('(' + expressionToString(item.value, toJS) + ')');
} else if (type === IENDSTATEMENT) ; else {
throw new Error(qsTranslate('error', 'Invalid expression.'));
throw new Error('invalid Expression');
}
}
if (nstack.length > 1) {
@ -521,7 +487,7 @@ Expression.prototype.substitute = function (variable, expr) {
};
Expression.prototype.evaluate = function (values) {
values = Object.assign({}, values, this.parser.consts)
values = values || {};
return evaluate(this.tokens, this, values);
};
@ -541,9 +507,8 @@ Expression.prototype.variables = function (options) {
var vars = [];
getSymbols(this.tokens, vars, options);
var functions = this.functions;
var consts = this.parser.consts
return vars.filter(function (name) {
return !(name in functions) && !(name in consts);
return !(name in functions);
});
};
@ -581,7 +546,7 @@ function TokenStream(parser, expression) {
this.unaryOps = parser.unaryOps;
this.binaryOps = parser.binaryOps;
this.ternaryOps = parser.ternaryOps;
this.builtinConsts = parser.builtinConsts;
this.consts = parser.consts;
this.expression = expression;
this.savedPosition = 0;
this.savedCurrent = null;
@ -623,7 +588,7 @@ TokenStream.prototype.next = function () {
this.isName()) {
return this.current;
} else {
this.parseError(qsTranslate('error', 'Unknown character "%1".').arg(this.expression.charAt(this.pos)));
this.parseError('Unknown character "' + this.expression.charAt(this.pos) + '"');
}
};
@ -693,7 +658,7 @@ TokenStream.prototype.isConst = function () {
var i = startPos;
for (; i < this.expression.length; i++) {
var c = this.expression.charAt(i);
if (c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
if (c.toUpperCase() === c.toLowerCase()) {
if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) {
break;
}
@ -701,8 +666,8 @@ TokenStream.prototype.isConst = function () {
}
if (i > startPos) {
var str = this.expression.substring(startPos, i);
if (str in this.builtinConsts) {
this.current = this.newToken(TNUMBER, this.builtinConsts[str]);
if (str in this.consts) {
this.current = this.newToken(TNUMBER, this.consts[str]);
this.pos += str.length;
return true;
}
@ -738,7 +703,7 @@ TokenStream.prototype.isName = function () {
var hasLetter = false;
for (; i < this.expression.length; i++) {
var c = this.expression.charAt(i);
if (c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
if (c.toUpperCase() === c.toLowerCase()) {
if (i === this.pos && (c === '$' || c === '_')) {
if (c === '_') {
hasLetter = true;
@ -817,13 +782,13 @@ TokenStream.prototype.unescape = function (v) {
// interpret the following 4 characters as the hex of the unicode code point
var codePoint = v.substring(index + 1, index + 5);
if (!codePointPattern.test(codePoint)) {
this.parseError(qsTranslate('error', 'Illegal escape sequence: %1.').arg("\\u" + codePoint));
this.parseError('Illegal escape sequence: \\u' + codePoint);
}
buffer += String.fromCharCode(parseInt(codePoint, 16));
index += 4;
break;
default:
throw this.parseError(qsTranslate('error', 'Illegal escape sequence: %1.').arg('\\' + c));
throw this.parseError('Illegal escape sequence: "\\' + c + '"');
}
++index;
var backslash = v.indexOf('\\', index);
@ -1025,7 +990,7 @@ TokenStream.prototype.getCoordinates = function () {
TokenStream.prototype.parseError = function (msg) {
var coords = this.getCoordinates();
throw new Error(qsTranslate('error', 'Parse error [%1:%2]: %3').arg(coords.line).arg(coords.column).arg(msg));
throw new Error('parse error [' + coords.line + ':' + coords.column + ']: ' + msg);
};
function ParserState(parser, tokenStream, options) {
@ -1079,9 +1044,7 @@ ParserState.prototype.accept = function (type, value) {
ParserState.prototype.expect = function (type, value) {
if (!this.accept(type, value)) {
var coords = this.tokens.getCoordinates();
throw new Error(qsTranslate('error', 'Parse error [%1:%2]: %3')
.arg(coords.line).arg(coords.column)
.arg(qsTranslate('error', 'Expected %1').arg(value || type)));
throw new Error('parse error [' + coords.line + ':' + coords.column + ']: Expected ' + (value || type));
}
};
@ -1108,7 +1071,7 @@ ParserState.prototype.parseAtom = function (instr) {
instr.push(new Instruction(IARRAY, argCount));
}
} else {
throw new Error(qsTranslate('error', 'Unexpected %1').arg(this.nextToken));
throw new Error('unexpected ' + this.nextToken);
}
};
@ -1117,7 +1080,7 @@ ParserState.prototype.parseExpression = function (instr) {
if (this.parseUntilEndStatement(instr, exprInstr)) {
return;
}
this.parseConditionalExpression(exprInstr);
this.parseVariableAssignmentExpression(exprInstr);
if (this.parseUntilEndStatement(instr, exprInstr)) {
return;
}
@ -1157,6 +1120,37 @@ ParserState.prototype.parseArrayList = function (instr) {
return argCount;
};
ParserState.prototype.parseVariableAssignmentExpression = function (instr) {
this.parseConditionalExpression(instr);
while (this.accept(TOP, '=')) {
var varName = instr.pop();
var varValue = [];
var lastInstrIndex = instr.length - 1;
if (varName.type === IFUNCALL) {
if (!this.tokens.isOperatorEnabled('()=')) {
throw new Error('function definition is not permitted');
}
for (var i = 0, len = varName.value + 1; i < len; i++) {
var index = lastInstrIndex - i;
if (instr[index].type === IVAR) {
instr[index] = new Instruction(IVARNAME, instr[index].value);
}
}
this.parseVariableAssignmentExpression(varValue);
instr.push(new Instruction(IEXPR, varValue));
instr.push(new Instruction(IFUNDEF, varName.value));
continue;
}
if (varName.type !== IVAR && varName.type !== IMEMBER) {
throw new Error('expected variable for assignment');
}
this.parseVariableAssignmentExpression(varValue);
instr.push(new Instruction(IVARNAME, varName.value));
instr.push(new Instruction(IEXPR, varValue));
instr.push(binaryInstruction('='));
}
};
ParserState.prototype.parseConditionalExpression = function (instr) {
this.parseOrExpression(instr);
while (this.accept(TOP, '?')) {
@ -1312,21 +1306,21 @@ ParserState.prototype.parseMemberExpression = function (instr) {
if (op.value === '.') {
if (!this.allowMemberAccess) {
throw new Error(qsTranslate('error', 'Unexpected ".": member access is not permitted'));
throw new 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.'));
throw new 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));
throw new Error('unexpected symbol: ' + op.value);
}
}
};
@ -1588,29 +1582,25 @@ function arrayIndex(array, index) {
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'))
return Math.max.apply(Math, arguments);
}
}
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'))
return Math.min.apply(Math, arguments);
}
}
function arrayMap(f, a) {
if (typeof f !== 'function') {
throw new EvalError(qsTranslate('error', 'First argument to map is not a function.'));
throw new 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.'));
throw new Error('Second argument to map is not an array');
}
return a.map(function (x, i) {
return f(x, i);
@ -1619,10 +1609,10 @@ function arrayMap(f, a) {
function arrayFold(f, init, a) {
if (typeof f !== 'function') {
throw new EvalError(qsTranslate('error', 'First argument to fold is not a function.'));
throw new 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.'));
throw new Error('Second argument to fold is not an array');
}
return a.reduce(function (acc, x, i) {
return f(acc, x, i);
@ -1631,10 +1621,10 @@ function arrayFold(f, init, a) {
function arrayFilter(f, a) {
if (typeof f !== 'function') {
throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.'));
throw new 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.'));
throw new Error('Second argument to filter is not an array');
}
return a.filter(function (x, i) {
return f(x, i);
@ -1643,7 +1633,7 @@ function arrayFilter(f, a) {
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.'));
throw new Error('Second argument to indexOf is not a string or array');
}
return s.indexOf(target);
@ -1651,7 +1641,7 @@ function stringOrArrayIndexOf(target, s) {
function arrayJoin(sep, a) {
if (!Array.isArray(a)) {
throw new Error(qsTranslate('error', 'Second argument to join is not an array.'));
throw new Error('Second argument to join is not an array');
}
return a.join(sep);
@ -1753,7 +1743,6 @@ class Parser {
atan2: Math.atan2,
'if': condition,
gamma: gamma,
'Γ': gamma,
roundTo: roundTo,
map: arrayMap,
fold: arrayFold,
@ -1762,12 +1751,12 @@ class Parser {
join: 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 = {}
this.consts = {
E: Math.E,
PI: Math.PI,
'true': true,
'false': false
};
}
parse(expr) {
@ -1779,7 +1768,7 @@ class Parser {
);
parserState.parseExpression(instr);
parserState.expect(TEOF, QT_TRANSLATE_NOOP('error','EOF'));
parserState.expect(TEOF, 'EOF');
return new Expression(instr, this);
}
@ -1820,9 +1809,9 @@ var optionNameMap = {
'not': 'logical',
'?': 'conditional',
':': 'conditional',
//'=': 'assignment', // Disable assignment
'=': 'assignment',
'[': 'array',
//'()=': 'fndef' // Diable function definition
'()=': 'fndef'
};
function getOptionName(op) {
@ -1837,12 +1826,10 @@ Parser.prototype.isOperatorEnabled = function (op) {
};
/*!
Based on ndef.parser, by Raphael Graf <r@undefined.ch>
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> (http://silentmatt.com/)
Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
Ported to JavaScript and modified by Matthew Crumley (email@matthewcrumley.com, http://silentmatt.com/)
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,

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import EditedProperty from "editproperty.mjs"
import Objects from "../objects.mjs"
.pragma library
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
type(){return 'ColorChanged'}

View file

@ -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()
}
}

View file

@ -1,134 +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 "../modules.mjs"
import Latex from "../math/latex.mjs"
class HistoryCommonAPI extends Module {
constructor() {
super('History', [
Modules.Latex
])
// History QML object
this.history = null;
this.themeTextColor = "#ff0000";
this.imageDepth = 2;
this.fontSize = 14;
}
undo() { this.history.undo() }
redo() { this.history.redo() }
clear() { this.history.clear() }
addToHistory(action) { this.history.addToHistory(action) }
unserialize(...data) { this.history.unserialize(...data) }
serialize() { return this.history.serialize() }
}
/** @type {HistoryCommonAPI} */
Modules.History = Modules.History || new HistoryCommonAPI()
export const API = Modules.History
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: ${Modules.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 {string}
*/
renderLatexAsHtml(latexString) {
if(!Latex.enabled)
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
let imgDepth = Modules.History.imageDepth
let [src, width, height] = Latex.render(
latexString,
imgDepth * (Modules.History.fontSize + 2),
Modules.History.themeTextColor
).split(",")
return `<img src="${src}" width="${parseInt(width)/imgDepth}" height="${parseInt(height)/imgDepth}" style="vertical-align: middle"/>`
}
/**
* Returns a string with the HTML-formatted description of the action.
*
* @returns {string}
*/
getHTMLString() {
return this.getReadableString()
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import Objects from "../objects.mjs"
import { Action } from "common.mjs"
.pragma library
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
type(){return 'CreateNewObject'}
@ -34,12 +36,13 @@ export default class CreateNewObject extends Action {
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() {
Objects.createNewRegisteredObject(this.targetType, this.targetProperties)
Objects.currentObjectsByName[this.targetName].update()
}
export() {

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import Objects from "../objects.mjs"
import CreateNewObject from "create.mjs"
.pragma library
.import "../objects.js" as Objects
.import "create.js" as Create
export default class DeleteObject extends CreateNewObject {
/**
* Action used at the deletion of an object. Basically the same thing as creating a new object, except Redo & Undo are reversed.
*/
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.
type(){return 'DeleteObject'}
icon(){return 'delete'}

View file

@ -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;">&nbsp;' + this.targetName + '&nbsp;</b>')
.arg('<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.prev+'&nbsp;</tt>')
.arg('<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.next+'</tt>')
// .arg('<b style="font-size: 15px;">' + Objects.types[this.targetType].displayType())
}
}

View file

@ -1,140 +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 "../objects.mjs"
import Latex from "../math/latex.mjs"
import * as MathLib from "../mathlib.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 = "";
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);">&nbsp;'+this.prevString+'&nbsp;</tt>'
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.nextString+'&nbsp;</tt>'
if(Latex.enabled && typeof this.propertyType == 'object' && this.propertyType.type === "Expression") {
this.prevHTML= this.renderLatexAsHtml(this.previousValue.latexMarkup)
this.nextHTML= this.renderLatexAsHtml(this.newValue.latexMarkup)
}
}
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.prevString).arg(this.nextString)
}
getHTMLString() {
return qsTr('%1 of %2 changed from %3 to %4.')
.arg(this.targetPropertyReadable)
.arg('<b style="font-size: 15px;">&nbsp;' + this.targetName + '&nbsp;</b>')
.arg(this.prevHTML)
.arg(this.nextHTML)
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import EditedProperty from "editproperty.mjs"
import Objects from "../objects.mjs"
.pragma library
.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
type(){return 'NameChanged'}
icon(){return 'name'}
color(darkVer=false){return darkVer ? 'darkorange' : 'orange'}
constructor(targetName = "", targetType = "Point", newName = "") {
@ -37,11 +40,11 @@ export default class NameChanged extends EditedProperty {
}
undo() {
Objects.renameObject(this.newValue, this.previousValue)
Objects.getObjectByName(this.newValue, this.targetType)['name'] = this.previousValue
}
redo() {
Objects.renameObject(this.previousValue, this.newValue)
Objects.getObjectByName(this.previousValue, this.targetType)['name'] = this.newValue
}
getReadableString() {

View file

@ -1,93 +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 "../objects.mjs"
import Latex from "../math/latex.mjs"
import * as MathLib from "../mathlib.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()})`
// Render as LaTeX
if(Latex.enabled) {
this.prevHTML = this.renderLatexAsHtml(`\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`)
this.nextHTML = this.renderLatexAsHtml(`\\left(${this.newXValue.latexMarkup},${this.newYValue.latexMarkup}\\right)`)
} else {
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+escapeHTML(this.prevString)+'&nbsp;</tt>'
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+escapeHTML(this.nextString)+'&nbsp;</tt>'
}
}
export() {
return [this.targetName, this.targetType,
this.previousXValue.toEditableString(), this.newXValue.toEditableString(),
this.previousYValue.toEditableString(), this.newYValue.toEditableString()]
}
getReadableString() {
return qsTr('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 qsTr('Position of %1 set from %2 to %3.')
.arg('<b style="font-size: 15px;">&nbsp;' + this.targetName + '&nbsp;</b>')
.arg(this.prevHTML)
.arg(this.nextHTML)
}
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import EditedProperty from "editproperty.mjs"
import Objects from "../objects.mjs"
.pragma library
.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.
type(){return 'EditedVisibility'}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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)
// 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 "history/common.mjs"
import Create from "history/create.mjs"
import Delete from "history/delete.mjs"
import EP from "history/editproperty.mjs"
import Pos from "history/position.mjs"
import V from "history/visibility.mjs"
import Name from "history/name.mjs"
import Color from "history/color.mjs"
.import "history/common.js" as Common
.import "history/create.js" as Create
.import "history/delete.js" as Delete
.import "history/editproperty.js" as EP
.import "history/visibility.js" as V
.import "history/name.js" as Name
.import "history/color.js" as Color
var history = null;
export const Action = A
export const CreateNewObject = Create
export const DeleteObject = Delete
export const EditedProperty = EP
export const EditedPosition = Pos
export const EditedVisibility = V
export const NameChanged = Name
export const ColorChanged = Color
var Action = Common.Action
var CreateNewObject = Create.CreateNewObject
var DeleteObject = Delete.DeleteObject
var EditedProperty = EP.EditedProperty
var EditedVisibility = V.EditedVisibility
var NameChanged = Name.NameChanged
var ColorChanged = Color.ColorChanged
export const Actions = {
var Actions = {
"Action": Action,
"CreateNewObject": CreateNewObject,
"DeleteObject": DeleteObject,
"EditedProperty": EditedProperty,
"EditedPosition": EditedPosition,
"EditedVisibility": EditedVisibility,
"NameChanged": NameChanged,
"ColorChanged": ColorChanged,

View file

@ -1,169 +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 "./modules.mjs"
class IOAPI extends Module {
constructor() {
super('IO', [
Modules.Objects,
Modules.History
])
/**
* Path of the currently opened file. Empty if no file is opened.
* @type {string}
*/
this.saveFileName = ""
}
/**
* Initializes module with QML elements.
* @param {LogarithmPlotter} rootElement
* @param {Settings} settings
* @param {{show: function(string)}} alert
*/
initialize(rootElement, settings, alert) {
this.rootElement = rootElement
this.settings = settings
this.alert = alert
}
/**
* Saves the diagram to a certain \c filename.
* @param {string} filename
*/
saveDiagram(filename) {
// Add extension if necessary
if(['lpf'].indexOf(filename.split('.')[filename.split('.').length-1]) === -1)
filename += '.lpf'
this.saveFilename = filename
let objs = {}
for(let objType in Modules.Objects.currentObjects){
objs[objType] = []
for(let obj of Modules.Objects.currentObjects[objType]) {
objs[objType].push(obj.export())
}
}
let settings = {
"xzoom": this.settings.xzoom,
"yzoom": this.settings.yzoom,
"xmin": this.settings.xmin,
"ymax": this.settings.ymax,
"xaxisstep": this.settings.xaxisstep,
"yaxisstep": this.settings.yaxisstep,
"xaxislabel": this.settings.xlabel,
"yaxislabel": this.settings.ylabel,
"logscalex": this.settings.logscalex,
"linewidth": this.settings.linewidth,
"showxgrad": this.settings.showxgrad,
"showygrad": this.settings.showygrad,
"textsize": this.settings.textsize,
"history": Modules.History.serialize(),
"width": this.rootElement.width,
"height": this.rootElement.height,
"objects": objs,
"type": "logplotv1"
}
Helper.write(filename, JSON.stringify(settings))
this.alert.show(qsTranslate('io', "Saved plot to '%1'.").arg(filename.split("/").pop()))
Modules.History.history.saved = true
}
/**
* Loads the diagram from a certain \c filename.
* @param {string} filename
*/
loadDiagram(filename) {
let basename = filename.split("/").pop()
this.alert.show(qsTranslate('io', "Loading file '%1'.").arg(basename))
let data = JSON.parse(Helper.load(filename))
let error = "";
if(Object.keys(data).includes("type") && data["type"] === "logplotv1") {
Modules.History.clear()
// Importing settings
this.settings.saveFilename = filename
this.settings.xzoom = parseFloat(data["xzoom"]) || 100
this.settings.yzoom = parseFloat(data["yzoom"]) || 10
this.settings.xmin = parseFloat(data["xmin"]) || 5/10
this.settings.ymax = parseFloat(data["ymax"]) || 24
this.settings.xaxisstep = data["xaxisstep"] || "4"
this.settings.yaxisstep = data["yaxisstep"] || "4"
this.settings.xlabel = data["xaxislabel"] || ""
this.settings.ylabel = data["yaxislabel"] || ""
this.settings.logscalex = data["logscalex"] === true
if("showxgrad" in data)
this.settings.showxgrad = data["showxgrad"]
if("showygrad" in data)
this.settings.textsize = data["showygrad"]
if("linewidth" in data)
this.settings.linewidth = data["linewidth"]
if("textsize" in data)
this.settings.textsize = data["textsize"]
this.rootElement.height = parseFloat(data["height"]) || 500
this.rootElement.width = parseFloat(data["width"]) || 1000
// Importing objects
Modules.Objects.currentObjects = {}
for(let key of Object.keys(Modules.Objects.currentObjectsByName)) {
delete Modules.Objects.currentObjectsByName[key];
// Required to keep the same reference for the copy of the object used in expression variable detection.
// Another way would be to change the reference as well, but I feel like the code would be less clean.
}
for(let objType in data['objects']) {
if(Object.keys(Modules.Objects.types).indexOf(objType) > -1) {
Modules.Objects.currentObjects[objType] = []
for(let objData of data['objects'][objType]) {
/** @type {DrawableObject} */
let obj = Modules.Objects.types[objType].import(...objData)
Modules.Objects.currentObjects[objType].push(obj)
Modules.Objects.currentObjectsByName[obj.name] = obj
}
} else {
error += qsTranslate('io', "Unknown object type: %1.").arg(objType) + "\n";
}
}
// Updating object dependencies.
for(let objName in Modules.Objects.currentObjectsByName)
Modules.Objects.currentObjectsByName[objName].update()
// Importing history
if("history" in data)
Modules.History.unserialize(...data["history"])
// Refreshing sidebar
this.rootElement.updateObjectsLists()
} else {
error = qsTranslate('io', "Invalid file provided.")
}
if(error !== "") {
console.log(error)
this.alert.show(qsTranslate('io', "Could not save file: ") + error)
// TODO: Error handling
return
}
Modules.Canvas.redraw()
this.alert.show(qsTranslate('io', "Loaded file '%1'.").arg(basename))
Modules.History.history.saved = true
}
}
/** @type {IOAPI} */
Modules.IO = Modules.IO || new IOAPI()

View file

@ -1,119 +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/>.
*/
.pragma library
.import "expr-eval.js" as ExprEval
.import "../../modules.mjs" as M
const evalVariables = {
// Variables not provided by expr-eval.js, needs to be provided manually
"pi": Math.PI,
"PI": Math.PI,
"π": Math.PI,
"inf": Infinity,
"infinity": Infinity,
"Infinity": Infinity,
"∞": Infinity,
"e": Math.E,
"E": Math.E,
"true": true,
"false": false
}
class ExprParserAPI extends M.Module {
constructor() {
super('ExprParser', [
/** @type {ObjectsAPI} */
Modules.Objects
])
this.currentVars = {}
this.Internals = ExprEval
this._parser = new ExprEval.Parser()
this._parser.consts = Object.assign({}, this._parser.consts, evalVariables)
this._parser.functions.integral = this.integral.bind(this)
this._parser.functions.derivative = this.derivative.bind(this)
}
/**
* Parses arguments for a function, returns the corresponding JS function if it exists.
* Throws either usage error otherwise.
* @param {array} args - Arguments of the function, either [ ExecutableObject ] or [ string, variable ].
* @param {string} usage1 - Usage for executable object.
* @param {string} usage2 - Usage for string function.
* @return {function} JS function to call.
*/
parseArgumentsForFunction(args, usage1, usage2) {
let f, target, variable
if(args.length === 1) {
// Parse object
f = args[0]
if(typeof f !== 'object' || !f.execute)
throw EvalError(qsTranslate('usage', 'Usage: %1').arg(usage1))
let target = f
f = (x) => target.execute(x)
} else if(args.length === 2) {
// Parse variable
[f,variable] = args
if(typeof f !== 'string' || typeof variable !== 'string')
throw EvalError(qsTranslate('usage', 'Usage: %1').arg(usage2))
f = this._parser.parse(f).toJSFunction(variable, this.currentVars)
} else
throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
return f
}
/**
* @param {string} expression - Expression to parse
*/
parse(expression) {
return this._parser.parse(expression)
}
integral(a, b, ...args) {
let usage1 = qsTranslate('usage', 'integral(<from: number>, <to: number>, <f: ExecutableObject>)')
let usage2 = qsTranslate('usage', 'integral(<from: number>, <to: number>, <f: string>, <variable: string>)')
let f = this.parseArgumentsForFunction(args, usage1, usage2)
if(a == null || b == null)
throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
// https://en.wikipedia.org/wiki/Simpson%27s_rule
// Simpler, faster than tokenizing the expression
return (b-a)/6*(f(a)+4*f((a+b)/2)+f(b))
}
derivative(...args) {
let usage1 = qsTranslate('usage', 'derivative(<f: ExecutableObject>, <x: number>)')
let usage2 = qsTranslate('usage', 'derivative(<f: string>, <variable: string>, <x: number>)')
let x = args.pop()
let f = this.parseArgumentsForFunction(args, usage1, usage2)
if(x == null)
throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2))
let derivative_precision = x/10
return (f(x+derivative_precision/2)-f(x-derivative_precision/2))/derivative_precision
}
}
/** @type {ExprParserAPI} */
Modules.ExprParser = Modules.ExprParser || new ExprParserAPI()

View file

@ -1,70 +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) => '',
}

View file

@ -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
}

View file

@ -1,6 +1,6 @@
/**
* 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
* 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/>.
*/
import { Expression, executeExpression } from "expression.mjs"
.pragma library
.import "expression.js" as Expr
/**
* Main abstract domain class
* It doesn't represent any kind of domain and is meant to be extended.
*/
export class Domain {
class Domain {
constructor() {}
/**
* Checks whether x is included in the domain.
* @param {number} x - The x value.
* @return {boolean} true if included, false otherwise.
* @return {bool} true if included, false otherwise.
*/
includes(x) { return false }
@ -70,13 +72,11 @@ export class Domain {
case "RP":
case "R+":
case "ℝ⁺":
case "+":
return Domain.RP
break;
case "RM":
case "R-":
case "ℝ⁻":
case "-":
return Domain.RM
break;
case "RPE":
@ -85,8 +85,6 @@ export class Domain {
case "R*+":
case "*⁺":
case "ℝ⁺*":
case "*+":
case "+*":
return Domain.RPE
break;
case "RME":
@ -95,21 +93,16 @@ export class Domain {
case "R*-":
case "ℝ⁻*":
case "*⁻":
case "-*":
case "*-":
return Domain.RME
break;
case "":
case "N":
case "ZP":
case "Z+":
case "ℤ⁺":
case "+":
return Domain.N
break;
case "NLOG":
case "ℕˡᵒᵍ":
case "LOG":
return Domain.NLog
break;
case "NE":
@ -118,15 +111,12 @@ export class Domain {
case "N+":
case "*":
case "ℕ⁺":
case "+":
case "ZPE":
case "ZEP":
case "Z+*":
case "Z*+":
case "ℤ⁺*":
case "*⁺":
case "+*":
case "*+":
return Domain.NE
break;
case "Z":
@ -136,7 +126,6 @@ export class Domain {
case "ZM":
case "Z-":
case "ℤ⁻":
case "-":
return Domain.ZM
break;
case "ZME":
@ -145,8 +134,6 @@ export class Domain {
case "Z*-":
case "ℤ⁻*":
case "*⁻":
case "-*":
case "*-":
return Domain.ZME
break;
case "ZE":
@ -164,7 +151,7 @@ export class Domain {
/**
* Represents an empty set.
*/
export class EmptySet extends Domain {
class EmptySet extends Domain {
constructor() {
super()
this.displayName = "∅"
@ -185,12 +172,12 @@ export class EmptySet extends Domain {
/**
* Domain classes for ranges (e.g ]0;3[, [1;2[ ...)
*/
export class Range extends Domain {
class Range extends Domain {
constructor(begin, end, openBegin, openEnd) {
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
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.openBegin = openBegin
this.openEnd = openEnd
@ -199,7 +186,7 @@ export class Range extends Domain {
}
includes(x) {
if(typeof x == 'string') x = executeExpression(x)
if(typeof x == 'string') x = Expr.executeExpression(x)
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()))
}
@ -227,9 +214,9 @@ export class Range extends Domain {
}
static import(frm) {
let openBegin = frm.trim().charAt(0) === "]"
let openEnd = frm.trim().charAt(frm.length -1) === "["
let [begin, end] = frm.substr(1, frm.length-2).split(";")
var openBegin = frm.trim().charAt(0) == "]"
var openEnd = frm.trim().charAt(frm.length -1) == "["
var [begin, end] = frm.substr(1, frm.length-2).split(";")
return new Range(begin.trim(), end.trim(), openBegin, openEnd)
}
}
@ -237,16 +224,17 @@ export class Range extends Domain {
/**
* Domain classes for special domains (N, Z, ...)
*/
export class SpecialDomain extends Domain {
class SpecialDomain extends Domain {
/**
* @constructs SpecialDomain
* @param {string} displayName
* @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} 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) {
super()
this.displayName = displayName
@ -257,17 +245,17 @@ export class SpecialDomain extends Domain {
}
includes(x) {
if(typeof x == 'string') x = executeExpression(x)
if(typeof x == 'string') x = Expr.executeExpression(x)
return this.isValid(x)
}
next(x) {
if(typeof x == 'string') x = executeExpression(x)
if(typeof x == 'string') x = Expr.executeExpression(x)
return this.nextValue(x)
}
previous(x) {
if(typeof x == 'string') x = executeExpression(x)
if(typeof x == 'string') x = Expr.executeExpression(x)
return this.prevValue(x)
}
@ -297,14 +285,14 @@ export class SpecialDomain extends Domain {
/**
* Domain classes for sets (e.g {0;3}, {0;1;2;pi} ...)
*/
export class DomainSet extends SpecialDomain {
class DomainSet extends SpecialDomain {
constructor(values) {
super('', x => true, x => x, true)
let newVals = {}
var newVals = {}
this.executedValues = []
for(let value of values) {
let expr = new Expression(value.toString())
let ex = expr.execute()
for(var value of values) {
var expr = new Expr.Expression(value.toString())
var ex = expr.execute()
newVals[ex] = expr
this.executedValues.push(ex)
}
@ -315,30 +303,30 @@ export class DomainSet extends SpecialDomain {
}
includes(x) {
if(typeof x == 'string') x = executeExpression(x)
for(let value of this.values)
if(x === value.execute()) return true
if(typeof x == 'string') x = Expr.executeExpression(x)
for(var value of this.values)
if(x == value.execute()) return true
return false
}
next(x) {
if(typeof x == 'string') x = executeExpression(x)
if(typeof x == 'string') x = Expr.executeExpression(x)
if(x < this.executedValues[0]) return this.executedValues[0]
for(let i = 1; i < this.values.length; i++) {
let prevValue = this.executedValues[i-1]
let value = this.executedValues[i]
for(var i = 1; i < this.values.length; i++) {
var prevValue = this.executedValues[i-1]
var value = this.executedValues[i]
if(x >= prevValue && x < value) return value
}
return null
}
previous(x) {
if(typeof x == 'string') x = executeExpression(x)
if(typeof x == 'string') x = Expr.executeExpression(x)
if(x > this.executedValues[this.executedValues.length-1])
return this.executedValues[this.executedValues.length-1]
for(let i = 1; i < this.values.length; i++) {
let prevValue = this.executedValues[i-1]
let value = this.executedValues[i]
for(var i = 1; i < this.values.length; i++) {
var prevValue = this.executedValues[i-1]
var value = this.executedValues[i]
if(x > prevValue && x <= value) return prevValue
}
return null
@ -351,56 +339,56 @@ export class DomainSet extends SpecialDomain {
union(domain) {
if(domain instanceof EmptySet) return this
if(domain instanceof DomainSet) {
let newValues = []
let values = this.values.concat(domain.values).filter(function(val){
var newValues = []
var values = this.values.concat(domain.values).filter(function(val){
newValues.push(val.execute())
return newValues.indexOf(val.execute()) === newValues.length - 1
return newValues.indexOf(val.execute()) == newValues.length - 1
})
return new DomainSet(values)
}
let notIncludedValues = []
for(let i = 0; i < this.values.length; i++) {
let value = this.executedValues[i]
var notIncludedValues = []
for(var value in this.values) {
var value = this.executedValues[i]
if(domain instanceof Range) {
if(domain.begin.execute() === value && domain.openBegin) {
if(domain.begin.execute() == value && domain.openBegin) {
domain.openBegin = false
}
if(domain.end.execute() === value && domain.openEnd) {
if(domain.end.execute() == value && domain.openEnd) {
domain.openEnd = false
}
}
if(!domain.includes(value))
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))
}
intersection(domain) {
if(domain instanceof EmptySet) return domain
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){
return domValues.indexOf(val.execute()) >= 0
})
return this
}
let includedValues = []
for(let i in this.values) {
let value = this.executedValues[i]
var includedValues = []
for(var i in this.values) {
var value = this.executedValues[i]
if(domain instanceof Range) {
if(domain.begin.execute() === value && !domain.openBegin) {
if(domain.begin.execute() == value && !domain.openBegin) {
domain.openBegin = false
}
if(domain.end.execute() === value && !domain.openEnd) {
if(domain.end.execute() == value && !domain.openEnd) {
domain.openEnd = false
}
}
if(domain.includes(value))
includedValues.push(this.values[i].toEditableString())
}
if(includedValues.length === 0) return new EmptySet()
if(includedValues.length === this.values.length) return this
if(includedValues.length == 0) return new EmptySet()
if(includedValues.length == this.values.length) return this
return new IntersectionDomain(domain, new DomainSet(includedValues))
}
@ -412,7 +400,7 @@ export class DomainSet extends SpecialDomain {
/**
* Domain representing the union between two domains.
*/
export class UnionDomain extends Domain {
class UnionDomain extends Domain {
constructor(dom1, dom2) {
super()
this.dom1 = dom1
@ -447,10 +435,10 @@ export class UnionDomain extends Domain {
}
static import(frm) {
let domains = frm.trim().split("")
if(domains.length === 1) domains = frm.trim().split("U") // Fallback
let dom2 = parseDomain(domains.pop())
let dom1 = parseDomain(domains.join(''))
var domains = frm.trim().split("")
if(domains.length == 1) domains = frm.trim().split("U") // Fallback
var dom1 = parseDomain(domains.pop())
var dom2 = parseDomain(domains.join(''))
return dom1.union(dom2)
}
}
@ -458,7 +446,7 @@ export class UnionDomain extends Domain {
/**
* Domain representing the intersection between two domains.
*/
export class IntersectionDomain extends Domain {
class IntersectionDomain extends Domain {
constructor(dom1, dom2) {
super()
this.dom1 = dom1
@ -493,9 +481,9 @@ export class IntersectionDomain extends Domain {
}
static import(frm) {
let domains = frm.trim().split("∩")
let dom1 = parseDomain(domains.pop())
let dom2 = parseDomain(domains.join('∩'))
var domains = frm.trim().split("∩")
var dom1 = parseDomain(domains.pop())
var dom2 = parseDomain(domains.join('∩'))
return dom1.intersection(dom2)
}
}
@ -503,7 +491,7 @@ export class IntersectionDomain extends Domain {
/**
* Domain representing the minus between two domains.
*/
export class MinusDomain extends Domain {
class MinusDomain extends Domain {
constructor(dom1, dom2) {
super()
this.dom1 = dom1
@ -521,10 +509,10 @@ export class MinusDomain extends Domain {
}
static import(frm) {
let domains = frm.trim().split("")
if(domains.length === 1) domains = frm.trim().split("\\") // Fallback
let dom1 = parseDomain(domains.shift())
let dom2 = parseDomain(domains.join(''))
var domains = frm.trim().split("")
if(domains.length == 1) domains = frm.trim().split("\\") // Fallback
var dom1 = parseDomain(domains.shift())
var dom2 = parseDomain(domains.join(''))
return new MinusDomain(dom1, dom2)
}
}
@ -548,53 +536,53 @@ Domain.RPE.latexMarkup = "\\mathbb{R}^{+*}"
Domain.RME = new Range(-Infinity,0,true,true)
Domain.RME.displayName = "ℝ⁻*"
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.ceil(x)-1, 0))
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.ceil(x)-1, 1))
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.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.ceil(x)-1 === 0 ? Math.ceil(x)-2 : Math.ceil(x)-1)
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.ceil(x)-1 == 0 ? Math.ceil(x)-2 : Math.ceil(x)-1)
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.ceil(x)-1, 0))
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.ceil(x)-1, -1))
Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}"
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) {
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)
},
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)
})
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})
* @param {string} domain - string of the domain to be parsed.
* @returns {Domain} Parsed domain.
*/
export function parseDomain(domain) {
function parseDomain(domain) {
if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain)
let domStr
var domStr
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)
refedDomains.push(dom)
}
@ -602,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+*...
* @param {string} domain - string of the domain to be parsed.
* @returns {Domain} Parsed domain.
*/
export function parseDomainSimple(domain) {
function parseDomainSimple(domain) {
domain = domain.trim()
if(domain.includes("U") || domain.includes("")) return UnionDomain.import(domain)
if(domain.includes("∩")) return IntersectionDomain.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(["R", "", "N", "", "Z", ""].some(str => domain.toUpperCase().includes(str)))
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()
}

View file

@ -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()
}

View file

@ -1,103 +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 Latex from "latex.mjs"
import * as Utils from "../utils.mjs"
/**
* Represents any kind of x-based or non variable based expression.
*/
export class Expression {
constructor(expr) {
if(!Modules.ExprParser)
throw new Error('Expression parser not initialized.')
if(!Modules.Objects)
throw new Error('Objects API not initialized.')
this.expr = Utils.exponentsToExpression(expr)
this.calc = Modules.ExprParser.parse(this.expr).simplify()
this.cached = this.isConstant()
this.cachedValue = null
if(this.cached && this.allRequirementsFullfilled())
this.cachedValue = this.calc.evaluate(Modules.Objects.currentObjectsByName)
this.latexMarkup = Latex.expression(this.calc.tokens)
}
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 Modules.Objects.currentObjectsByName)
}
undefinedVariables() {
return this.requiredObjects().filter(objName => !(objName in Modules.Objects.currentObjectsByName))
}
recache() {
if(this.cached)
this.cachedValue = this.calc.evaluate(Modules.Objects.currentObjectsByName)
}
execute(x = 1) {
if(this.cached) {
if(this.cachedValue == null)
this.cachedValue = this.calc.evaluate(Modules.Objects.currentObjectsByName)
return this.cachedValue
}
Modules.ExprParser.currentVars = Object.assign({'x': x}, Modules.Objects.currentObjectsByName)
return this.calc.evaluate(Modules.ExprParser.currentVars)
}
simplify(x) {
let expr = this.calc.substitute('x', x).simplify()
if(expr.evaluate() === 0) return '0'
let 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) {
let str = Utils.makeExpressionReadable(this.calc.toString())
if(str[0] !== '-' && forceSign) str = '+' + str
return str
}
}
export function executeExpression(expr){
return (new Expression(expr.toString())).execute()
}

View 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]);
}

View file

@ -1,288 +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 '../modules.mjs'
const unicodechars = ["α","β","γ","δ","ε","ζ","η",
"π","θ","κ","λ","μ","ξ","ρ",
"ς","σ","τ","φ","χ","ψ","ω",
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀",
"pi", "∞"]
const 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", "\\infty"]
class LatexAPI extends Module {
constructor() {
super('Latex', [
/** @type {ExprParserAPI} */
Modules.ExprParser
])
/**
* true if latex has been enabled by the user, false otherwise.
*/
this.enabled = Helper.getSettingBool("enable_latex")
/**
* Mirror method for Python object.
* @type {function(string, number, string): string}.
*/
this.render = Latex.render
}
/**
* Puts element within parenthesis.
*
* @param {string} elem - element to put within parenthesis.
* @returns {string}
*/
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}
*/
parif(elem, contents) {
elem = elem.toString()
if(elem[0] !== "(" && elem[elem.length-1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
return this.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 {(number,string)[]} args - Arguments of the function
* @returns {string}
*/
functionToLatex(f, args) {
switch(f) {
case "derivative":
if(args.length === 3)
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}';
else
return '\\frac{d' + args[0] + '}{dx}(x)';
break;
case "integral":
if(args.length === 4)
return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2].substr(1, args[2].length-2) + ' d' + args[3].substr(1, args[3].length-2);
else
return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2] + '(t) dt';
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 text to convert
* @param {boolean} wrapIn$ - checks whether the escaped chars should be escaped
* @returns {string}
*/
variable(vari, wrapIn$ = false) {
if(wrapIn$)
for(let i = 0; i < unicodechars.length; i++) {
if(vari.includes(unicodechars[i]))
vari = vari.replace(new RegExp(unicodechars[i], 'g'), '$'+equivalchars[i]+'$')
}
else
for(let i = 0; i < unicodechars.length; 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}
*/
expression(tokens) {
let nstack = []
let n1, n2, n3
let f, args, argCount
for (let i = 0; i < tokens.length; i++) {
let item = tokens[i]
let type = item.type
switch(type) {
case Modules.ExprParser.Internals.INUMBER:
if(item.value === Infinity) {
nstack.push("\\infty")
} else if(typeof item.value === 'number' && item.value < 0) {
nstack.push(this.par(item.value));
} else if(Array.isArray(item.value)) {
nstack.push('[' + item.value.map(Modules.ExprParser.Internals.escapeValue).join(', ') + ']');
} else {
nstack.push(Modules.ExprParser.Internals.escapeValue(item.value));
}
break;
case Modules.ExprParser.Internals.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(this.par(n1) + f + this.par(n2));
break;
case '*':
if(n2 == "\\pi" || n2 == "e" || n2 == "x" || n2 == "n")
nstack.push(this.parif(n1,['+','-']) + n2)
else
nstack.push(this.parif(n1,['+','-']) + " \\times " + this.parif(n2,['+','-']));
break;
case '/':
nstack.push("\\frac{" + n1 + "}{" + n2 + "}");
break;
case '^':
nstack.push(this.parif(n1,['+','-','*','/','!']) + "^{" + n2 + "}");
break;
case '%':
nstack.push(this.parif(n1,['+','-','*','/','!','^']) + " \\mathrm{mod} " + parif(n2,['+','-','*','/','!','^']));
break;
case '[':
nstack.push(n1 + '[' + n2 + ']');
break;
default:
throw new EvalError("Unknown operator " + ope + ".");
}
break;
case Modules.ExprParser.Internals.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 Modules.ExprParser.Internals.IVAR:
case Modules.ExprParser.Internals.IVARNAME:
nstack.push(this.variable(item.value.toString()));
break;
case Modules.ExprParser.Internals.IOP1: // Unary operator
n1 = nstack.pop();
f = item.value;
switch(f) {
case '-':
case '+':
nstack.push(this.par(f + n1));
break;
case '!':
nstack.push(this.parif(n1,['+','-','*','/','^']) + '!');
break;
default:
nstack.push(f + this.parif(n1,['+','-','*','/','^']));
break;
}
break;
case Modules.ExprParser.Internals.IFUNCALL:
argCount = item.value;
args = [];
while (argCount-- > 0) {
args.unshift(nstack.pop());
}
f = nstack.pop();
// Handling various functions
nstack.push(this.functionToLatex(f, args))
break;
case Modules.ExprParser.Internals.IFUNDEF:
nstack.push(this.par(n1 + '(' + args.join(', ') + ') = ' + n2));
break;
case Modules.ExprParser.Internals.IMEMBER:
n1 = nstack.pop();
nstack.push(n1 + '.' + item.value);
break;
case Modules.ExprParser.Internals.IARRAY:
argCount = item.value;
args = [];
while (argCount-- > 0) {
args.unshift(nstack.pop());
}
nstack.push('[' + args.join(', ') + ']');
break;
case Modules.ExprParser.Internals.IEXPR:
nstack.push('(' + this.expression(item.value) + ')');
break;
case Modules.ExprParser.Internals.IENDSTATEMENT:
break;
default:
throw new EvalError('invalid Expression');
}
}
if (nstack.length > 1) {
nstack = [ nstack.join(';') ]
}
return String(nstack[0]);
}
}
/** @type {LatexAPI} */
Modules.Latex = Modules.Latex || new LatexAPI()
export default Modules.Latex

View file

@ -1,6 +1,6 @@
/**
* 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
* it under the terms of the GNU General Public License as published by
@ -16,14 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as Expr from "expression.mjs"
import * as Utils from "../utils.mjs"
import Latex from "../math/latex.mjs"
.pragma library
.import "common.js" as C
.import "expression.js" as Expr
.import "../utils.js" as Utils
.import "../math/latex.js" as Latex
/**
* Represents mathematical object for sequences.
*/
export class Sequence extends Expr.Expression {
class Sequence extends Expr.Expression {
constructor(name, baseValues = {}, valuePlus = 1, expr = "") {
// u[n+valuePlus] = expr
super(expr)
@ -31,17 +35,17 @@ export class Sequence extends Expr.Expression {
this.baseValues = baseValues
this.calcValues = 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])) {
let parsed = Modules.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.calcValues[n] = parsed.evaluate()
this.calcValues[n] = parsed.evaluate(C.evalVariables)
}
this.valuePlus = parseInt(valuePlus)
}
isConstant() {
return this.expr.indexOf("n") === -1
return this.expr.indexOf("n") == -1
}
execute(n = 1) {
@ -59,25 +63,19 @@ export class Sequence extends Expr.Expression {
}
cache(n = 1) {
let str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString())
let expr = Modules.ExprParser.parse(str).simplify()
// Cache values required for this one.
if(!this.calcValues[n-this.valuePlus] && n-this.valuePlus > 0)
this.cache(n-this.valuePlus)
// Setting current variables
Modules.ExprParser.currentVars = Object.assign(
{'n': n-this.valuePlus}, // Just in case, add n (for custom functions)
Modules.Objects.currentObjectsByName,
{[this.name]: this.calcValues}
)
this.calcValues[n] = expr.evaluate(Modules.ExprParser.currentVars)
var str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString())
var expr = C.parser.parse(str).simplify()
var l = {'n': n-this.valuePlus} // Just in case, add n (for custom functions)
l[this.name] = this.calcValues
C.currentVars = Object.assign(l, C.evalVariables)
this.calcValues[n] = expr.evaluate(C.currentVars)
}
toString(forceSign=false) {
let str = Utils.makeExpressionReadable(this.calc.toString())
if(str[0] !== '-' && forceSign) str = '+' + str
let subtxt = this.valuePlus === 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus)
let ret = `${this.name}${subtxt} = ${str}${this.baseValues.length === 0 ? '' : "\n"}`
var str = Utils.makeExpressionReadable(this.calc.toString())
if(str[0] != '-' && forceSign) str = '+' + str
var subtxt = this.valuePlus == 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus)
var ret = `${this.name}${subtxt} = ${str}${this.baseValues.length == 0 ? '' : "\n"}`
ret += Object.keys(this.baseValues).map(
n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}`
).join('; ')
@ -85,10 +83,10 @@ export class Sequence extends Expr.Expression {
}
toLatexString(forceSign=false) {
let str = this.latexMarkup
if(str[0] !== '-' && forceSign) str = '+' + str
let subtxt = '_{n' + (this.valuePlus === 0 ? '' : '+' + this.valuePlus) + '}'
let ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length === 0 ? '' : "\n"}\\\\`
var str = this.latexMarkup
if(str[0] != '-' && forceSign) str = '+' + str
var subtxt = '_{n' + (this.valuePlus == 0 ? '' : '+' + this.valuePlus) + '}'
var ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length == 0 ? '' : "\n"}\\\\`
ret += Object.keys(this.latexValues).map(
n => `${this.name}_{${n}} = ${this.latexValues[n]}`
).join('; ') + "\\end{array}"

View file

@ -1,49 +1,41 @@
/**
* 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
* 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 {BoolSetting} from "common.mjs"
.pragma library
const CHECK_FOR_UPDATES = new BoolSetting(
QT_TR_NOOP("Check for updates on startup"),
'check_for_updates',
'update'
)
.import "math/expression.js" as Expr
.import "math/sequence.js" as Seq
const RESET_REDO_STACK = new BoolSetting(
QT_TR_NOOP("Reset redo stack automaticly"),
'reset_redo_stack',
'timeline'
)
.import "math/domain.js" as Dom
class EnableLatex extends BoolSetting {
constructor() {
super(qsTr("Enable LaTeX rendering"), 'enable_latex', 'Expression')
}
var Expression = Expr.Expression
var executeExpression = Expr.executeExpression
var Sequence = Seq.Sequence
set(value) {
super.set(value)
Modules.Latex.enabled = value
Modules.Canvas.requestPaint()
}
}
// 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
export default [
CHECK_FOR_UPDATES,
RESET_REDO_STACK,
new EnableLatex()
]
var parseDomain = Dom.parseDomain
var parseDomainSimple = Dom.parseDomainSimple

View file

@ -1,45 +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/>.
*/
/**
* Base class for global APIs in runtime.
*/
export class Module {
/**
*
* @param {string} name - Name of the API
* @param {(Module|undefined)[]} requires - List of APIs required to initialize this one.
*/
constructor(name, requires = []) {
console.log(`Loading module ${name}...`)
this.__check_requirements(requires, name)
}
/**
* Checks if all requirements are defined.
* @param {(Module|undefined)[]} requires
* @param {string} name
*/
__check_requirements(requires, name) {
for(let requirement of requires) {
if(requirement === undefined)
throw new Error(`Requirement ${requires.indexOf(requirement)} of ${name} has not been initialized.`)
}
}
}

View file

@ -0,0 +1,76 @@
/**
* 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 "utils.js" as Utils
.import "mathlib.js" as MathLib
.import "parameters.js" as P
var types = {}
var currentObjects = {}
function getObjectByName(objName, objType = null) {
var objectTypes = Object.keys(currentObjects)
if(typeof objType == 'string' && objType != "") {
if(objType == "ExecutableObject") {
objectTypes = getExecutableTypes()
} else if(currentObjects[objType] != undefined) {
objectTypes = [objType]
}
}
if(Array.isArray(objType)) objectTypes = objType
var retObj = null
if(objName != "" && objName != null) {
objectTypes.forEach(function(objType){
if(currentObjects[objType] == undefined) return null
currentObjects[objType].forEach(function(obj){
if(obj.name == objName) retObj = obj
})
})
}
return retObj
}
function getObjectsName(objType) {
if(objType == "ExecutableObject") {
var types = getExecutableTypes()
var elementNames = ['']
types.forEach(function(elemType){
elementNames = elementNames.concat(currentObjects[elemType].map(obj => obj.name))
})
return elementNames
}
if(currentObjects[objType] == undefined) return []
return currentObjects[objType].map(obj => obj.name)
}
function getExecutableTypes() {
return Object.keys(currentObjects).filter(objType => types[objType].executable())
}
function createNewRegisteredObject(objType, args=[]) {
if(Object.keys(types).indexOf(objType) == -1) return null // Object type does not exist.
var newobj = new types[objType](...args)
if(Object.keys(currentObjects).indexOf(objType) == -1) {
currentObjects[objType] = []
}
currentObjects[objType].push(newobj)
return newobj
}

View file

@ -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 { Module } from './modules.mjs'
class ObjectsAPI extends Module {
constructor() {
super('Objects')
this.types = {}
/**
* List of objects for each type of object.
* @type {Object.<string,DrawableObject[]>}
*/
this.currentObjects = {}
/**
* List of objects matched by their name.
* @type {Object.<string,DrawableObject>}
*/
this.currentObjectsByName = {}
}
/**
* Renames an object from its old name to the new one.
* @param {string} oldName - Current name of the object.
* @param {string} newName - Name to rename the object to.
*/
renameObject(oldName, newName) {
let obj = this.currentObjectsByName[oldName]
delete this.currentObjectsByName[oldName]
this.currentObjectsByName[newName] = obj
obj.name = newName
}
/**
* Deletes an object by its given name.
* @param {string} objName - Current name of the object.
*/
deleteObject(objName) {
let obj = this.currentObjectsByName[objName]
if(obj !== undefined) {
this.currentObjects[obj.type].splice(this.currentObjects[obj.type].indexOf(obj),1)
obj.delete()
delete this.currentObjectsByName[objName]
}
}
/**
* Gets a list of all names of a certain object type.
* @param {string} objType - Type of the object to query. Can be ExecutableObject for all ExecutableObjects.
* @returns {string[]} List of names of the objects.
*/
getObjectsName(objType) {
if(objType === "ExecutableObject") {
// NOTE: QMLJS does not support flatMap.
// return getExecutableTypes().flatMap(elemType => currentObjects[elemType].map(obj => obj.name))
let types = this.getExecutableTypes()
let elementNames = ['']
for(let elemType of types)
elementNames = elementNames.concat(this.currentObjects[elemType].map(obj => obj.name))
return elementNames
}
if(this.currentObjects[objType] === undefined) return []
return this.currentObjects[objType].map(obj => obj.name)
}
/**
* Returns a list of all object types which are executable objects.
* @return {string[]} List of all object types which are executable objects.
*/
getExecutableTypes() {
return Object.keys(this.currentObjects).filter(objType => this.types[objType].executable())
}
/**
* Creates and register an object in the database.
* @param {string} objType - Type of the object to create.
* @param {string[]} args - List of arguments for the objects (can be empty).
* @return {DrawableObject<objType>} Newly created object.
*/
createNewRegisteredObject(objType, args= []) {
if(Object.keys(this.types).indexOf(objType) === -1) return null // Object type does not exist.
let newobj = new this.types[objType](...args)
if(Object.keys(this.currentObjects).indexOf(objType) === -1) {
this.currentObjects[objType] = []
}
this.currentObjects[objType].push(newobj)
this.currentObjectsByName[newobj.name] = newobj
return newobj
}
}
/** @type {ObjectsAPI} */
Modules.Objects = Modules.Objects || new ObjectsAPI()
export default Modules.Objects

View file

@ -1,6 +1,6 @@
/**
* 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
* it under the terms of the GNU General Public License as published by
@ -16,25 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
import * as Expr from "math/expression.mjs"
import * as Seq from "math/sequence.mjs"
import * as Dom from "math/domain.mjs"
.import "common.js" as C
.import "point.js" as P
.import "text.js" as T
.import "function.js" as F
.import "gainbode.js" as GB
.import "phasebode.js" as PB
.import "sommegainsbode.js" as SGB
.import "sommephasesbode.js" as SPB
.import "xcursor.js" as X
.import "sequence.js" as S
.import "repartition.js" as R
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
C.registerObject(P.Point)
C.registerObject(T.Text)
C.registerObject(F.Function)
C.registerObject(GB.GainBode)
C.registerObject(PB.PhaseBode)
C.registerObject(SGB.SommeGainsBode)
C.registerObject(SPB.SommePhasesBode)
C.registerObject(X.XCursor)
C.registerObject(S.Sequence)
C.registerObject(R.RepartitionFunction)

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