diff --git a/.gitignore b/.gitignore index 8802201..e2308a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,19 @@ +# Building build/ dist/ deb_dist/ -linux/flatpak/AppDir -linux/flatpak/repo -linux/flatpak/build-dir -linux/flatpak/.flatpak-builder +assets/linux/flatpak/AppDir +assets/linux/flatpak/repo +assets/linux/flatpak/build-dir +assets/linux/flatpak/.flatpak-builder *.snap *.spec *.zip +*.tar.gz +*.spec +*.egg-info/ + +# Runtime data **/**.qmlc **/**.jsc **/**.pyc @@ -20,17 +26,21 @@ linux/flatpak/.flatpak-builder .DS_Store **/.DS_Store **/__pycache__/ + +# IDE Data .ropeproject .vscode -build +*.kdev4 +.kdev4 docs/html .directory -*.kdev4 *.lpf *.lgg -*.spec -.kdev4 -AccountFree.pro -AccountFree.pro.user -*.egg-info/ -*.tar.gz + +# Tests +common/coverage/ +**/.coverage + +# npm +common/node_modules +runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/index.mjs* diff --git a/.gitmodules b/.gitmodules index df81e42..042c634 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "LogarithmPlotter/qml/eu/ad5001/MixedMenu"] - path = LogarithmPlotter/qml/eu/ad5001/MixedMenu +[submodule "runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/MixedMenu"] + path = runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/MixedMenu url = https://git.ad5001.eu/Ad5001/MixedMenu diff --git a/CHANGELOG.md b/CHANGELOG.md index be8eec8..c4b7c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v0.5.0 (11 Jan 2023) +## v0.5.0 (11 Jan 2024) **New** diff --git a/LogarithmPlotter/i18n/lp_de.ts b/LogarithmPlotter/i18n/lp_de.ts deleted file mode 100644 index 0bb06bf..0000000 --- a/LogarithmPlotter/i18n/lp_de.ts +++ /dev/null @@ -1,1692 +0,0 @@ - - - - - About - - - - About LogarithmPlotter - Über LogarithmPlotter - - - - LogarithmPlotter v%1 - LogarithmPlotter v%1 - - - - 2D plotter software to make BODE plots, sequences and repartition functions. - 2D-Grafiksoftware zur Erstellung von Bode-Diagramms, Folgen und Verteilungsfunktionen. - - - - Report a bug - Bug melden - - - - Official website - Offizielle Website - - - - AppMenuBar - - - &File - &Datei - - - - &Load... - &Laden… - - - - &Save - &Speichern - - - - Save &As... - Speichern &Unter… - - - - &Quit - &Ausfahrt - - - - &Edit - &Bearbeiten - - - - &Undo - &Lösen - - - - &Redo - &Wiederherstellen - - - - &Copy plot - Grafik &Kopieren - - - - &Create - &Erstellen - - - - &Settings - &Einstellungen - - - - Check for updates on startup - Beim Starten auf Updates prüfen - - - - Reset redo stack automaticly - Wiederherstellen-Stapel automatisch zurücksetzen - - - - Enable LaTeX rendering - LaTeX-Rendering aktivieren - - - - Expression editor - Ausdruckseditor - - - - Automatically close parenthesises and brackets - Klammern automatisch schließen - - - - Enable syntax highlighting - Syntaxhervorhebung einschalten - - - - Enable autocompletion - Automatische Vervollständigung einschalten - - - - Color Scheme - Syntaktische Färbung - - - - &Help - &Hilfe - - - - &Source code - &Quellcode - - - - &Report a bug - Fehler &Melden - - - - &User manual - &Benutzerhandbuch - - - - &Changelog - &Changelog - - - - &Help translating! - &Hilfe beim Übersetzen! - - - - &Thanks - &Danksagungen - - - - &About - &Übrigens - - - - Save unsaved changes? - Änderungen speichern? - - - - This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? - Diese Grafik enthält ungespeicherte Änderungen. Dadurch gehen alle ungespeicherten Daten verloren. Fortfahren? - - - - BaseDialog - - - Close - Schließen - - - - Changelog - - - Fetching changelog... - Changelog abrufen… - - - - Done - Schließen - - - - CustomPropertyList - - - - + Create new %1 - + Neues %1objekt erstellen - - - - Pick on graph - Aufnehmen auf Graph - - - - Dialog - - - Edit properties of %1 %2 - Eigenschaften von %1 %2 bearbeiten - - - - LogarithmPlotter - Invalid object name - LogarithmPlotter - Ungültiger Objektname - - - - An object with the name '%1' already exists. - Ein Objekt mit dem Namen '%1' existiert bereits. - - - - Name - Name - - - - Label content - Etikett - - - - null - leer - - - - name - Name - - - - name + value - Name + Wert - - - - EditorDialog - - Edit properties of %1 %2 - Eigenschaften von %1 %2 bearbeiten - - - Name - Name - - - Label content - Etikett - - - null - leer - - - name - Name - - - name + value - Name + Wert - - - + Create new %1 - + Neues %1objekt erstellen - - - - ExpressionEditor - - - Object Properties - Objekteigenschaften - - - - Variables - Variablen - - - - Constants - Konstanten - - - - Functions - Funktion - - - - Executable Objects - Funktionsobjekte - - - - Objects - Objekte - - - - FileDialog - - - Export Logarithm Plot file - Logarithmusgrafik exportieren - - - - Import Logarithm Plot file - Logarithmusgrafik importieren - - - - GreetScreen - - - Welcome to LogarithmPlotter - Willkommen bei LogarithmPlotter - - - - Version %1 - Version %1 - - - - Take a few seconds to configure LogarithmPlotter. -These settings can be changed at any time from the "Settings" menu. - Nehmen Sie sich ein paar Sekunden Zeit, um LogarithmPlotter zu konfigurieren. -Diese Einstellungen können jederzeit über das Menü "Einstellungen" geändert werden. - - - - Check for updates on startup (requires online connectivity) - Beim Start nach Updates suchen (Online-Verbindung erforderlich) - - - - Reset redo stack when a new action is added to history - Redo-Stapel zurücksetzen, wenn eine neue Aktion zur Historie hinzugefügt wird - - - - Enable LaTeX rendering - LaTeX-Rendering aktivieren - - - - Automatically close parenthesises and brackets in expressions - Automatisches Schließen von Klammern in Ausdrücken - - - - Enable syntax highlighting for expressions - Syntaxhervorhebung für Ausdrücke einschalten - - - - Enable autocompletion interface in expression editor - Schnittstelle zur automatischen Vervollständigung im Ausdruckseditor aktivieren - - - - Color scheme: - Syntaktische Färbung Thema: - - - - User manual - Benutzerhandbuch - - - - Changelog - Changelog - - - - Done - Schließen - - - - HistoryBrowser - - - Filter... - Filtern… - - - - Redo > - Wiederherstellen > - - - - > Now - > Aktueller Stand - - - - < Undo - < Rückgängig - - - - ListSetting - - - + Add Entry - + Neuer Eintrag - - - - LogarithmPlotter - - - Objects - Objekte - - - - Settings - Einstellungen - - - - History - Verlauf - - - - Saved plot to '%1'. - Gespeicherte Grafik auf '%1'. - - - - Loading file '%1'. - Laden der Datei '%1'. - - - - Unknown object type: %1. - Unbekannter Objekttyp: %1. - - - - Invalid file provided. - Ungültige Datei angegeben. - - - - Could not save file: - Die Datei konnte nicht gespeichert werden: - - - - Loaded file '%1'. - Geladene Datei '%1'. - - - - Copied plot screenshot to clipboard! - Grafik in die Zwischenablage kopiert! - - - - &Update - &Aktualisieren - - - - &Update LogarithmPlotter - LogarithmPlotter &aktualisieren - - - - ObjectCreationGrid - - - + Create new: - + Neu erstellen: - - - - ObjectLists - - - Hide all %1 - Alle %1 ausblenden - - - - Show all %1 - Alle %1 anzeigen - - - Hide %1 %2 - Ausblenden %1 %2 - - - Show %1 %2 - Anzeigen %1 %2 - - - Set %1 %2 position - Position von %1 %2 einstellen - - - Delete %1 %2 - %1 %2 löschen - - - Pick new color for %1 %2 - Neue Farbe für %1 %2 auswählen - - - - ObjectRow - - - Hide %1 %2 - Ausblenden %1 %2 - - - - Show %1 %2 - Anzeigen %1 %2 - - - - Set %1 %2 position - Position von %1 %2 einstellen - - - - Delete %1 %2 - %1 %2 löschen - - - - Pick new color for %1 %2 - Neue Farbe für %1 %2 auswählen - - - - PickLocationOverlay - - - Pointer precision: - Genauigkeit des Zeigers: - - - Snap to grid - Am Gitter einrasten - - - - Snap to grid: - Am Raster einrasten: - - - - Pick X - X nehmen - - - - Pick Y - Y nehmen - - - - Open picker settings - Zeigereinstellungen öffnen - - - - Hide picker settings - Zeigereinstellungen ausblenden - - - - (no pick selected) - (keine Auswahl ausgewählt) - - - - Settings - - - X Zoom - Zoom auf X - - - - Y Zoom - Zoom auf Y - - - - Min X - Minimum X - - - - Max Y - Maximum Y - - - - Max X - Maximum X - - - - Min Y - Minimum Y - - - - X Axis Step - X-Achsen-Schritt - - - - Y Axis Step - Y-Achsen-Schritt - - - - Line width - Linienbreite - - - - Text size (px) - Textgröße (px) - - - - X Label - Etikett der X-Achse - - - - Y Label - Etikett der Y-Achse - - - - X Log scale - Logarithmische Skala in X - - - - Show X graduation - X-Teilung anzeigen - - - - Show Y graduation - Y-Teilung anzeigen - - - - Copy to clipboard - Kopieren in die Zwischenablage - - - - Save plot - Grafik speichern - - - - Save plot as - Grafik speichern unter - - - - Load plot - Grafik laden - - - - ThanksTo - - - Thanks and Contributions - LogarithmPlotter - Danksagungen und Beiträge - LogarithmPlotter - - - - Source code - Quellcode - - - - Original library by Raphael Graf - Originalbibliothek von Raphael Graf - - - - Source - Quelle - - - - Ported to Javascript by Matthew Crumley - Portiert auf Javascript von Matthew Crumley - - - - - - - - Website - Website - - - - Ported to QMLJS by Ad5001 - Portiert auf QMLJS von Ad5001 - - - - Libraries included - Einschließlich Bibliotheken - - - - Email - E-Mail - - - - English - Englisch - - - - French - Französisch - - - - German - Deutsch - - - - Hungarian - Ungarisch - - - - - Github - Github - - - - Norwegian - Norwegisch - - - - Translations included - Einschließlich Übersetzungen - - - - Improve - Verbessern - - - - changelog - - - Could not fetch changelog: Server error {}. - Changelog konnte nicht geholt werden: Server-Fehler {}. - - - - Could not fetch update: {}. - Changelog konnte nicht geholt werden: {}. - - - - color - - - - %1 %2's color changed from %3 to %4. - %1 %2 wurde von %3 bis %4 umgefärbt. - - - - comment - - - Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - Beispiel: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ-*), ]0;1[, {3;4;5} - - - - The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) - Die folgenden Parameter werden verwendet, wenn der Definitionsbereich eine nicht kontinuierliche Menge ist. (Beispiel: ℕ, ℤ, Mengen wie {0;3}...) - - - - Note: Specify the probability for each value. - Hinweis: Geben Sie die Wahrscheinlichkeit für jeden Wert an. - - - - Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... - Hinweis: Verwenden Sie %1[n], um sich auf %1ₙ zu beziehen, %1[n+1] für %1ₙ₊₁… - - - - If you have latex enabled, you can use use latex markup in between $$ to create equations. - Wenn Sie Latex aktiviert haben, können Sie Latex-Auszeichnungen zwischen $$ verwenden, um Gleichungen zu erstellen. - - - - control - - - - - - - %1: - %1: - - - - create - - - - New %1 %2 created. - Neu %1 %2 erstellt. - - - - delete - - - - %1 %2 deleted. - %1 %2 gelöscht. - - - - editproperty - - - %1 of %2 %3 changed from "%4" to "%5". - %1 von %2 %3 wurde von "%4" auf "%5" geändert. - - - - %1 of %2 changed from %3 to %4. - %1 von %2 wurde von %3 auf %4 geändert. - - - - error - - - - Cannot find property %1 of object %2. - Eigenschaft %1 von Objekt %2 kann nicht gefunden werden. - - - - Undefined variable %1. - Die Variable %1 ist nicht definiert. - - - - In order to be executed, object %1 must have at least one argument. - Um als Funktion verwendet zu werden, benötigt das Objekt %1 mindestens ein Parameter. - - - - %1 cannot be executed. - %1 ist keine Formel. - - - - - - Invalid expression. - Ungültiger Ausdruck. - - - - Invalid expression (parity). - Ungültiger Ausdruck (Parität). - - - - Unknown character "%1". - Unbekanntes Schriftzeichen "%1". - - - - - Illegal escape sequence: %1. - Unzulässige Escapesequenz: %1. - - - - - Parse error [%1:%2]: %3 - Analysefehler [%1:%2]: %3 - - - - Expected %1 - Erwartet %1 - - - - Unexpected %1 - Unerwartetes %1 - - - Function definition is not permitted. - Funktionsdefinition ist nicht erlaubt. - - - Expected variable for assignment. - Erwartete Variable für Zuweisung. - - - - Unexpected ".": member access is not permitted - Unerwartetes ".": Mitgliederzugriff ist nicht erlaubt - - - - Unexpected "[]": arrays are disabled. - Unerwartetes "[]": Arrays sind deaktiviert. - - - - Unexpected symbol: %1. - Unerwartetes Symbol: %1. - - - - - Function %1 must have at least one argument. - Die Funktion %1 benötigt mindestens ein Parameter. - - - - - First argument to map is not a function. - Der erste Parameter von map ist keine Formel. - - - - - Second argument to map is not an array. - Der zweite Parameter von map ist kein Array. - - - - - First argument to fold is not a function. - Der erste Parameter für fold ist keine Formel. - - - - - Second argument to fold is not an array. - Der zweite Parameter für fold ist kein Array. - - - - - - First argument to filter is not a function. - Der erste Parameter für filter ist keine Formel. - - - - - - Second argument to filter is not an array. - Der zweite Parameter von filter ist kein Array. - - - - - Second argument to indexOf is not a string or array. - Der zweite Parameter von indexOf ist kein String oder Array. - - - - - Second argument to join is not an array. - Der zweite Parameter von join ist kein Array. - - - - EOF - Ende des Ausdrucks - - - - No object found with names %1. - Kein Objekt mit Namen %1 gefunden. - - - - No object found with name %1. - Kein Objekt mit dem Namen %1 gefunden. - - - - Object cannot be dependent on itself. - Ein Objekt kann nicht von sich selbst abhängen. - - - - Circular dependency detected. Object %1 depends on %2. - Zirkuläre Abhängigkeit entdeckt. Objekt %1 hängt von %2 ab. - - - - Circular dependency detected. Objects %1 depend on %2. - Zirkuläre Abhängigkeit entdeckt. Objekte %1 hängen von %2 ab. - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - Fehler beim Analysieren des Ausdrucks für die Eigenschaft %1: -%2 - -Ausdruck analysiert: %3 - - - - Error while attempting to draw %1 %2: -%3 - -Undoing last change. - Fehler beim Versuch, das %1 %2 zu zeichnen: -%3 - -Die letzte Änderung wurde rückgängig gemacht. - - - - expression - - - - LogarithmPlotter - Parsing error - LogarithmPlotter - Analysefehler - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - Fehler beim Analysieren des Ausdrucks für die Eigenschaft %1: -%2 - -Ausdruck analysiert: %3 - - - - LogarithmPlotter - Drawing error - LogarithmPlotter - Fehler - - - - function - - - Function - Funktion - - - - Functions - Funktionen - - - - gainbode - - - Bode Magnitude - Bode-Magnitude - - - - Bode Magnitudes - Bode-Magnituden - - - - - low-pass - Tiefpass - - - - - high-pass - Hochpass - - - - historylib - - New %1 %2 created. - Neu %1 %2 erstellt. - - - %1 %2 deleted. - %1 %2 gelöscht. - - - %1 of %2 %3 changed from "%4" to "%5". - %1 von %2 %3 wurde von "%4" auf "%5" geändert. - - - %1 %2 shown. - %1 %2 angezeigt. - - - %1 %2 hidden. - %1 %2 ausgeblendet. - - - Name of %1 %2 changed to %3. - Der Name von %1 %2 wurde in %3 geändert. - - - - latex - - - No Latex installation found. -If you already have a latex distribution installed, make sure it's installed on your path. -Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. - Keine LaTeX-Installation gefunden. -Wenn Sie bereits eine LaTeX-Distribution installiert haben, vergewissern Sie sich, dass sie in Ihrem Pfad installiert ist. -Andernfalls können Sie eine LaTeX-Distribution wie TeX Live unter https://tug.org/texlive/ herunterladen. - - - - DVIPNG was not found. Make sure you include it from your Latex distribution. - DVIPNG wurde nicht gefunden. Stellen Sie sicher, dass Sie es aus Ihrer LaTeX-Distribution einbinden. - - - - An exception occured within the creation of the latex formula. -Process '{}' ended with a non-zero return code {}: - -{} -Please make sure your latex installation is correct and report a bug if so. - Bei der Erstellung der LaTeX-Formel ist eine Exception aufgetreten. -Der Prozess '{}' wurde mit einem Rückgabecode ungleich Null beendet {}: - -{} -Bitte vergewissern Sie sich, dass Ihre LaTeX-Installation korrekt ist, und melden Sie einen Fehler, falls dies der Fall ist. - - - - An exception occured within the creation of the latex formula. -Process '{}' took too long to finish: -{} -Please make sure your latex installation is correct and report a bug if so. - Bei der Erstellung der LaTeX-Formel ist eine Exception aufgetreten. -Der Prozess '{}' brauchte zu lange, um beendet zu werden: -{} -Bitte vergewissern Sie sich, dass Ihre LaTeX-Installation korrekt ist, und melden Sie einen Fehler, falls dies der Fall ist. - - - - name - - - - %1 %2 renamed to %3. - %1 %2 umbenannt in %3. - - - - parameters - - - above - ↑ Über - - - - below - ↓ Unter - - - - - left - ← Links - - - - - right - → Rechts - - - - above-left - ↖ Oben links - - - - above-right - ↗ Oben rechts - - - - below-left - ↙ Unten links - - - - below-right - ↘ Unten rechts - - - - center - >|< Zentrum - - - - top - ↑ Über - - - - bottom - ↓ Unter - - - - top-left - ↖ Oben links - - - - top-right - ↗ Oben rechts - - - - bottom-left - ↙ Unten links - - - - bottom-right - ↘ Unten rechts - - - - application - Anwendung - - - - function - Funktion - - - - high - Hoch - - - - low - Tief - - - - Next to target - Neben dem Ziel - - - - With label - Mit Etikett - - - - Hidden - Versteckt - - - - phasebode - - - Bode Phase - Bode-Phase - - - - Bode Phases - Bode-Phasen - - - - point - - - Point - Punkt - - - - Points - Punkte - - - - position - - - Position of %1 %2 set from "%3" to "%4". - %1 %2 wurde von "%3" nach "%4" verschoben. - - - - Position of %1 set from %2 to %3. - %1 wurde von %2 nach %3 verschoben. - - - - prop - - - expression - Ausdruck - - - - definitionDomain - Definitionsbereich - - - - destinationDomain - Reichweite - - - - - - - - - - - - - labelPosition - Position des Etiketts - - - - displayMode - Anzeigemodus - - - - - - - - - - labelX - X-Position des Etiketts - - - - - drawPoints - Unentschiedene Punkte - - - - - drawDashedLines - Gestrichelte Linien anzeigen - - - - - om_0 - ω₀ - - - - pass - Pass - - - - gain - Größenordnung - - - - omGraduation - Teilung auf ω zeigen - - - - phase - Phase - - - - unit - Einheit - - - - - - x - X - - - - - y - Y - - - - pointStyle - Punkt-Stil - - - - probabilities - Wahrscheinlichkeiten - - - - text - Inhalt - - - - disableLatex - LaTeX-Rendering für diesen Text deaktivieren - - - - targetElement - Zielobjekt - - - - approximate - Ungefähren Wert anzeigen - - - - rounding - Rundung - - - - displayStyle - Stil - - - - targetValuePosition - Wertposition des Ziels - - - - defaultExpression - Standardausdruck - - - - baseValues - Initialisierungswerte - - - color - Farbe - - - - repartition - - - Repartition - Verteilungsfunktion - - - - Repartition functions - Verteilungsfunktionen - - - - sequence - - - Sequence - Folge - - - - Sequences - Folgen - - - - sommegainsbode - - - - Bode Magnitudes Sum - Bode-Magnituden Summe - - - - sommephasesbode - - - - Bode Phases Sum - Bode-Phasen Summe - - - - text - - - Text - Text - - - - Texts - Texte - - - - update - - - An update for LogarithPlotter (v{}) is available. - Ein Aktualisierung für LogarithmPlotter (v{}) ist verfügbar. - - - - No update available. - Keine Aktualisierung verfügbar. - - - - Could not fetch update information: Server error {}. - Es konnten keine Aktualisierungsinformationen abgerufen werden: Server-Fehler {}. - - - - Could not fetch update information: {}. - Es konnten keine Aktualisierungsinformationen abgerufen werden:{}. - - - - usage - - - - Usage: %1 - Verwendung: %1 - - - - - - Usage: %1 or -%2 - Verwendung: %1 oder -%2 - - - - integral(<from: number>, <to: number>, <f: ExecutableObject>) - integral(<von: Zahl>, <bis: Zahl>, <f: ExecutableObject>) - - - - integral(<from: number>, <to: number>, <f: string>, <variable: string>) - integral(<von: Zahl>, <bis: Zahl>, <f: String>, <Variablen: String>) - - - - derivative(<f: ExecutableObject>, <x: number>) - derivative(<f: ExecutableObject>, <x: Zahl>) - - - - derivative(<f: string>, <variable: string>, <x: number>) - derivative(<f: String>, <Variablen: String>, <x: Zahl>) - - - - visibility - - - - %1 %2 shown. - %1 %2 angezeigt. - - - - - %1 %2 hidden. - %1 %2 ausgeblendet. - - - - xcursor - - - X Cursor - X Zeiger - - - - X Cursors - X Zeiger - - - diff --git a/LogarithmPlotter/i18n/lp_en.ts b/LogarithmPlotter/i18n/lp_en.ts deleted file mode 100644 index 91b0e51..0000000 --- a/LogarithmPlotter/i18n/lp_en.ts +++ /dev/null @@ -1,1692 +0,0 @@ - - - - - About - - - - About LogarithmPlotter - About LogarithmPlotter - - - - LogarithmPlotter v%1 - LogarithmPlotter v%1 - - - - 2D plotter software to make BODE plots, sequences and repartition functions. - 2D plotter software to make Bode plots, sequences and distribution functions. - - - - Report a bug - Report a bug - - - - Official website - Official website - - - - AppMenuBar - - - &File - &File - - - - &Load... - &Open… - - - - &Save - &Save - - - - Save &As... - Save &As… - - - - &Quit - &Quit - - - - &Edit - &Edit - - - - &Undo - &Undo - - - - &Redo - &Redo - - - - &Copy plot - &Copy plot - - - - &Create - &Create - - - - &Settings - &Settings - - - - Check for updates on startup - Check for updates on startup - - - - Reset redo stack automaticly - Reset redo stack automatically - - - - Enable LaTeX rendering - Enable LaTeX rendering - - - - Expression editor - Expression editor - - - - Automatically close parenthesises and brackets - Automatically close parentheses and brackets - - - - Enable syntax highlighting - Enable syntax highlighting - - - - Enable autocompletion - Enable autocompletion - - - - Color Scheme - Color Scheme - - - - &Help - &Help - - - - &Source code - &Source code - - - - &Report a bug - &Report a bug - - - - &User manual - &User manual - - - - &Changelog - &Changelog - - - - &Help translating! - &Help translating! - - - - &Thanks - &Thanks - - - - &About - &About - - - - Save unsaved changes? - Save unsaved changes? - - - - This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? - This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? - - - - BaseDialog - - - Close - Close - - - - Changelog - - - Fetching changelog... - Fetching changelog… - - - - Done - Done - - - - CustomPropertyList - - - - + Create new %1 - + Create new %1 - - - - Pick on graph - Pick on graph - - - - Dialog - - - Edit properties of %1 %2 - Edit properties of %1 %2 - - - - LogarithmPlotter - Invalid object name - LogarithmPlotter - Invalid object name - - - - An object with the name '%1' already exists. - An object with the name '%1' already exists. - - - - Name - Name - - - - Label content - Label content - - - - null - null - - - - name - name - - - - name + value - name + value - - - - EditorDialog - - Edit properties of %1 %2 - Edit properties of %1 %2 - - - Name - Name - - - Label content - Label content - - - null - null - - - name - name - - - name + value - name + value - - - + Create new %1 - + Create new %1 - - - - ExpressionEditor - - - Object Properties - Object Properties - - - - Variables - Variables - - - - Constants - Constants - - - - Functions - Functions - - - - Executable Objects - Function Objects - - - - Objects - Objects - - - - FileDialog - - - Export Logarithm Plot file - Export Logarithm Plot file - - - - Import Logarithm Plot file - Import Logarithm Plot file - - - - GreetScreen - - - Welcome to LogarithmPlotter - Welcome to LogarithmPlotter - - - - Version %1 - Version %1 - - - - Take a few seconds to configure LogarithmPlotter. -These settings can be changed at any time from the "Settings" menu. - Take a few seconds to configure LogarithmPlotter. -These settings can be changed at any time from the "Settings" menu. - - - - Check for updates on startup (requires online connectivity) - Check for updates on startup (requires online connectivity) - - - - Reset redo stack when a new action is added to history - Reset redo stack when a new action is added to history - - - - Enable LaTeX rendering - Enable LaTeX rendering - - - - Automatically close parenthesises and brackets in expressions - Automatically close parentheses and brackets in expressions - - - - Enable syntax highlighting for expressions - Enable syntax highlighting for expressions - - - - Enable autocompletion interface in expression editor - Enable autocompletion interface in expression editor - - - - Color scheme: - Color scheme: - - - - User manual - User manual - - - - Changelog - Changelog - - - - Done - Done - - - - HistoryBrowser - - - Filter... - Filter… - - - - Redo > - Redo > - - - - > Now - > Now - - - - < Undo - < Undo - - - - ListSetting - - - + Add Entry - + Add Entry - - - - LogarithmPlotter - - - Objects - Objects - - - - Settings - Settings - - - - History - History - - - - Saved plot to '%1'. - Saved plot to '%1'. - - - - Loading file '%1'. - Loading file '%1'. - - - - Unknown object type: %1. - Unknown object type: %1. - - - - Invalid file provided. - Invalid file provided. - - - - Could not save file: - Could not save file: - - - - Loaded file '%1'. - Loaded file '%1'. - - - - Copied plot screenshot to clipboard! - Copied plot screenshot to clipboard! - - - - &Update - &Update - - - - &Update LogarithmPlotter - &Update LogarithmPlotter - - - - ObjectCreationGrid - - - + Create new: - + Create new: - - - - ObjectLists - - - Hide all %1 - Hide all %1 - - - - Show all %1 - Show all %1 - - - Hide %1 %2 - Hide %1 %2 - - - Show %1 %2 - Show %1 %2 - - - Set %1 %2 position - Set %1 %2 position - - - Delete %1 %2 - Delete %1 %2 - - - Pick new color for %1 %2 - Pick new color for %1 %2 - - - - ObjectRow - - - Hide %1 %2 - Hide %1 %2 - - - - Show %1 %2 - Show %1 %2 - - - - Set %1 %2 position - Set %1 %2 position - - - - Delete %1 %2 - Delete %1 %2 - - - - Pick new color for %1 %2 - Pick new color for %1 %2 - - - - PickLocationOverlay - - - Pointer precision: - Pointer precision: - - - Snap to grid - Snap to grid - - - - Snap to grid: - Snap to grid: - - - - Pick X - Pick X - - - - Pick Y - Pick Y - - - - Open picker settings - Open picker settings - - - - Hide picker settings - Hide picker settings - - - - (no pick selected) - (no pick selected) - - - - Settings - - - X Zoom - X Zoom - - - - Y Zoom - Y Zoom - - - - Min X - Min X - - - - Max Y - Max Y - - - - Max X - Max X - - - - Min Y - Min Y - - - - X Axis Step - X Axis Step - - - - Y Axis Step - Y Axis Step - - - - Line width - Line width - - - - Text size (px) - Text size (px) - - - - X Label - X Label - - - - Y Label - Y Label - - - - X Log scale - X Log scale - - - - Show X graduation - Show X graduation - - - - Show Y graduation - Show Y graduation - - - - Copy to clipboard - Copy to clipboard - - - - Save plot - Save plot - - - - Save plot as - Save plot as - - - - Load plot - Open plot - - - - ThanksTo - - - Thanks and Contributions - LogarithmPlotter - Thanks and Contributions - LogarithmPlotter - - - - Source code - Source code - - - - Original library by Raphael Graf - Original library by Raphael Graf - - - - Source - Source - - - - Ported to Javascript by Matthew Crumley - Ported to Javascript by Matthew Crumley - - - - - - - - Website - Website - - - - Ported to QMLJS by Ad5001 - Ported to QMLJS by Ad5001 - - - - Libraries included - Libraries included - - - - Email - Email - - - - English - English - - - - French - French - - - - German - German - - - - Hungarian - Hungarian - - - - - Github - Github - - - - Norwegian - Norwegian - - - - Translations included - Translations included - - - - Improve - Improve - - - - changelog - - - Could not fetch changelog: Server error {}. - Could not fetch changelog: Server error {}. - - - - Could not fetch update: {}. - Could not fetch changelog: {}. - - - - color - - - - %1 %2's color changed from %3 to %4. - %1 %2's color changed from %3 to %4. - - - - comment - - - Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - - - - The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) - The following parameters are used when the domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}…) - - - - Note: Specify the probability for each value. - Note: Specify the probability for each value. - - - - Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... - Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁… - - - - If you have latex enabled, you can use use latex markup in between $$ to create equations. - If you have latex enabled, you can use use latex markup in between $$ to create equations. - - - - control - - - - - - - %1: - %1: - - - - create - - - - New %1 %2 created. - New %1 %2 created. - - - - delete - - - - %1 %2 deleted. - %1 %2 deleted. - - - - editproperty - - - %1 of %2 %3 changed from "%4" to "%5". - %1 of %2 %3 changed from "%4" to "%5". - - - - %1 of %2 changed from %3 to %4. - %1 of %2 changed from %3 to %4. - - - - error - - - - Cannot find property %1 of object %2. - Cannot find property %1 of object %2. - - - - Undefined variable %1. - Undefined variable %1. - - - - In order to be executed, object %1 must have at least one argument. - In order to be executed, object %1 must have at least one argument. - - - - %1 cannot be executed. - %1 is not a function. - - - - - - Invalid expression. - Invalid expression. - - - - Invalid expression (parity). - Invalid expression (parity). - - - - Unknown character "%1". - Unknown character "%1". - - - - - Illegal escape sequence: %1. - Illegal escape sequence: %1. - - - - - Parse error [%1:%2]: %3 - Parse error [%1:%2]: %3 - - - - Expected %1 - Expected %1 - - - - Unexpected %1 - Unexpected %1 - - - Function definition is not permitted. - Function definition is not permitted. - - - Expected variable for assignment. - Expected variable for assignment. - - - - Unexpected ".": member access is not permitted - Unexpected ".": member access is not permitted - - - - Unexpected "[]": arrays are disabled. - Unexpected "[]": arrays are disabled. - - - - Unexpected symbol: %1. - Unexpected symbol: %1. - - - - - Function %1 must have at least one argument. - Function %1 must have at least one argument. - - - - - First argument to map is not a function. - First argument to map is not a function. - - - - - Second argument to map is not an array. - Second argument to map is not an array. - - - - - First argument to fold is not a function. - First argument to fold is not a function. - - - - - Second argument to fold is not an array. - Second argument to fold is not an array. - - - - - - First argument to filter is not a function. - First argument to filter is not a function. - - - - - - Second argument to filter is not an array. - Second argument to filter is not an array. - - - - - Second argument to indexOf is not a string or array. - Second argument to indexOf is not a string or array. - - - - - Second argument to join is not an array. - Second argument to join is not an array. - - - - EOF - End of expression - - - - No object found with names %1. - No object found with names %1. - - - - No object found with name %1. - No object found with name %1. - - - - Object cannot be dependent on itself. - Object cannot be dependent on itself. - - - - Circular dependency detected. Object %1 depends on %2. - Circular dependency detected. Object %1 depends on %2. - - - - Circular dependency detected. Objects %1 depend on %2. - Circular dependency detected. Objects %1 depend on %2. - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - - - - Error while attempting to draw %1 %2: -%3 - -Undoing last change. - Error while attempting to draw %1 %2: -%3 - -Undoing last change. - - - - expression - - - - LogarithmPlotter - Parsing error - LogarithmPlotter - Parsing error - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - - - - LogarithmPlotter - Drawing error - LogarithmPlotter - Drawing error - - - - function - - - Function - Function - - - - Functions - Functions - - - - gainbode - - - Bode Magnitude - Bode Magnitude - - - - Bode Magnitudes - Bode Magnitudes - - - - - low-pass - low-pass - - - - - high-pass - high-pass - - - - historylib - - New %1 %2 created. - New %1 %2 created. - - - %1 %2 deleted. - %1 %2 deleted. - - - %1 of %2 %3 changed from "%4" to "%5". - %1 of %2 %3 changed from "%4" to "%5". - - - %1 %2 shown. - %1 %2 shown. - - - %1 %2 hidden. - %1 %2 hidden. - - - Name of %1 %2 changed to %3. - Name of %1 %2 changed to %3. - - - - latex - - - No Latex installation found. -If you already have a latex distribution installed, make sure it's installed on your path. -Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. - No LaTeX installation found. -If you already have a LaTeX distribution installed, make sure it's installed on your path. -Otherwise, you can download a LaTeX distribution like TeX Live at https://tug.org/texlive/. - - - - DVIPNG was not found. Make sure you include it from your Latex distribution. - DVIPNG was not found. Make sure you include it from your LaTeX distribution. - - - - An exception occured within the creation of the latex formula. -Process '{}' ended with a non-zero return code {}: - -{} -Please make sure your latex installation is correct and report a bug if so. - An exception occurred within the creation of the LaTeX formula. -Process '{}' ended with a non-zero return code {}: - -{} -Please make sure your LaTeX installation is correct and report a bug if so. - - - - An exception occured within the creation of the latex formula. -Process '{}' took too long to finish: -{} -Please make sure your latex installation is correct and report a bug if so. - An exception occurred within the creation of the LaTeX formula. -Process '{}' took too long to finish: -{} -Please make sure your LaTeX installation is correct and report a bug if so. - - - - name - - - - %1 %2 renamed to %3. - %1 %2 renamed to %3. - - - - parameters - - - above - ↑ Above - - - - below - ↓ Below - - - - - left - ← Left - - - - - right - → Right - - - - above-left - ↖ Above left - - - - above-right - ↗ Above right - - - - below-left - ↙ Below left - - - - below-right - ↘ Below right - - - - center - >|< Center - - - - top - ↑ Top - - - - bottom - ↓ Bottom - - - - top-left - ↖ Top left - - - - top-right - ↗ Top right - - - - bottom-left - ↙ Bottom left - - - - bottom-right - ↘ Bottom right - - - - application - Application - - - - function - Function - - - - high - High - - - - low - Low - - - - Next to target - Next to target - - - - With label - With label - - - - Hidden - Hidden - - - - phasebode - - - Bode Phase - Bode Phase - - - - Bode Phases - Bode Phases - - - - point - - - Point - Point - - - - Points - Points - - - - position - - - Position of %1 %2 set from "%3" to "%4". - %1 %2 moved from "%3" to "%4". - - - - Position of %1 set from %2 to %3. - %1 moved from %2 to %3. - - - - prop - - - expression - Expression - - - - definitionDomain - Domain - - - - destinationDomain - Range - - - - - - - - - - - - - labelPosition - Label position - - - - displayMode - Display mode - - - - - - - - - - labelX - Label's X position - - - - - drawPoints - Show points - - - - - drawDashedLines - Show dashed lines - - - - - om_0 - ω₀ - - - - pass - Pass - - - - gain - Magnitude gain - - - - omGraduation - Show graduation on ω₀ - - - - phase - Phase - - - - unit - Unit to use - - - - - - x - X - - - - - y - Y - - - - pointStyle - Point style - - - - probabilities - Probabilities list - - - - text - Content - - - - disableLatex - Disable LaTeX rendering for this text - - - - targetElement - Object to target - - - - approximate - Show approximate value - - - - rounding - Rounding - - - - displayStyle - Display style - - - - targetValuePosition - Target's value position - - - - defaultExpression - Default expression - - - - baseValues - Initialisation values - - - color - Color - - - - repartition - - - Repartition - Distribution - - - - Repartition functions - Distribution functions - - - - sequence - - - Sequence - Sequence - - - - Sequences - Sequences - - - - sommegainsbode - - - - Bode Magnitudes Sum - Bode Magnitudes Sum - - - - sommephasesbode - - - - Bode Phases Sum - Bode Phases Sum - - - - text - - - Text - Text - - - - Texts - Texts - - - - update - - - An update for LogarithPlotter (v{}) is available. - An update for LogarithmPlotter (v{}) is available. - - - - No update available. - No update available. - - - - Could not fetch update information: Server error {}. - Could not fetch update information: Server error {}. - - - - Could not fetch update information: {}. - Could not fetch update information: {}. - - - - usage - - - - Usage: %1 - Usage: %1 - - - - - - Usage: %1 or -%2 - Usage: %1 or -%2 - - - - integral(<from: number>, <to: number>, <f: ExecutableObject>) - integral(<from: number>, <to: number>, <f: ExecutableObject>) - - - - integral(<from: number>, <to: number>, <f: string>, <variable: string>) - integral(<from: number>, <to: number>, <f: string>, <variable: string>) - - - - derivative(<f: ExecutableObject>, <x: number>) - derivative(<f: ExecutableObject>, <x: number>) - - - - derivative(<f: string>, <variable: string>, <x: number>) - derivative(<f: string>, <variable: string>, <x: number>) - - - - visibility - - - - %1 %2 shown. - %1 %2 shown. - - - - - %1 %2 hidden. - %1 %2 hidden. - - - - xcursor - - - X Cursor - X Cursor - - - - X Cursors - X Cursors - - - diff --git a/LogarithmPlotter/i18n/lp_es.ts b/LogarithmPlotter/i18n/lp_es.ts deleted file mode 100644 index 11bbbc5..0000000 --- a/LogarithmPlotter/i18n/lp_es.ts +++ /dev/null @@ -1,1578 +0,0 @@ - - - - - About - - - - About LogarithmPlotter - Sobre LogarithmPlotter - - - - LogarithmPlotter v%1 - LogarithmPlotter v%1 - - - - 2D plotter software to make BODE plots, sequences and repartition functions. - Software de trazado 2D para diagramas de Bode, secuencias y funciones de distribución. - - - - Report a bug - Informar de un error - - - - Official website - Sitio web oficial - - - - AppMenuBar - - - &File - &Archivo - - - - &Load... - &Abrir… - - - - &Save - &Guardar - - - - Save &As... - Guardar &como… - - - - &Quit - &Salida - - - - &Edit - &Editar - - - - &Undo - &Cancelar - - - - &Redo - &Reiniciar - - - - &Copy plot - &Copiar el gráfico - - - - &Create - &Crear - - - - &Settings - &Ajustes - - - - Check for updates on startup - Comprobación de las actualizaciones al arrancar - - - - Reset redo stack automaticly - Restablecer la pila de rehacer automáticamente - - - - Enable LaTeX rendering - Activar el renderizado de LaTeX - - - - Expression editor - - - - - Automatically close parenthesises and brackets - - - - - Enable syntax highlighting - - - - - Enable autocompletion - - - - - Color Scheme - - - - - &Help - &Ayuda - - - - &Source code - &Código fuente - - - - &Report a bug - &Informar de un error - - - - &User manual - &Manual del usuario - - - - &Changelog - &Registro de cambios - - - - &Help translating! - &¡Ayuda a la traducción! - - - - &Thanks - &Agradecimientos - - - - &About - &Acerca de - - - - Save unsaved changes? - ¿Guardar los cambios no guardados? - - - - This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? - - - - - BaseDialog - - - Close - - - - - Changelog - - - Fetching changelog... - - - - - Done - - - - - CustomPropertyList - - - - + Create new %1 - - - - - Pick on graph - - - - - Dialog - - - Edit properties of %1 %2 - - - - - LogarithmPlotter - Invalid object name - - - - - An object with the name '%1' already exists. - - - - - Name - - - - - Label content - - - - - null - - - - - name - - - - - name + value - - - - - ExpressionEditor - - - Object Properties - - - - - Variables - - - - - Constants - - - - - Functions - - - - - Executable Objects - - - - - Objects - - - - - FileDialog - - - Export Logarithm Plot file - - - - - Import Logarithm Plot file - - - - - GreetScreen - - - Welcome to LogarithmPlotter - - - - - Version %1 - - - - - Take a few seconds to configure LogarithmPlotter. -These settings can be changed at any time from the "Settings" menu. - - - - - Check for updates on startup (requires online connectivity) - - - - - Reset redo stack when a new action is added to history - - - - - Enable LaTeX rendering - Activar el renderizado de LaTeX - - - - Automatically close parenthesises and brackets in expressions - - - - - Enable syntax highlighting for expressions - - - - - Enable autocompletion interface in expression editor - - - - - Color scheme: - - - - - User manual - - - - - Changelog - - - - - Done - - - - - HistoryBrowser - - - Filter... - - - - - Redo > - - - - - > Now - - - - - < Undo - - - - - ListSetting - - - + Add Entry - - - - - LogarithmPlotter - - - Objects - - - - - Settings - - - - - History - - - - - Saved plot to '%1'. - - - - - Loading file '%1'. - - - - - Unknown object type: %1. - - - - - Invalid file provided. - - - - - Could not save file: - - - - - Loaded file '%1'. - - - - - Copied plot screenshot to clipboard! - - - - - &Update - - - - - &Update LogarithmPlotter - - - - - ObjectCreationGrid - - - + Create new: - - - - - ObjectLists - - - Hide all %1 - - - - - Show all %1 - - - - - ObjectRow - - - Hide %1 %2 - - - - - Show %1 %2 - - - - - Set %1 %2 position - - - - - Delete %1 %2 - - - - - Pick new color for %1 %2 - - - - - PickLocationOverlay - - - Pointer precision: - - - - - Snap to grid: - - - - - Pick X - - - - - Pick Y - - - - - Open picker settings - - - - - Hide picker settings - - - - - (no pick selected) - - - - - Settings - - - X Zoom - - - - - Y Zoom - - - - - Min X - - - - - Max Y - - - - - Max X - - - - - Min Y - - - - - X Axis Step - - - - - Y Axis Step - - - - - Line width - - - - - Text size (px) - - - - - X Label - - - - - Y Label - - - - - X Log scale - - - - - Show X graduation - - - - - Show Y graduation - - - - - Copy to clipboard - - - - - Save plot - - - - - Save plot as - - - - - Load plot - - - - - ThanksTo - - - Thanks and Contributions - LogarithmPlotter - - - - - Source code - - - - - Original library by Raphael Graf - - - - - Source - - - - - Ported to Javascript by Matthew Crumley - - - - - - - - - Website - - - - - Ported to QMLJS by Ad5001 - - - - - Libraries included - - - - - Email - - - - - English - - - - - French - - - - - German - - - - - Hungarian - - - - - - Github - - - - - Norwegian - - - - - Translations included - - - - - Improve - - - - - changelog - - - Could not fetch changelog: Server error {}. - - - - - Could not fetch update: {}. - - - - - color - - - - %1 %2's color changed from %3 to %4. - - - - - comment - - - Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - - - - - The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) - - - - - Note: Specify the probability for each value. - - - - - Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... - - - - - If you have latex enabled, you can use use latex markup in between $$ to create equations. - - - - - control - - - - - - - %1: - - - - - create - - - - New %1 %2 created. - - - - - delete - - - - %1 %2 deleted. - - - - - editproperty - - - %1 of %2 %3 changed from "%4" to "%5". - - - - - %1 of %2 changed from %3 to %4. - - - - - error - - - - Cannot find property %1 of object %2. - - - - - Undefined variable %1. - - - - - In order to be executed, object %1 must have at least one argument. - - - - - %1 cannot be executed. - - - - - - - Invalid expression. - - - - - Invalid expression (parity). - - - - - Unknown character "%1". - - - - - - Illegal escape sequence: %1. - - - - - - Parse error [%1:%2]: %3 - - - - - Expected %1 - - - - - Unexpected %1 - - - - - Unexpected ".": member access is not permitted - - - - - Unexpected "[]": arrays are disabled. - - - - - Unexpected symbol: %1. - - - - - - Function %1 must have at least one argument. - - - - - - First argument to map is not a function. - - - - - - Second argument to map is not an array. - - - - - - First argument to fold is not a function. - - - - - - Second argument to fold is not an array. - - - - - - - First argument to filter is not a function. - - - - - - - Second argument to filter is not an array. - - - - - - Second argument to indexOf is not a string or array. - - - - - - Second argument to join is not an array. - - - - - EOF - - - - - No object found with names %1. - - - - - No object found with name %1. - - - - - Object cannot be dependent on itself. - - - - - Circular dependency detected. Object %1 depends on %2. - - - - - Circular dependency detected. Objects %1 depend on %2. - - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - - - - - Error while attempting to draw %1 %2: -%3 - -Undoing last change. - - - - - expression - - - - LogarithmPlotter - Parsing error - - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - - - - - LogarithmPlotter - Drawing error - - - - - function - - - Function - - - - - Functions - - - - - gainbode - - - Bode Magnitude - - - - - Bode Magnitudes - - - - - - low-pass - - - - - - high-pass - - - - - latex - - - No Latex installation found. -If you already have a latex distribution installed, make sure it's installed on your path. -Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. - - - - - DVIPNG was not found. Make sure you include it from your Latex distribution. - - - - - An exception occured within the creation of the latex formula. -Process '{}' ended with a non-zero return code {}: - -{} -Please make sure your latex installation is correct and report a bug if so. - - - - - An exception occured within the creation of the latex formula. -Process '{}' took too long to finish: -{} -Please make sure your latex installation is correct and report a bug if so. - - - - - name - - - - %1 %2 renamed to %3. - - - - - parameters - - - above - - - - - below - - - - - - left - - - - - - right - - - - - above-left - - - - - above-right - - - - - below-left - - - - - below-right - - - - - center - - - - - top - - - - - bottom - - - - - top-left - - - - - top-right - - - - - bottom-left - - - - - bottom-right - - - - - application - - - - - function - - - - - high - - - - - low - - - - - Next to target - - - - - With label - - - - - Hidden - - - - - phasebode - - - Bode Phase - - - - - Bode Phases - - - - - point - - - Point - - - - - Points - - - - - position - - - Position of %1 %2 set from "%3" to "%4". - - - - - Position of %1 set from %2 to %3. - - - - - prop - - - expression - - - - - definitionDomain - - - - - destinationDomain - - - - - - - - - - - - - - labelPosition - - - - - displayMode - - - - - - - - - - - labelX - - - - - - drawPoints - - - - - - drawDashedLines - - - - - - om_0 - - - - - pass - - - - - gain - - - - - omGraduation - - - - - phase - - - - - unit - - - - - - - x - - - - - - y - - - - - pointStyle - - - - - probabilities - - - - - text - - - - - disableLatex - - - - - targetElement - - - - - approximate - - - - - rounding - - - - - displayStyle - - - - - targetValuePosition - - - - - defaultExpression - - - - - baseValues - - - - - repartition - - - Repartition - - - - - Repartition functions - - - - - sequence - - - Sequence - - - - - Sequences - - - - - sommegainsbode - - - - Bode Magnitudes Sum - - - - - sommephasesbode - - - - Bode Phases Sum - - - - - text - - - Text - - - - - Texts - - - - - update - - - An update for LogarithPlotter (v{}) is available. - - - - - No update available. - - - - - Could not fetch update information: Server error {}. - - - - - Could not fetch update information: {}. - - - - - usage - - - - Usage: %1 - - - - - - - Usage: %1 or -%2 - - - - - integral(<from: number>, <to: number>, <f: ExecutableObject>) - - - - - integral(<from: number>, <to: number>, <f: string>, <variable: string>) - - - - - derivative(<f: ExecutableObject>, <x: number>) - - - - - derivative(<f: string>, <variable: string>, <x: number>) - - - - - visibility - - - - %1 %2 shown. - - - - - - %1 %2 hidden. - - - - - xcursor - - - X Cursor - - - - - X Cursors - - - - diff --git a/LogarithmPlotter/i18n/lp_fr.ts b/LogarithmPlotter/i18n/lp_fr.ts deleted file mode 100644 index aeb408f..0000000 --- a/LogarithmPlotter/i18n/lp_fr.ts +++ /dev/null @@ -1,1701 +0,0 @@ - - - - - About - - - - About LogarithmPlotter - À propos de LogarithmPlotter - - - - LogarithmPlotter v%1 - LogarithmPlotter v%1 - - - - 2D plotter software to make BODE plots, sequences and repartition functions. - Logiciel de traçage 2D pour les diagrammes de Bode, les suites et les fonctions de répartition. - - - - Report a bug - Rapport de bug - - - - Official website - Site officiel - - - - AppMenuBar - - - &File - &Fichier - - - - &Load... - &Ouvrir… - - - - &Save - &Sauvegarder - - - - Save &As... - Sauvegarde &Sous… - - - - &Quit - &Quitter - - - - &Edit - &Édition - - - - &Undo - &Annuler - - - - &Redo - &Rétablir - - - - &Copy plot - &Copier le graphe - - - - &Create - &Créer - - - - &Settings - &Paramètres - - - - Check for updates on startup - Vérifier la présence de mise à jour au démarrage - - - - Reset redo stack automaticly - Légèrement long, et pas forcément très compréhensible. - Réinitialiser la pile d'action "Rétablir" automatiquement - - - - Enable LaTeX rendering - Activer le rendu LaTeX - - - - Expression editor - Éditeur de formule - - - - Automatically close parenthesises and brackets - Fermer automatiquement les parenthèses et les crochets - - - - Enable syntax highlighting - Activer la coloration syntaxique - - - - Enable autocompletion - Activer l'autocomplétion - - - - Color Scheme - Coloration Syntaxique - - - - &Help - &Aide - - - - &Source code - &Code source - - - - &Report a bug - &Rapport de bug - - - - &User manual - Manuel d'&utilisation - - - - &Changelog - &Historique des modifications - - - - &Help translating! - &Aider à la traduction ! - - - - &Thanks - &Remerciements - - - - &About - &À propos - - - - Save unsaved changes? - Sauvegarder les modifications ? - - - - This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? - Ce graphe contient des modifications non sauvegardées. En faisant cela, toutes les données non sauvegardées seront perdues. Continuer ? - - - - BaseDialog - - - Close - Fermer - - - - Changelog - - - Fetching changelog... - Récupération de l'historique des modifications… - - - - Done - Fermer - - - - CustomPropertyList - - - - + Create new %1 - + Créer un nouvel objet %1 - - - - Pick on graph - Prendre la position sur le graphe - - - - Dialog - - - Edit properties of %1 %2 - Changer les propriétés de %1 %2 - - - - LogarithmPlotter - Invalid object name - LogarithmPlotter - Nom d'objet invalide - - - - An object with the name '%1' already exists. - Un objet portant le nom '%1' existe déjà. - - - - Name - Nom - - - - Label content - Étiquette - - - - null - vide - - - - name - nom - - - - name + value - nom + valeur - - - - EditorDialog - - Edit properties of %1 %2 - Changer les propriétés de %1 %2 - - - Name - Nom - - - Label content - Étiquette - - - null - vide - - - name - nom - - - name + value - nom + valeur - - - + Create new %1 - Traduction non litéralle pour éviter les problèmes de genre. - + Créer un nouvel objet %1 - - - - ExpressionEditor - - - Object Properties - Propriétés de l'objet - - - - Variables - Variables - - - - Constants - Constantes - - - - Functions - Fonctions - - - - Executable Objects - Objets fonction - - - - Objects - Objets - - - - FileDialog - - - Export Logarithm Plot file - Exporter le graphe Logarithmique - - - - Import Logarithm Plot file - Importer un graphe Logarithmique - - - - GreetScreen - - - Welcome to LogarithmPlotter - Bienvenue sur LogarithmPlotter - - - - Version %1 - Version %1 - - - - Take a few seconds to configure LogarithmPlotter. -These settings can be changed at any time from the "Settings" menu. - Prenez quelques secondes pour configurer LogarithmPlotter. -Ces paramètres peuvent être modifiés à tout moment à partir du menu "Paramètres". - - - - Enable LaTeX rendering - Activer le rendu LaTeX - - - - Automatically close parenthesises and brackets in expressions - Fermer automatiquement les parenthèses et les crochets dans les formules - - - - Enable syntax highlighting for expressions - Activer la coloration syntaxique des formules - - - - Enable autocompletion interface in expression editor - Activer l'interface d'autocomplétion dans l'éditeur de formules - - - - Color scheme: - Thème de coloration syntaxique : - - - - User manual - Manuel d'utilisation - - - - Changelog - Historique des modifications - - - - Done - Fermer - - - Take a few seconds to configure LogarithmPlotter. -These settings can always be changed at any time from the "Settings" menu. - Take a few seconds to configure LogarithmPlotter. -These settings can always be changed at any time from the "Settings" menu. - - - - Check for updates on startup (requires online connectivity) - Vérifier les mises à jour au démarrage (nécessite d'être connecté à internet) - - - - Reset redo stack when a new action is added to history - Réinitialiser la pile d'action "Rétablir" lorsqu'une nouvelle action est ajoutée à l'historique - - - - HistoryBrowser - - - Filter... - Filtrer… - - - - Redo > - Rétablir > - - - - > Now - > État actuel - - - - < Undo - < Annuler - - - - ListSetting - - - + Add Entry - + Nouvelle entrée - - - - LogarithmPlotter - - - Objects - Objets - - - - Settings - Paramètres - - - - History - Historique - - - - Saved plot to '%1'. - Graphe sauvegardé dans '%1'. - - - - Loading file '%1'. - Chargement du fichier '%1'. - - - - Unknown object type: %1. - Type d'objet inconnu : %1. - - - - Invalid file provided. - Fichier fourni invalide. - - - - Could not save file: - Impossible de sauvegarder le fichier : - - - - Loaded file '%1'. - Fichier '%1' chargé. - - - - Copied plot screenshot to clipboard! - Image du graphe copiée dans le presse-papiers ! - - - - &Update - &Mise à jour - - - - &Update LogarithmPlotter - &Mettre à jour LogarithmPlotter - - - - ObjectCreationGrid - - - + Create new: - + Créer : - - - - ObjectLists - - - Hide all %1 - Cacher tous les %1 - - - - Show all %1 - Montrer tous les %1 - - - Hide %1 %2 - Cacher l'objet %1 %2 - - - Show %1 %2 - Montrer l'objet %1 %2 - - - Set %1 %2 position - Définir la position de l'objet %1 %2 - - - Delete %1 %2 - Supprimer l'objet %1 %2 - - - Pick new color for %1 %2 - Choisissez une nouvelle couleur pour %1 %2 - - - - ObjectRow - - - Hide %1 %2 - Cacher l'objet %1 %2 - - - - Show %1 %2 - Montrer l'objet %1 %2 - - - - Set %1 %2 position - Définir la position de l'objet %1 %2 - - - - Delete %1 %2 - Supprimer l'objet %1 %2 - - - - Pick new color for %1 %2 - Choisissez une nouvelle couleur pour %1 %2 - - - - PickLocationOverlay - - - Pointer precision: - Précision du pointeur : - - - Snap to grid - Placement sur la grille - - - - Snap to grid: - Aligner sur la grille : - - - - Pick X - Prendre la position X - - - - Pick Y - Prendre la position Y - - - - Open picker settings - Ouvrir les paramètres du pointeur - - - - Hide picker settings - Cacher les paramètres du pointeur - - - - (no pick selected) - (aucun axe sélectionné) - - - - Settings - - - X Zoom - Zoom en X - - - - Y Zoom - Zoom en Y - - - - Min X - Min X - - - - Max Y - Max Y - - - - Max X - Max X - - - - Min Y - Min Y - - - - X Axis Step - Pas de l'axe X - - - - Y Axis Step - Pas de l'axe Y - - - - Line width - Taille des lignes - - - - Text size (px) - Taille du texte (px) - - - - X Label - Label de l'axe X - - - - Y Label - Label de l'axe Y - - - - X Log scale - Échelle logarithmique en X - - - - Show X graduation - Montrer la graduation de l'axe X - - - - Show Y graduation - Montrer la graduation de l'axe Y - - - - Copy to clipboard - Copier vers le presse-papiers - - - - Save plot - Sauvegarder le graphe - - - - Save plot as - Sauvegarder le graphe sous - - - - Load plot - Ouvrir un graphe - - - - ThanksTo - - - Thanks and Contributions - LogarithmPlotter - Remerciements et contributions - LogarithmPlotter - - - - Source code - Code source - - - - Original library by Raphael Graf - Bibliothèque originale de Raphael Graf - - - - Source - Source - - - - Ported to Javascript by Matthew Crumley - Porté en Javascript par Matthew Crumley - - - - - - - - Website - Site web - - - - Ported to QMLJS by Ad5001 - Porté à QMLJS par Ad5001 - - - - Libraries included - Bibliothèques incluses - - - - Email - Email - - - - English - Anglais - - - - French - Français - - - - German - Allemand - - - - Hungarian - Hongrois - - - - - Github - Github - - - - Norwegian - Norvégien - - - - Translations included - Traductions incluses - - - - Improve - Améliorer - - - - changelog - - - Could not fetch changelog: Server error {}. - Impossible de récupérer l'historique des modifications : Erreur de serveur {}. - - - - Could not fetch update: {}. - Impossible de récupérer l'historique des modifications : {}. - - - - color - - - - %1 %2's color changed from %3 to %4. - %1 %2 a été re colorisé du %3 au %4. - - - - comment - - - Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - Par exemple : R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - - - - The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) - Les paramètres suivants sont utilisés lorsque le domaine de définition est un ensemble non-continu. (Ex : ℕ, ℤ, des ensembles comme {0;3}…) - - - - Note: Specify the probability for each value. - Note : Spécifiez la probabilité pour chaque valeur. - - - - Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... - Note : Utilisez %1[n] pour faire référence à %1ₙ, %1[n+1] pour %1ₙ₊₁... - Note : Utilisez %1[n] pour faire référence à %1ₙ, %1[n+1] pour %1ₙ₊₁… - - - - If you have latex enabled, you can use use latex markup in between $$ to create equations. - Si vous avez activé le rendu latex, vous pouvez utiliser les balises latex entre $$ pour créer des équations. - - - - control - - - - - - - %1: - %1 : - - - - create - - - - New %1 %2 created. - Nouvel objet %1 %2 créé. - - - - delete - - - - %1 %2 deleted. - %1 %2 supprimé(e). - - - - editproperty - - - %1 of %2 %3 changed from "%4" to "%5". - %1 de %2 %3 modifiée de "%4" à "%5". - - - - %1 of %2 changed from %3 to %4. - %1 de %2 modifiée de %3 à %4. - - - - error - - - - Cannot find property %1 of object %2. - Impossible de trouver la propriété %1 de l'objet %2. - - - - Undefined variable %1. - La variable %1 n'est pas définie. - - - - In order to be executed, object %1 must have at least one argument. - Pour être utilisé comme fonction, l'objet %1 nécessite au moins un argument. - - - - %1 cannot be executed. - %1 n'est pas une fonction. - - - - - - Invalid expression. - Formule invalide. - - - - Invalid expression (parity). - Formule invalide (parité). - - - - Unknown character "%1". - Le caractère "%1" est inconnu. - - - - - Illegal escape sequence: %1. - Séquence d'échappement illégale : %1. - - - - - Parse error [%1:%2]: %3 - Erreur de syntaxe [%1:%2] : %3 - - - - Expected %1 - %1 attendu - - - - Unexpected %1 - %1 inattendu - - - Function definition is not permitted. - La définition de fonctions n'est pas autorisée. - - - Expected variable for assignment. - Une variable est attendue pour l'affectation. - - - - Unexpected ".": member access is not permitted - "." inattendu : l'accès aux propriétés n'est pas autorisé - - - - Unexpected "[]": arrays are disabled. - "[]" inattendu : les tableaux sont désactivés. - - - - Unexpected symbol: %1. - Symbole inconnu : %1. - - - - - Function %1 must have at least one argument. - La fonction %1 nécessite au moins un argument. - - - - - First argument to map is not a function. - Le premier argument de map n'est pas une fonction. - - - - - Second argument to map is not an array. - Le deuxième argument de map n'est pas un tableau. - - - - - First argument to fold is not a function. - Le premier argument de fold n'est pas une fonction. - - - - - Second argument to fold is not an array. - Le deuxième argument de fold n'est pas un tableau. - - - - - - First argument to filter is not a function. - Le premier argument de filter n'est pas une fonction. - - - - - - Second argument to filter is not an array. - Le deuxième argument de filter n'est pas un tableau. - - - - - Second argument to indexOf is not a string or array. - Le deuxième argument de indexOf n'est ni chaîne de caractères ni un tableau. - - - - - Second argument to join is not an array. - Le deuxième argument de join n'est pas un tableau. - - - - EOF - Fin de la formule - - - - No object found with names %1. - Aucun objet trouvé ayant pour noms %1. - - - - No object found with name %1. - Aucun objet avec le nom %1 n'a été trouvé. - - - - Object cannot be dependent on itself. - Un objet ne peut pas dépendre de lui-même. - - - - Circular dependency detected. Object %1 depends on %2. - Dépendance circulaire détectée. L'objet %1 dépend de %2. - - - - Circular dependency detected. Objects %1 depend on %2. - Dépendance circulaire détectée. Les objets %1 dépendent de %2. - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - Erreur lors de l'analyse de la formule pour la propriété %1 : -%2 - -Formule analysée : %3 - - - - Error while attempting to draw %1 %2: -%3 - -Undoing last change. - Erreur lors de la tentative de dessin du %1 %2 : -%3 - -La dernière modification a été annulée. - - - - expression - - - - LogarithmPlotter - Parsing error - LogarithmPlotter - Erreur de syntaxe - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - Erreur lors de l'analyse de la formule pour la propriété %1 : -%2 - -Formule analysée : %3 - - - - LogarithmPlotter - Drawing error - LogarithmPlotter - Erreur - - - - function - - - Function - Fonction - - - - Functions - Fonctions - - - - gainbode - - - Bode Magnitude - Gain de Bode - - - - Bode Magnitudes - Gains de Bode - - - - - low-pass - passe-bas - - - - - high-pass - passe-haut - - - - historylib - - New %1 %2 created. - Nouvel objet %1 %2 créé. - - - %1 %2 deleted. - %1 %2 supprimé(e). - - - %1 of %2 %3 changed from "%4" to "%5". - %1 de %2 %3 modifiée de "%4" à "%5". - - - %1 %2 shown. - %1 %2 affiché(e). - - - %1 %2 hidden. - %1 %2 cachée(e). - - - Name of %1 %2 changed to %3. - Le nom de %1 %2 a été changé en %3. - - - - latex - - - No Latex installation found. -If you already have a latex distribution installed, make sure it's installed on your path. -Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. - Aucune installation de LaTeX trouvée. -Si vous avez déjà installé une distribution LaTeX, assurez-vous qu'elle est installée sur votre PATH. -Sinon, vous pouvez télécharger une distribution LaTeX comme TeX Live à l'adresse https://tug.org/texlive/. - - - - DVIPNG was not found. Make sure you include it from your Latex distribution. - DVIPNG n'a pas été trouvé. Assurez-vous de l'inclure dans votre distribution LaTeX. - - - - An exception occured within the creation of the latex formula. -Process '{}' ended with a non-zero return code {}: - -{} -Please make sure your latex installation is correct and report a bug if so. - Une exception s'est produite lors de la création de la formule LaTeX. -Le processus '{}' s'est terminé par un code de retour non nul {} : - -{} -Vérifiez que votre installation de LaTeX est correcte et signalez un bogue si c'est le cas. - - - - An exception occured within the creation of the latex formula. -Process '{}' took too long to finish: -{} -Please make sure your latex installation is correct and report a bug if so. - Une exception s'est produite lors de la création de la formule LaTeX. -Le processus '{}' a mis trop de temps à se terminer : -{} -Vérifiez que votre installation de LaTeX est correcte et signalez un bogue si c'est le cas. - - - - name - - - - %1 %2 renamed to %3. - %1 %2 renommé(e) en %3. - - - - parameters - - - above - ↑ Au dessus - - - - below - ↓ En dessous - - - - - left - ← À gauche - - - - - right - → À droite - - - - above-left - ↖ Au dessus à gauche - - - - above-right - ↗ Au dessus à droite - - - - below-left - ↙ En dessous à gauche - - - - below-right - ↘ En dessous à droite - - - - center - >|< Centré - - - - top - ↑ Au dessus - - - - bottom - ↓ En dessous - - - - top-left - ↖ Au dessus à gauche - - - - top-right - ↗ Au dessus à droite - - - - bottom-left - ↙ En dessous à gauche - - - - bottom-right - ↘ En dessous à droite - - - - application - Application - - - - function - Fonction - - - - high - Haut - - - - low - Bas - - - - Next to target - A côté de la cible - - - - With label - Avec l'étiquette - - - - Hidden - Caché - - - - phasebode - - - Bode Phase - Phase de Bode - - - - Bode Phases - Phases de Bode - - - - point - - - Point - Point - - - - Points - Points - - - - position - - - Position of %1 %2 set from "%3" to "%4". - %1 %2 a été déplacé depuis "%3" vers "%4". - - - - Position of %1 set from %2 to %3. - %1 a été déplacé depuis %2 vers %3. - - - - prop - - - expression - Expression - - - - definitionDomain - Domaine de définition - - - - destinationDomain - Portée - - - - - - - - - - - - - labelPosition - Position de l'étiquette - - - - displayMode - Mode d'affichage - - - - - - - - - - labelX - Position en X de l'étiquette - - - - - drawPoints - Afficher les points - - - - - drawDashedLines - Afficher les pointillés - - - - - om_0 - ω₀ - - - - pass - Passe - - - - gain - Gain - - - - omGraduation - Afficher la graduation sur ω₀ - - - - phase - Phase - - - - unit - Unité de la phase - - - - - - x - X - - - - - y - Y - - - - pointStyle - Style du point - - - - probabilities - Liste de probabilités - - - - text - Contenu - - - - disableLatex - Désactiver le rendu LaTeX pour ce texte - - - - targetElement - Objet à cibler - - - - approximate - Afficher la valeur approximative - - - - rounding - Arrondi - - - - displayStyle - Style d'affichage - - - - targetValuePosition - Position de la valeur de la cible - - - - defaultExpression - Expression - - - - baseValues - Valeurs d'initialisation - - - color - Couleur - - - - repartition - - - Repartition - Répartition - - - - Repartition functions - Fonctions de répartition - - - - sequence - - - Sequence - Suite - - - - Sequences - Suites - - - - sommegainsbode - - - - Bode Magnitudes Sum - Sommes des gains de Bode - - - - sommephasesbode - - - - Bode Phases Sum - Somme des phases de Bode - - - - text - - - Text - Texte - - - - Texts - Textes - - - - update - - - An update for LogarithPlotter (v{}) is available. - Une mise à jour de LogarithmPlotter (v{}) est disponible. - - - - No update available. - À jour. - - - - Could not fetch update information: Server error {}. - Impossible de récupérer les informations de mise à jour. Erreur du serveur {}. - - - - Could not fetch update information: {}. - Impossible de récupérer les informations de mise à jour. {}. - - - - usage - - - - Usage: %1 - Emploi : %1 - - - - - - Usage: %1 or -%2 - Emploi : %1 ou -%2 - - - - integral(<from: number>, <to: number>, <f: ExecutableObject>) - integral(<de : nombre>, <à : nombre>, <f : Objet exécutable>) - - - - integral(<from: number>, <to: number>, <f: string>, <variable: string>) - integral(<de : nombre>, <à : nombre>, <f : fonction chaîne>, <variable>) - - - - derivative(<f: ExecutableObject>, <x: number>) - derivative(<f : Objet exécutable>, <x : nombre>) - - - - derivative(<f: string>, <variable: string>, <x: number>) - derivative(<f : fonction chaîne>, <variable>, <x : nombre>) - - - - visibility - - - - %1 %2 shown. - %1 %2 affiché(e). - - - - - %1 %2 hidden. - %1 %2 cachée(e). - - - - xcursor - - - X Cursor - Curseur X - - - - X Cursors - Curseurs X - - - diff --git a/LogarithmPlotter/i18n/lp_hu.ts b/LogarithmPlotter/i18n/lp_hu.ts deleted file mode 100644 index 89396ce..0000000 --- a/LogarithmPlotter/i18n/lp_hu.ts +++ /dev/null @@ -1,1684 +0,0 @@ - - - - - About - - - - About LogarithmPlotter - LogarithmPlotter névjegye - - - - LogarithmPlotter v%1 - LogarithmPlotter %1 verzió - - - - 2D plotter software to make BODE plots, sequences and repartition functions. - Síkbeli ábrázolásszoftver Bode-ábrák, sorozatok és eloszlási funkciók készítéséhez. - - - - Report a bug - Hiba bejelentése - - - - Official website - Hivatalos honlap - - - - AppMenuBar - - - &File - &Fájl - - - - &Load... - &Betöltés… - - - - &Save - &Mentés - - - - Save &As... - Me&ntés másként… - - - - &Quit - &Kilépés - - - - &Edit - S&zerkesztés - - - - &Undo - &Visszavonás - - - - &Redo - &Ismétlés - - - - &Copy plot - Ábra má&solása - - - - &Create - &Létrehozás - - - - &Settings - &Beállítások - - - - Check for updates on startup - Frissítések keresése indításkor - - - - Reset redo stack automaticly - Ismétlési verem önműködő visszaállítása - - - - Enable LaTeX rendering - LaTeX-megjelenítés engedélyezése - - - - Expression editor - Kifejezésszerkesztő - - - - Automatically close parenthesises and brackets - Zárójelek automatikus bezárása - - - - Enable syntax highlighting - Mondattani kiemelés engedélyezése - - - - Enable autocompletion - Automatikus befejezés engedélyezése - - - - Color Scheme - Színséma - - - - &Help - &Súgó - - - - &Source code - &Forráskód - - - - &Report a bug - &Hiba bejelentése - - - - &User manual - &Használati utasítás - - - - &Changelog - &Változásnapló - - - - &Help translating! - &Segítség a fordításban! - - - - &Thanks - &Köszönjük - - - - &About - &Névjegy - - - - Save unsaved changes? - Menti a változtatásokat? - - - - This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? - Ez az ábra nem mentett változtatásokat tartalmaz. Ezzel az összes nem mentett adat elveszik. Folytatja? - - - - BaseDialog - - - Close - Bezárás - - - - Changelog - - - Fetching changelog... - Változásnapló lekérése… - - - - Done - Kész - - - - CustomPropertyList - - - - + Create new %1 - + Új %1 létrehozása - - - - Pick on graph - Ábra kijelölése - - - - Dialog - - - Edit properties of %1 %2 - %1 %2 tulajdonságainak szerkesztése - - - - LogarithmPlotter - Invalid object name - LogarithmPlotter - Érvénytelen objektumnév - - - - An object with the name '%1' already exists. - A(z) „%1” nevű objektum már létezik. - - - - Name - Név - - - - Label content - Címketartalom - - - - null - üres - - - - name - név - - - - name + value - név + érték - - - - EditorDialog - - Edit properties of %1 %2 - %1 %2 tulajdonságainak szerkesztése - - - Name - Név - - - Label content - Címke tartalom - - - null - üres - - - name - név - - - name + value - név + érték - - - + Create new %1 - + Új %1 létrehozása - - - - ExpressionEditor - - - Object Properties - Objektumtulajdonságok - - - - Variables - Változók - - - - Constants - Állandók - - - - Functions - Függvények - - - - Executable Objects - Függvényobjektumok - - - - Objects - Objektumok - - - - FileDialog - - - Export Logarithm Plot file - Logaritmus-ábra-fájl exportálása - - - - Import Logarithm Plot file - Logaritmus-ábra-fájl importálása - - - - GreetScreen - - - Welcome to LogarithmPlotter - Isten hozott a LogarithmPlotter! - - - - Version %1 - %1 verzió - - - - Take a few seconds to configure LogarithmPlotter. -These settings can be changed at any time from the "Settings" menu. - Szánjon néhány másodpercet a LogarithmPlotter beállításához. -Ezek a beállítások bármikor módosíthatók a „Beállítások” menüben. - - - - Check for updates on startup (requires online connectivity) - Frissítések keresése indításkor (online kapcsolat szükséges) - - - - Reset redo stack when a new action is added to history - Ismétlési verem alaphelyzet visszaállítása, ha új műveletet adnak az előzményekhez - - - - Enable LaTeX rendering - LaTeX-megjelenítés engedélyezése - - - - Automatically close parenthesises and brackets in expressions - Zárójelek automatikus bezárása a kifejezésekben - - - - Enable syntax highlighting for expressions - Mondattani kiemelés engedélyezése a kifejezésekhez - - - - Enable autocompletion interface in expression editor - Automatikus befejezési felület engedélyezése a kifejezésszerkesztőben - - - - Color scheme: - Színséma: - - - - User manual - Használati utasítás - - - - Changelog - Változásnapló - - - - Done - Kész - - - - HistoryBrowser - - - Filter... - Szűrő… - - - - Redo > - Ismétlés > - - - - > Now - > Most - - - - < Undo - < Visszavonás - - - - ListSetting - - - + Add Entry - + Bejegyzés hozzáadása - - - - LogarithmPlotter - - - Objects - Tárgyak - - - - Settings - Beállítások - - - - History - Előzmények - - - - Saved plot to '%1'. - Ábra mentve ide: „%1”. - - - - Loading file '%1'. - A(z) „%1” fájl betöltése folyamatban van. - - - - Unknown object type: %1. - Ismeretlen objektumtípus: %1. - - - - Invalid file provided. - A megadott fájl érvénytelen. - - - - Could not save file: - A fájl mentése nem sikerült: - - - - Loaded file '%1'. - A(z) „%1” fájl betöltve. - - - - Copied plot screenshot to clipboard! - Ábra képernyőkép vágólapra másolva! - - - - &Update - &Frissítés - - - - &Update LogarithmPlotter - A LogarithmPlotter &frissítése - - - - ObjectCreationGrid - - - + Create new: - + Új létrehozása: - - - - ObjectLists - - - Hide all %1 - Összes %1 elrejtése - - - - Show all %1 - Összes %1 megjelenítése - - - Hide %1 %2 - %1 %2 elrejtése - - - Show %1 %2 - %1 %2 megjelenítése - - - Set %1 %2 position - %1 %2 helye beállítása - - - Delete %1 %2 - %1 %2 törlése - - - Pick new color for %1 %2 - Válasszon új színt a következőhöz: %1 %2 - - - - ObjectRow - - - Hide %1 %2 - %1 %2 elrejtése - - - - Show %1 %2 - %1 %2 megjelenítése - - - - Set %1 %2 position - %1 %2 helye beállítása - - - - Delete %1 %2 - %1 %2 törlése - - - - Pick new color for %1 %2 - Válasszon új színt a következőhöz: %1 %2 - - - - PickLocationOverlay - - - Pointer precision: - Mutató pontossága: - - - Snap to grid - Rácshoz illesztés - - - - Snap to grid: - Rácshoz igazítás: - - - - Pick X - X kijelölése - - - - Pick Y - Y kijelölése - - - - Open picker settings - Kijelölési beállítások megnyitása - - - - Hide picker settings - Kijelölési beállítások elrejtése - - - - (no pick selected) - (nincs kijelölés kiválasztva) - - - - Settings - - - X Zoom - X-nagyítás - - - - Y Zoom - Y-nagyítás - - - - Min X - Legkisebb X - - - - Max Y - Legnagyobb Y - - - - Max X - Legnagyobb X - - - - Min Y - Legkisebb Y - - - - X Axis Step - X tengely lépésköze - - - - Y Axis Step - Y tengely lépésköze - - - - Line width - Vonalvastagság - - - - Text size (px) - Szövegméret (képpont) - - - - X Label - X címke - - - - Y Label - Y címke - - - - X Log scale - X tengely logaritmikus skálával - - - - Show X graduation - X érettségi megjelenítése - - - - Show Y graduation - Y érettségi megjelenítése - - - - Copy to clipboard - Másolás a vágólapra - - - - Save plot - Ábra mentése - - - - Save plot as - Ábra mentése másként - - - - Load plot - Ábra betöltése - - - - ThanksTo - - - Thanks and Contributions - LogarithmPlotter - Köszönet és hozzájárulás - LogarithmPlotter - - - - Source code - Forráskód - - - - Original library by Raphael Graf - Eredeti könyvtár: Graf Raphael - - - - Source - Forrás - - - - Ported to Javascript by Matthew Crumley - JavaScript-átalakítás: Crumley Máté - - - - - - - - Website - Honlap - - - - Ported to QMLJS by Ad5001 - QMLJS-átalakítás: Ad5001 - - - - Libraries included - Tartalmazott könyvtárak - - - - Email - E-mail - - - - English - angol - - - - French - francia - - - - German - német - - - - Hungarian - magyar - - - - - Github - GitHub - - - - Norwegian - norvég - - - - Translations included - A felhasználói felület nyelvei - - - - Improve - Fejlesztés - - - - changelog - - - Could not fetch changelog: Server error {}. - Nem sikerült lekérni a változásnaplót: Kiszolgálóhiba: {}. - - - - Could not fetch update: {}. - Nem sikerült lekérni a változásnaplót: {}. - - - - color - - - - %1 %2's color changed from %3 to %4. - %1 %2 színe %3-ról %4-re változott. - - - - comment - - - Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - Példák: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - - - - The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) - A következő paraméterek használatosak, ha a tartomány nem folytonos halmaz. (Példák: ℕ, ℤ, olyan halmazok, mint a {0;3}…) - - - - Note: Specify the probability for each value. - Megjegyzés: Adja meg az egyes értékek valószínűségét. - - - - Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... - Megjegyzés: A(z) %1[n] használatával hivatkozhat erre: %1ₙ, a(z) %1[n+1] használatával hivatkozhat erre: %1ₙ₊₁, … - - - - If you have latex enabled, you can use use latex markup in between $$ to create equations. - Ha a LaTeX engedélyezve van, a LaTeX-jelölés használható egyenletek létrehozására $$ között. - - - - control - - - - - - - %1: - %1: - - - - create - - - - New %1 %2 created. - Új %1 %2 létrehozva. - - - - delete - - - - %1 %2 deleted. - %1 %2 törölve. - - - - editproperty - - - %1 of %2 %3 changed from "%4" to "%5". - %1/%2 %3 megváltozott. Régi érték: %4, új érték: %5. - - - - %1 of %2 changed from %3 to %4. - %1/%2 megváltozott. Régi érték: %3, új érték: %4. - - - - error - - - - Cannot find property %1 of object %2. - A(z) %2 objektum %1 tulajdonsága nem található. - - - - Undefined variable %1. - A(z) %1 változó nincs meghatározva. - - - - In order to be executed, object %1 must have at least one argument. - A végrehajtáshoz a(z) %1 objektumnak legalább egy argumentummal kell rendelkeznie. - - - - %1 cannot be executed. - A(z) %1 nem függvény. - - - - - - Invalid expression. - Érvénytelen kifejezés. - - - - Invalid expression (parity). - Érvénytelen kifejezés (paritás). - - - - Unknown character "%1". - Ismeretlen karakter „%1”. - - - - - Illegal escape sequence: %1. - Érvénytelen kilépési sorozat: %1. - - - - - Parse error [%1:%2]: %3 - Elemzési hiba [%1:%2]: %3 - - - - Expected %1 - Várható %1 - - - - Unexpected %1 - Váratlan %1 - - - Function definition is not permitted. - A függvény meghatározása nem engedélyezett. - - - Expected variable for assignment. - A hozzárendeléshez várt változó. - - - - Unexpected ".": member access is not permitted - Váratlan „.”: a tagok hozzáférése nem engedélyezett - - - - Unexpected "[]": arrays are disabled. - Váratlan „[]”: a tömbök le vannak tiltva. - - - - Unexpected symbol: %1. - Váratlan szimbólum: %1. - - - - - Function %1 must have at least one argument. - A(z) %1 függvénynek legalább egy argumentumnak kell lennie. - - - - - First argument to map is not a function. - Az első leképezési argumentum nem függvény. - - - - - Second argument to map is not an array. - A második leképezési argumentum nem tömb. - - - - - First argument to fold is not a function. - Az első behajtási argumentum nem függvény. - - - - - Second argument to fold is not an array. - A második behajtási argumentum nem tömb. - - - - - - First argument to filter is not a function. - Az első szűrési argumentum nem függvény. - - - - - - Second argument to filter is not an array. - A második szűrési argumentum nem tömb. - - - - - Second argument to indexOf is not a string or array. - Az indexOf második argumentuma nem karakterlánc vagy tömb. - - - - - Second argument to join is not an array. - A második csatlakozási argumentum nem tömb. - - - - EOF - Kifejezés vége - - - - No object found with names %1. - A(z) %1 nevű objektum nem található. - - - - No object found with name %1. - A(z) %1 nevű objektum nem található. - - - - Object cannot be dependent on itself. - Az objektum nem függhet önmagától. - - - - Circular dependency detected. Object %1 depends on %2. - Körkörös függőség észlelve. A(z) %1-objektum a(z) %2-objektumtól függ. - - - - Circular dependency detected. Objects %1 depend on %2. - Körkörös függőség észlelve. A(z) %1-objektumok a(z) %2-objektumtól függenek. - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - Hiba a(z) %1 tulajdonság kifejezésének elemzésekor: -%2 - -Kiértékelt kifejezés: %3 - - - - Error while attempting to draw %1 %2: -%3 - -Undoing last change. - Hiba történt a(z) %1 %2 rajzolása közben: -%3 - -Az utolsó módosítás visszavonása. - - - - expression - - - - LogarithmPlotter - Parsing error - LogarithmPlotter - Elemzési hiba - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - Hiba a(z) %1 tulajdonság kifejezésének elemzésekor: -%2 - -Kiértékelt kifejezés: %3 - - - - LogarithmPlotter - Drawing error - LogarithmPlotter - Rajzolási hiba - - - - function - - - Function - Függvény - - - - Functions - Függvények - - - - gainbode - - - Bode Magnitude - Bode-nagyságrend - - - - Bode Magnitudes - Bode-nagyságrendek - - - - - low-pass - aluláteresztő - - - - - high-pass - felüláteresztő - - - - historylib - - New %1 %2 created. - Új %1 %2 létrehozva. - - - %1 %2 deleted. - %1 %2 törölve. - - - %1 of %2 %3 changed from "%4" to "%5". - %1/%2 %3 megváltozott. Régi érték: %4, új érték: %5. - - - %1 %2 shown. - %1 %2 megjelenítve. - - - %1 %2 hidden. - %1 %2 rejtve. - - - - latex - - - No Latex installation found. -If you already have a latex distribution installed, make sure it's installed on your path. -Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. - Nem található LaTeX telepítés. -Ha már telepítve van egy LaTeX disztribúció, győződjön meg arról, hogy az telepítve van az elérési útján. -Egyébként letölthet egy LaTeX disztribúciót, például a TeX Live-t a https://tug.org/texlive/ címről. - - - - DVIPNG was not found. Make sure you include it from your Latex distribution. - DVIPNG nem található. Ügyeljen arra, hogy a LaTeX disztribúciójából tartalmazza. - - - - An exception occured within the creation of the latex formula. -Process '{}' ended with a non-zero return code {}: - -{} -Please make sure your latex installation is correct and report a bug if so. - Kivétel történt a LaTeX-képlet létrehozása során. -A(z) „{}” folyamat nullától eltérő visszatérési kóddal ({}) végződött: - -{} -Kérjük, ellenőrizze, hogy a LaTeX telepítése helyes-e, és ha igen, jelentse a hibát. - - - - An exception occured within the creation of the latex formula. -Process '{}' took too long to finish: -{} -Please make sure your latex installation is correct and report a bug if so. - Kivétel történt a LaTeX-képlet létrehozása során. -A(z) „{}” folyamat túl sokáig tartott a befejezéshez: -{} -Kérjük, ellenőrizze, hogy a LaTeX telepítése helyes-e, és ha igen, jelentse a hibát. - - - - name - - - - %1 %2 renamed to %3. - %1 %2 átnevezve erre: %3. - - - - parameters - - - above - ↑ Felett - - - - below - ↓ Alatt - - - - - left - ← Balra - - - - - right - → Jobbra - - - - above-left - ↖ Felett, balra - - - - above-right - ↗ Felett, jobbra - - - - below-left - ↙ Alatt, balra - - - - below-right - ↘ Alatt, jobbra - - - - center - >|< Középre - - - - top - ↑ Felső - - - - bottom - ↓ Alsó - - - - top-left - ↖ Bal felső - - - - top-right - ↗ Jobb felső - - - - bottom-left - ↙ Bal alsó - - - - bottom-right - ↘ Jobb alsó - - - - application - Alkalmazás - - - - function - Függvény - - - - high - Magas - - - - low - Alul - - - - Next to target - Cél mellé - - - - With label - Címkével - - - - Hidden - Rejtett - - - - phasebode - - - Bode Phase - Bode-fázis - - - - Bode Phases - Bode-fázisok - - - - point - - - Point - Pont - - - - Points - Pontok - - - - position - - - Position of %1 %2 set from "%3" to "%4". - %1 %2 áthelyezve innen: „%3” ide: „%4”. - - - - Position of %1 set from %2 to %3. - %1 áthelyezve innen: %2 ide: %3. - - - - prop - - - expression - Kifejezés - - - - definitionDomain - Abszcissza tartomány - - - - destinationDomain - Ordináta tartomány - - - - - - - - - - - - - labelPosition - Címke helyzete - - - - displayMode - Megjelenítési mód - - - - - - - - - - labelX - Címke X helyzete - - - - - drawPoints - Pontok megjelenítése - - - - - drawDashedLines - Szaggatott vonalak megjelenítése - - - - - om_0 - ω₀ - - - - pass - Áteresztő - - - - gain - Nagyságrend nyeresége - - - - omGraduation - ω₀ érettségi megjelenítése - - - - phase - Fázis - - - - unit - Egység használata - - - - - - x - X - - - - - y - Y - - - - pointStyle - Pontstílus - - - - probabilities - Valószínűségek listája - - - - text - Tartalom - - - - disableLatex - LaTeX-megjelenítés letiltása ennél a szövegnél - - - - targetElement - Tárgycél - - - - approximate - Hozzávetőleges érték megjelenítése - - - - rounding - Kerekítés - - - - displayStyle - Megjelenítési stílus - - - - targetValuePosition - Cél értékpozíciója - - - - defaultExpression - Alapértelmezett kifejezés - - - - baseValues - Kezdeményezési értékek - - - - repartition - - - Repartition - Elosztás - - - - Repartition functions - Elosztási függvények - - - - sequence - - - Sequence - Sorozat - - - - Sequences - Sorozatok - - - - sommegainsbode - - - - Bode Magnitudes Sum - Bode-nagyságrendek összege - - - - sommephasesbode - - - - Bode Phases Sum - Bode-fázisok összege - - - - text - - - Text - Szöveg - - - - Texts - Szövegek - - - - update - - - An update for LogarithPlotter (v{}) is available. - Elérhető a Logaritmus-ábrázoló ({} verzió) frissítése. - - - - No update available. - Nincs telepíthető frissítés. - - - - Could not fetch update information: Server error {}. - Nem sikerült lekérni a frissítési adatokat: Kiszolgálóhiba: {}. - - - - Could not fetch update information: {}. - Nem sikerült lekérni a frissítési adatokat: {}. - - - - usage - - - - Usage: %1 - Használat: %1 - - - - - - Usage: %1 or -%2 - Használat: %1 vagy -%2 - - - - integral(<from: number>, <to: number>, <f: ExecutableObject>) - integrál(<alsó korlát: szám>, <felső korlát: szám>, <függvény: végrehajtható objektum>) - - - - integral(<from: number>, <to: number>, <f: string>, <variable: string>) - integrál(<alsó korlát: szám>, <felső korlát: szám>, <függvény: karakterlánc>, <változó: karakterlánc>) - - - - derivative(<f: ExecutableObject>, <x: number>) - derivált(<függvény: VégrehajthatóObjektum>, <x: szám>) - - - - derivative(<f: string>, <variable: string>, <x: number>) - derivált(<függvény: karakterlánc>, <változó: karakterlánc>, <x: szám>) - - - - visibility - - - - %1 %2 shown. - %1 %2 megjelenítve. - - - - - %1 %2 hidden. - %1 %2 rejtve. - - - - xcursor - - - X Cursor - X kurzor - - - - X Cursors - X kurzorok - - - diff --git a/LogarithmPlotter/i18n/lp_nb_NO.ts b/LogarithmPlotter/i18n/lp_nb_NO.ts deleted file mode 100644 index 18d0f09..0000000 --- a/LogarithmPlotter/i18n/lp_nb_NO.ts +++ /dev/null @@ -1,1657 +0,0 @@ - - - - - About - - - - About LogarithmPlotter - Om - - - - LogarithmPlotter v%1 - LogarithmPlotter v%1 - - - - 2D plotter software to make BODE plots, sequences and repartition functions. - 2D-plotterprogramvare laget for opprettelse av Bode-diagram, sekvenser, og distribusjonsfunksjoner. - - - - Report a bug - Rapporter en feil - - - - Official website - - - - - AppMenuBar - - - &File - &Fil - - - - &Load... - &Last inn … - - - - &Save - &Lagre - - - - Save &As... - Lagre &som … - - - - &Quit - &Avslutt - - - - &Edit - &Rediger - - - - &Undo - &Angre - - - - &Redo - &Gjenta - - - - &Copy plot - &Kopier plott - - - - &Create - &Opprett - - - - &Settings - &Innstillinger - - - - Check for updates on startup - Se etter nye versjoner ved programstart - - - - Reset redo stack automaticly - Tilbakestill angrehistorikk automatisk - - - - Enable LaTeX rendering - - - - - Expression editor - - - - - Automatically close parenthesises and brackets - - - - - Enable syntax highlighting - - - - - Enable autocompletion - - - - - Color Scheme - - - - - &Help - &Hjelp - - - - &Source code - - - - - &Report a bug - - - - - &User manual - - - - - &Changelog - - - - - &Help translating! - - - - - &Thanks - - - - - &About - &Om - - - - Save unsaved changes? - - - - - This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? - - - - - BaseDialog - - - Close - - - - - Changelog - - - Fetching changelog... - - - - - Done - - - - - CustomPropertyList - - - - + Create new %1 - + Opprett nytt %1 - - - - Pick on graph - - - - - Dialog - - - Edit properties of %1 %2 - Rediger egenskaper for %1 %2 - - - - LogarithmPlotter - Invalid object name - - - - - An object with the name '%1' already exists. - - - - - Name - Navn - - - - Label content - Etikett-innhold - - - - null - NULL - - - - name - navn - - - - name + value - navn + veri - - - - EditorDialog - - Edit properties of %1 %2 - Rediger egenskaper for %1 %2 - - - Name - Navn - - - Label content - Etikett-innhold - - - null - NULL - - - name - navn - - - name + value - navn + veri - - - + Create new %1 - + Opprett nytt %1 - - - - ExpressionEditor - - - Object Properties - - - - - Variables - - - - - Constants - - - - - Functions - Funksjoner - - - - Executable Objects - - - - - Objects - Objekter - - - - FileDialog - - - Export Logarithm Plot file - Eksporter logaritmeplott-fil - - - - Import Logarithm Plot file - Importer logaritmeplott-fil - - - - GreetScreen - - - Welcome to LogarithmPlotter - Velkommen til LogarithmPlotter - - - - Version %1 - Versjon %1 - - - - Take a few seconds to configure LogarithmPlotter. -These settings can be changed at any time from the "Settings" menu. - Sett opp LogarithmPlotter. -Disse innstillingene kan endres når som helst fra «Innstillinger»-menyen. - - - - Check for updates on startup (requires online connectivity) - Se etter nye versjoner ved programstart. (Krever tilkobling til Internett.) - - - - Reset redo stack when a new action is added to history - Tilbakesitll angrehistorikk når en ny handling legges til - - - - Enable LaTeX rendering - - - - - Automatically close parenthesises and brackets in expressions - - - - - Enable syntax highlighting for expressions - - - - - Enable autocompletion interface in expression editor - - - - - Color scheme: - - - - - User manual - - - - - Changelog - - - - - Done - - - - - HistoryBrowser - - - Filter... - - - - - Redo > - Angre > - - - - > Now - > Nå - - - - < Undo - < Angre - - - - ListSetting - - - + Add Entry - - - - - LogarithmPlotter - - - Objects - Objekter - - - - Settings - Innstillinger - - - - History - Historikk - - - - Saved plot to '%1'. - Lagret plott i «%1». - - - - Loading file '%1'. - Laster inn «%1»-fil. - - - - Unknown object type: %1. - Ukjent objekttype: %1. - - - - Invalid file provided. - Ugyldig fil angitt. - - - - Could not save file: - Kunne ikke lagre fil: - - - - Loaded file '%1'. - Lastet inn filen «%1». - - - - Copied plot screenshot to clipboard! - Kopierte plott-skjermavbildning til utklippstavlen! - - - - &Update - &Oppdater - - - - &Update LogarithmPlotter - &Installer ny versjon av LogartimePlotter - - - - ObjectCreationGrid - - - + Create new: - + Opprett ny: - - - - ObjectLists - - - Hide all %1 - Skjul alle %1 - - - - Show all %1 - Vis alle %1 - - - Hide %1 %2 - Skjul %1 %2 - - - Show %1 %2 - Vis %1 %2 - - - Set %1 %2 position - Sett %1 %2 posisjon - - - Delete %1 %2 - Slett %1 %2 - - - Pick new color for %1 %2 - Velg ny farge for %1 %2 - - - - ObjectRow - - - Hide %1 %2 - Skjul %1 %2 - - - - Show %1 %2 - Vis %1 %2 - - - - Set %1 %2 position - Sett %1 %2 posisjon - - - - Delete %1 %2 - Slett %1 %2 - - - - Pick new color for %1 %2 - Velg ny farge for %1 %2 - - - - PickLocationOverlay - - - Pointer precision: - Peker-presisjon: - - - Snap to grid - Fest til rutenett - - - - Snap to grid: - - - - - Pick X - - - - - Pick Y - - - - - Open picker settings - - - - - Hide picker settings - - - - - (no pick selected) - - - - - Settings - - - X Zoom - X-forstørrelse - - - - Y Zoom - Y-forstørrelse - - - - Min X - Min. X - - - - Max Y - Maks. Y - - - - Max X - Maks. X - - - - Min Y - Min. Y - - - - X Axis Step - X-aksesteg - - - - Y Axis Step - Y-aksesteg - - - - Line width - Linjebredde - - - - Text size (px) - Tekststørrelse (piksler) - - - - X Label - Navn på X-akse - - - - Y Label - Navn på Y-akse - - - - X Log scale - Logaritmisk skala i x - - - - Show X graduation - Vis X-inndeling - - - - Show Y graduation - Vis Y-inndeling - - - - Copy to clipboard - Kopier til utklippstavle - - - - Save plot - Lagre plott - - - - Save plot as - Lagre plott som - - - - Load plot - Last inn plott - - - - ThanksTo - - - Thanks and Contributions - LogarithmPlotter - - - - - Source code - - - - - Original library by Raphael Graf - - - - - Source - - - - - Ported to Javascript by Matthew Crumley - - - - - - - - - Website - - - - - Ported to QMLJS by Ad5001 - - - - - Libraries included - - - - - Email - - - - - English - - - - - French - - - - - German - - - - - Hungarian - - - - - - Github - - - - - Norwegian - - - - - Translations included - - - - - Improve - - - - - changelog - - - Could not fetch changelog: Server error {}. - - - - - Could not fetch update: {}. - - - - - color - - - - %1 %2's color changed from %3 to %4. - - - - - comment - - - Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - - - - - The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) - - - - - Note: Specify the probability for each value. - - - - - Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... - - - - - If you have latex enabled, you can use use latex markup in between $$ to create equations. - - - - - control - - - - - - - %1: - - - - - create - - - - New %1 %2 created. - Ny %1 %2 opprettet. - - - - delete - - - - %1 %2 deleted. - %1 %2 slettet. - - - - editproperty - - - %1 of %2 %3 changed from "%4" to "%5". - %1 av %2 %3 endret fra «%4» til «%5». - - - - %1 of %2 changed from %3 to %4. - - - - - error - - - - Cannot find property %1 of object %2. - - - - - Undefined variable %1. - - - - - In order to be executed, object %1 must have at least one argument. - - - - - %1 cannot be executed. - - - - - - - Invalid expression. - - - - - Invalid expression (parity). - - - - - Unknown character "%1". - - - - - - Illegal escape sequence: %1. - - - - - - Parse error [%1:%2]: %3 - - - - - Expected %1 - - - - - Unexpected %1 - - - - - Unexpected ".": member access is not permitted - - - - - Unexpected "[]": arrays are disabled. - - - - - Unexpected symbol: %1. - - - - - - Function %1 must have at least one argument. - - - - - - First argument to map is not a function. - - - - - - Second argument to map is not an array. - - - - - - First argument to fold is not a function. - - - - - - Second argument to fold is not an array. - - - - - - - First argument to filter is not a function. - - - - - - - Second argument to filter is not an array. - - - - - - Second argument to indexOf is not a string or array. - - - - - - Second argument to join is not an array. - - - - - EOF - - - - - No object found with names %1. - - - - - No object found with name %1. - - - - - Object cannot be dependent on itself. - - - - - Circular dependency detected. Object %1 depends on %2. - - - - - Circular dependency detected. Objects %1 depend on %2. - - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - - - - - Error while attempting to draw %1 %2: -%3 - -Undoing last change. - - - - - expression - - - - LogarithmPlotter - Parsing error - - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - - - - - LogarithmPlotter - Drawing error - - - - - function - - - Function - Funksjon - - - - Functions - Funksjoner - - - - gainbode - - - Bode Magnitude - Bode-magnitude - - - - Bode Magnitudes - Bode-magnituder - - - - - low-pass - lavpass - - - - - high-pass - høypass - - - - historylib - - New %1 %2 created. - Ny %1 %2 opprettet. - - - %1 %2 deleted. - %1 %2 slettet. - - - %1 of %2 %3 changed from "%4" to "%5". - %1 av %2 %3 endret fra «%4» til «%5». - - - %1 %2 shown. - %1 %2 vist. - - - %1 %2 hidden. - %1 %2 skjult. - - - - latex - - - No Latex installation found. -If you already have a latex distribution installed, make sure it's installed on your path. -Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. - - - - - DVIPNG was not found. Make sure you include it from your Latex distribution. - - - - - An exception occured within the creation of the latex formula. -Process '{}' ended with a non-zero return code {}: - -{} -Please make sure your latex installation is correct and report a bug if so. - - - - - An exception occured within the creation of the latex formula. -Process '{}' took too long to finish: -{} -Please make sure your latex installation is correct and report a bug if so. - - - - - name - - - - %1 %2 renamed to %3. - - - - - parameters - - - above - - - - - below - - - - - - left - - - - - - right - - - - - above-left - - - - - above-right - - - - - below-left - - - - - below-right - - - - - center - - - - - top - - - - - bottom - - - - - top-left - - - - - top-right - - - - - bottom-left - - - - - bottom-right - - - - - application - - - - - function - - - - - high - - - - - low - - - - - Next to target - - - - - With label - - - - - Hidden - - - - - phasebode - - - Bode Phase - Bode-fase - - - - Bode Phases - Bode-faser - - - - point - - - Point - Punkt - - - - Points - Punkter - - - - position - - - Position of %1 %2 set from "%3" to "%4". - - - - - Position of %1 set from %2 to %3. - - - - - prop - - - expression - - - - - definitionDomain - - - - - destinationDomain - - - - - - - - - - - - - - labelPosition - - - - - displayMode - - - - - - - - - - - labelX - - - - - - drawPoints - - - - - - drawDashedLines - - - - - - om_0 - - - - - pass - - - - - gain - - - - - omGraduation - - - - - phase - - - - - unit - - - - - - - x - - - - - - y - - - - - pointStyle - - - - - probabilities - - - - - text - - - - - disableLatex - - - - - targetElement - - - - - approximate - - - - - rounding - - - - - displayStyle - - - - - targetValuePosition - - - - - defaultExpression - - - - - baseValues - - - - - repartition - - - Repartition - Distribusjon - - - - Repartition functions - Distribusjonsfunksjoner - - - - sequence - - - Sequence - Følge - - - - Sequences - Følger - - - - sommegainsbode - - - - Bode Magnitudes Sum - Bode-magnitudesum - - - - sommephasesbode - - - - Bode Phases Sum - Bode-fasesum - - - - text - - - Text - Tekst - - - - Texts - Tekster - - - - update - - - An update for LogarithPlotter (v{}) is available. - En ny versjon av LogartimePlotter (v{}) er tilgjengelig - - - - No update available. - Ingen nye versjoner. - - - - Could not fetch update information: Server error {}. - Fant ikke ut om det er noen nye versjoner. Tjenerfeil {}. - - - - Could not fetch update information: {}. - Kunne ikke hente info om hvorvidt det er nye versjoner: {}. - - - - usage - - - - Usage: %1 - - - - - - - Usage: %1 or -%2 - - - - - integral(<from: number>, <to: number>, <f: ExecutableObject>) - - - - - integral(<from: number>, <to: number>, <f: string>, <variable: string>) - - - - - derivative(<f: ExecutableObject>, <x: number>) - - - - - derivative(<f: string>, <variable: string>, <x: number>) - - - - - visibility - - - - %1 %2 shown. - %1 %2 vist. - - - - - %1 %2 hidden. - %1 %2 skjult. - - - - xcursor - - - X Cursor - X-peker - - - - X Cursors - X-pekere - - - diff --git a/LogarithmPlotter/i18n/lp_template.ts b/LogarithmPlotter/i18n/lp_template.ts deleted file mode 100644 index 2cbbff2..0000000 --- a/LogarithmPlotter/i18n/lp_template.ts +++ /dev/null @@ -1,1578 +0,0 @@ - - - - - About - - - - About LogarithmPlotter - - - - - LogarithmPlotter v%1 - - - - - 2D plotter software to make BODE plots, sequences and repartition functions. - - - - - Report a bug - - - - - Official website - - - - - AppMenuBar - - - &File - - - - - &Load... - - - - - &Save - - - - - Save &As... - - - - - &Quit - - - - - &Edit - - - - - &Undo - - - - - &Redo - - - - - &Copy plot - - - - - &Create - - - - - &Settings - - - - - Check for updates on startup - - - - - Reset redo stack automaticly - - - - - Enable LaTeX rendering - - - - - Expression editor - - - - - Automatically close parenthesises and brackets - - - - - Enable syntax highlighting - - - - - Enable autocompletion - - - - - Color Scheme - - - - - &Help - - - - - &Source code - - - - - &Report a bug - - - - - &User manual - - - - - &Changelog - - - - - &Help translating! - - - - - &Thanks - - - - - &About - - - - - Save unsaved changes? - - - - - This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? - - - - - BaseDialog - - - Close - - - - - Changelog - - - Fetching changelog... - - - - - Done - - - - - CustomPropertyList - - - - + Create new %1 - - - - - Pick on graph - - - - - Dialog - - - Edit properties of %1 %2 - - - - - LogarithmPlotter - Invalid object name - - - - - An object with the name '%1' already exists. - - - - - Name - - - - - Label content - - - - - null - - - - - name - - - - - name + value - - - - - ExpressionEditor - - - Object Properties - - - - - Variables - - - - - Constants - - - - - Functions - - - - - Executable Objects - - - - - Objects - - - - - FileDialog - - - Export Logarithm Plot file - - - - - Import Logarithm Plot file - - - - - GreetScreen - - - Welcome to LogarithmPlotter - - - - - Version %1 - - - - - Take a few seconds to configure LogarithmPlotter. -These settings can be changed at any time from the "Settings" menu. - - - - - Check for updates on startup (requires online connectivity) - - - - - Reset redo stack when a new action is added to history - - - - - Enable LaTeX rendering - - - - - Automatically close parenthesises and brackets in expressions - - - - - Enable syntax highlighting for expressions - - - - - Enable autocompletion interface in expression editor - - - - - Color scheme: - - - - - User manual - - - - - Changelog - - - - - Done - - - - - HistoryBrowser - - - Filter... - - - - - Redo > - - - - - > Now - - - - - < Undo - - - - - ListSetting - - - + Add Entry - - - - - LogarithmPlotter - - - Objects - - - - - Settings - - - - - History - - - - - Saved plot to '%1'. - - - - - Loading file '%1'. - - - - - Unknown object type: %1. - - - - - Invalid file provided. - - - - - Could not save file: - - - - - Loaded file '%1'. - - - - - Copied plot screenshot to clipboard! - - - - - &Update - - - - - &Update LogarithmPlotter - - - - - ObjectCreationGrid - - - + Create new: - - - - - ObjectLists - - - Hide all %1 - - - - - Show all %1 - - - - - ObjectRow - - - Hide %1 %2 - - - - - Show %1 %2 - - - - - Set %1 %2 position - - - - - Delete %1 %2 - - - - - Pick new color for %1 %2 - - - - - PickLocationOverlay - - - Pointer precision: - - - - - Snap to grid: - - - - - Pick X - - - - - Pick Y - - - - - Open picker settings - - - - - Hide picker settings - - - - - (no pick selected) - - - - - Settings - - - X Zoom - - - - - Y Zoom - - - - - Min X - - - - - Max Y - - - - - Max X - - - - - Min Y - - - - - X Axis Step - - - - - Y Axis Step - - - - - Line width - - - - - Text size (px) - - - - - X Label - - - - - Y Label - - - - - X Log scale - - - - - Show X graduation - - - - - Show Y graduation - - - - - Copy to clipboard - - - - - Save plot - - - - - Save plot as - - - - - Load plot - - - - - ThanksTo - - - Thanks and Contributions - LogarithmPlotter - - - - - Source code - - - - - Original library by Raphael Graf - - - - - Source - - - - - Ported to Javascript by Matthew Crumley - - - - - - - - - Website - - - - - Ported to QMLJS by Ad5001 - - - - - Libraries included - - - - - Email - - - - - English - - - - - French - - - - - German - - - - - Hungarian - - - - - - Github - - - - - Norwegian - - - - - Translations included - - - - - Improve - - - - - changelog - - - Could not fetch changelog: Server error {}. - - - - - Could not fetch update: {}. - - - - - color - - - - %1 %2's color changed from %3 to %4. - - - - - comment - - - Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} - - - - - The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) - - - - - Note: Specify the probability for each value. - - - - - Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... - - - - - If you have latex enabled, you can use use latex markup in between $$ to create equations. - - - - - control - - - - - - - %1: - - - - - create - - - - New %1 %2 created. - - - - - delete - - - - %1 %2 deleted. - - - - - editproperty - - - %1 of %2 %3 changed from "%4" to "%5". - - - - - %1 of %2 changed from %3 to %4. - - - - - error - - - - Cannot find property %1 of object %2. - - - - - Undefined variable %1. - - - - - In order to be executed, object %1 must have at least one argument. - - - - - %1 cannot be executed. - - - - - - - Invalid expression. - - - - - Invalid expression (parity). - - - - - Unknown character "%1". - - - - - - Illegal escape sequence: %1. - - - - - - Parse error [%1:%2]: %3 - - - - - Expected %1 - - - - - Unexpected %1 - - - - - Unexpected ".": member access is not permitted - - - - - Unexpected "[]": arrays are disabled. - - - - - Unexpected symbol: %1. - - - - - - Function %1 must have at least one argument. - - - - - - First argument to map is not a function. - - - - - - Second argument to map is not an array. - - - - - - First argument to fold is not a function. - - - - - - Second argument to fold is not an array. - - - - - - - First argument to filter is not a function. - - - - - - - Second argument to filter is not an array. - - - - - - Second argument to indexOf is not a string or array. - - - - - - Second argument to join is not an array. - - - - - EOF - - - - - No object found with names %1. - - - - - No object found with name %1. - - - - - Object cannot be dependent on itself. - - - - - Circular dependency detected. Object %1 depends on %2. - - - - - Circular dependency detected. Objects %1 depend on %2. - - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - - - - - Error while attempting to draw %1 %2: -%3 - -Undoing last change. - - - - - expression - - - - LogarithmPlotter - Parsing error - - - - - Error while parsing expression for property %1: -%2 - -Evaluated expression: %3 - - - - - LogarithmPlotter - Drawing error - - - - - function - - - Function - - - - - Functions - - - - - gainbode - - - Bode Magnitude - - - - - Bode Magnitudes - - - - - - low-pass - - - - - - high-pass - - - - - latex - - - No Latex installation found. -If you already have a latex distribution installed, make sure it's installed on your path. -Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. - - - - - DVIPNG was not found. Make sure you include it from your Latex distribution. - - - - - An exception occured within the creation of the latex formula. -Process '{}' ended with a non-zero return code {}: - -{} -Please make sure your latex installation is correct and report a bug if so. - - - - - An exception occured within the creation of the latex formula. -Process '{}' took too long to finish: -{} -Please make sure your latex installation is correct and report a bug if so. - - - - - name - - - - %1 %2 renamed to %3. - - - - - parameters - - - above - - - - - below - - - - - - left - - - - - - right - - - - - above-left - - - - - above-right - - - - - below-left - - - - - below-right - - - - - center - - - - - top - - - - - bottom - - - - - top-left - - - - - top-right - - - - - bottom-left - - - - - bottom-right - - - - - application - - - - - function - - - - - high - - - - - low - - - - - Next to target - - - - - With label - - - - - Hidden - - - - - phasebode - - - Bode Phase - - - - - Bode Phases - - - - - point - - - Point - - - - - Points - - - - - position - - - Position of %1 %2 set from "%3" to "%4". - - - - - Position of %1 set from %2 to %3. - - - - - prop - - - expression - - - - - definitionDomain - - - - - destinationDomain - - - - - - - - - - - - - - labelPosition - - - - - displayMode - - - - - - - - - - - labelX - - - - - - drawPoints - - - - - - drawDashedLines - - - - - - om_0 - - - - - pass - - - - - gain - - - - - omGraduation - - - - - phase - - - - - unit - - - - - - - x - - - - - - y - - - - - pointStyle - - - - - probabilities - - - - - text - - - - - disableLatex - - - - - targetElement - - - - - approximate - - - - - rounding - - - - - displayStyle - - - - - targetValuePosition - - - - - defaultExpression - - - - - baseValues - - - - - repartition - - - Repartition - - - - - Repartition functions - - - - - sequence - - - Sequence - - - - - Sequences - - - - - sommegainsbode - - - - Bode Magnitudes Sum - - - - - sommephasesbode - - - - Bode Phases Sum - - - - - text - - - Text - - - - - Texts - - - - - update - - - An update for LogarithPlotter (v{}) is available. - - - - - No update available. - - - - - Could not fetch update information: Server error {}. - - - - - Could not fetch update information: {}. - - - - - usage - - - - Usage: %1 - - - - - - - Usage: %1 or -%2 - - - - - integral(<from: number>, <to: number>, <f: ExecutableObject>) - - - - - integral(<from: number>, <to: number>, <f: string>, <variable: string>) - - - - - derivative(<f: ExecutableObject>, <x: number>) - - - - - derivative(<f: string>, <variable: string>, <x: number>) - - - - - visibility - - - - %1 %2 shown. - - - - - - %1 %2 hidden. - - - - - xcursor - - - X Cursor - - - - - X Cursors - - - - diff --git a/LogarithmPlotter/i18n/release.sh b/LogarithmPlotter/i18n/release.sh deleted file mode 100755 index 93475b5..0000000 --- a/LogarithmPlotter/i18n/release.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -lrelease *.ts diff --git a/LogarithmPlotter/i18n/update.sh b/LogarithmPlotter/i18n/update.sh deleted file mode 100755 index aa48415..0000000 --- a/LogarithmPlotter/i18n/update.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts diff --git a/LogarithmPlotter/logarithmplotter.py b/LogarithmPlotter/logarithmplotter.py deleted file mode 100644 index f6f2cb3..0000000 --- a/LogarithmPlotter/logarithmplotter.py +++ /dev/null @@ -1,155 +0,0 @@ -""" - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -""" - -from time import time - -from PySide6.QtWidgets import QApplication -from PySide6.QtQml import QQmlApplicationEngine -from PySide6.QtCore import Qt, QTranslator, QLocale -from PySide6.QtGui import QIcon - -from tempfile import TemporaryDirectory -from os import getcwd, chdir, environ, path, remove, close -from platform import release as os_release -from sys import platform, argv, version as sys_version, exit -from sys import path as sys_path - -start_time = time() - -# Create the temporary directory for saving copied screenshots and latex files -tempdir = TemporaryDirectory() -tmpfile = path.join(tempdir.name, 'graph.png') -pwd = getcwd() - -chdir(path.dirname(path.realpath(__file__))) - -if path.realpath(path.join(getcwd(), "..")) not in sys_path: - sys_path.append(path.realpath(path.join(getcwd(), ".."))) - - -from LogarithmPlotter import __VERSION__ -from LogarithmPlotter.util import config, native -from LogarithmPlotter.util.update import check_for_updates -from LogarithmPlotter.util.helper import Helper -from LogarithmPlotter.util.latex import Latex - -config.init() - -def get_linux_theme(): - des = { - "KDE": "Fusion", - "gnome": "Basic", - "lxqt": "Fusion", - "mate": "Fusion", - } - if "XDG_SESSION_DESKTOP" in environ: - return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "Fusion" - else: - # Android - return "Material" - -def run(): - - if not 'QT_QUICK_CONTROLS_STYLE' in environ: - environ["QT_QUICK_CONTROLS_STYLE"] = { - "linux": get_linux_theme(), - "freebsd": get_linux_theme(), - "win32": "Universal" if os_release == "10" else "Fusion", - "cygwin": "Fusion", - "darwin": "macOS" - }[platform] - - dep_time = time() - print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.") - - icon_fallbacks = QIcon.fallbackSearchPaths(); - base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons") - icon_fallbacks.append(path.realpath(path.join(base_icon_path, "common"))) - icon_fallbacks.append(path.realpath(path.join(base_icon_path, "objects"))) - icon_fallbacks.append(path.realpath(path.join(base_icon_path, "history"))) - icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings"))) - icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom"))) - QIcon.setFallbackSearchPaths(icon_fallbacks); - - QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) - app = QApplication(argv) - app.setApplicationName("LogarithmPlotter") - app.setOrganizationName("Ad5001") - app.styleHints().setShowShortcutsInContextMenus(True) - app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg")))) - - # Installing translators - translator = QTranslator() - # Check if lang is forced. - forcedlang = [p for p in argv if p[:7]=="--lang="] - locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale() - if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))): - app.installTranslator(translator); - - # Installing macOS file handler. - macOSFileOpenHandler = None - if platform == "darwin": - macOSFileOpenHandler = native.MacOSFileOpenHandler() - app.installEventFilter(macOSFileOpenHandler) - - engine = QQmlApplicationEngine() - global tmpfile - helper = Helper(pwd, tmpfile) - latex = Latex(tempdir) - engine.rootContext().setContextProperty("Helper", helper) - engine.rootContext().setContextProperty("Latex", latex) - engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv) - engine.rootContext().setContextProperty("StartTime", dep_time) - - app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file. - - engine.addImportPath(path.realpath(path.join(getcwd(), "qml"))) - engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml"))) - - - if not engine.rootObjects(): - print("No root object", path.realpath(path.join(getcwd(), "qml"))) - print(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml"))) - exit(-1) - - # Open the current diagram - chdir(pwd) - if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']: - engine.rootObjects()[0].loadDiagram(argv[-1]) - chdir(path.dirname(path.realpath(__file__))) - - if platform == "darwin": - macOSFileOpenHandler.init_graphics(engine.rootObjects()[0]) - - # Check for LaTeX installation if LaTeX support is enabled - if config.getSetting("enable_latex"): - latex.check_latex_install() - - # Check for updates - if config.getSetting("check_for_updates"): - check_for_updates(__VERSION__, engine.rootObjects()[0]) - - exit_code = app.exec() - - tempdir.cleanup() - config.save() - exit(exit_code) - -if __name__ == "__main__": - run() - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/History.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/History.qml deleted file mode 100644 index df9503d..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/History.qml +++ /dev/null @@ -1,221 +0,0 @@ -/** - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick -import QtQml -import QtQuick.Window -import "../js/objects.js" as Objects -import "../js/historylib.js" as HistoryLib -import "../js/history/common.js" as HistoryCommon - -/*! - \qmltype History - \inqmlmodule eu.ad5001.LogarithmPlotter.History - \brief QObject holding persistantly for undo & redo stacks. - - \sa HistoryBrowser, historylib -*/ -Item { - // Using a QtObject is necessary in order to have proper property propagation in QML - id: historyObj - - /*! - \qmlproperty int History::undoCount - Count of undo actions. - */ - property int undoCount: 0 - /*! - \qmlproperty int History::redoCount - Count of redo actions. - */ - property int redoCount: 0 - /*! - \qmlproperty var History::undoStack - Stack of undo actions. - */ - property var undoStack: [] - /*! - \qmlproperty var History::redoStack - Stack of redo actions. - */ - property var redoStack: [] - /*! - \qmlproperty bool History::saved - true when no modification was done to the current working file, false otherwise. - */ - property bool saved: true - - - /*! - \qmlmethod void History::clear() - Clears both undo and redo stacks completly. - */ - function clear() { - undoCount = 0 - redoCount = 0 - undoStack = [] - redoStack = [] - } - - - /*! - \qmlmethod var History::serialize() - Serializes history into JSON-able content. - */ - function serialize() { - let undoSt = [], redoSt = []; - for(let i = 0; i < undoCount; i++) - undoSt.push([ - undoStack[i].type(), - undoStack[i].export() - ]); - for(let i = 0; i < redoCount; i++) - redoSt.push([ - redoStack[i].type(), - redoStack[i].export() - ]); - return [undoSt, redoSt] - } - - /*! - \qmlmethod void History::unserialize(var undoSt, var redoSt) - Unserializes both \c undoSt stack and \c redoSt stack from serialized content. - */ - function unserialize(undoSt, redoSt) { - clear(); - for(let i = 0; i < undoSt.length; i++) - undoStack.push(new HistoryLib.Actions[undoSt[i][0]](...undoSt[i][1])) - for(let i = 0; i < redoSt.length; i++) - redoStack.push(new HistoryLib.Actions[redoSt[i][0]](...redoSt[i][1])) - undoCount = undoSt.length; - redoCount = redoSt.length; - objectLists.update() - } - - /*! - \qmlmethod void History::addToHistory(var action) - Adds an instance of historylib.Action to history. - */ - function addToHistory(action) { - if(action instanceof HistoryLib.Action) { - console.log("Added new entry to history: " + action.getReadableString()) - undoStack.push(action) - undoCount++; - if(Helper.getSettingBool("reset_redo_stack")) { - redoStack = [] - redoCount = 0 - } - saved = false - } - } - - /*! - \qmlmethod void History::undo(bool updateObjectList = true) - Undoes the historylib.Action at the top of the undo stack and pushes it to the top of the redo stack. - By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false. - */ - function undo(updateObjectList = true) { - if(undoStack.length > 0) { - var action = undoStack.pop() - action.undo() - if(updateObjectList) - objectLists.update() - redoStack.push(action) - undoCount--; - redoCount++; - saved = false - } - } - - /*! - \qmlmethod void History::redo(bool updateObjectList = true) - Redoes the historylib.Action at the top of the redo stack and pushes it to the top of the undo stack. - By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false. - */ - function redo(updateObjectList = true) { - if(redoStack.length > 0) { - var action = redoStack.pop() - action.redo() - if(updateObjectList) - objectLists.update() - undoStack.push(action) - undoCount++; - redoCount--; - saved = false - } - } - - /*! - \qmlmethod void History::undoMultipleDefered(int toUndoCount) - Undoes several historylib.Action at the top of the undo stack and pushes them to the top of the redo stack. - It undoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes. - */ - function undoMultipleDefered(toUndoCount) { - undoTimer.toUndoCount = toUndoCount; - undoTimer.start() - if(toUndoCount > 0) - saved = false - } - - - /*! - \qmlmethod void History::redoMultipleDefered(int toRedoCount) - Redoes several historylib.Action at the top of the redo stack and pushes them to the top of the undo stack. - It redoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes. - */ - function redoMultipleDefered(toRedoCount) { - redoTimer.toRedoCount = toRedoCount; - redoTimer.start() - if(toRedoCount > 0) - saved = false - } - - Timer { - id: undoTimer - interval: 5; running: false; repeat: true - property int toUndoCount: 0 - onTriggered: { - if(toUndoCount > 0) { - historyObj.undo(toUndoCount % 4 == 1) // Only redraw once every 4 changes. - toUndoCount--; - } else { - running = false; - } - } - } - - Timer { - id: redoTimer - interval: 5; running: false; repeat: true - property int toRedoCount: 0 - onTriggered: { - if(toRedoCount > 0) { - historyObj.redo(toRedoCount % 4 == 1) // Only redraw once every 4 changes. - toRedoCount--; - } else { - running = false; - } - } - } - - Component.onCompleted: { - HistoryLib.history = historyObj - HistoryCommon.themeTextColor = sysPalette.windowText - HistoryCommon.imageDepth = Screen.devicePixelRatio - } -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/qmldir b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/qmldir deleted file mode 100644 index 4f011a5..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/qmldir +++ /dev/null @@ -1,5 +0,0 @@ -module eu.ad5001.LogarithmPlotter.History - -History 1.0 History.qml -HistoryBrowser 1.0 HistoryBrowser.qml -HistoryItem 1.0 HistoryItem.qml diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml deleted file mode 100644 index 64bef06..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml +++ /dev/null @@ -1,493 +0,0 @@ -/** - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick -import Qt.labs.platform as Native -import "js/objects.js" as Objects -import "js/utils.js" as Utils -import "js/mathlib.js" as MathLib - -/*! - \qmltype LogGraphCanvas - \inqmlmodule eu.ad5001.LogarithmPlotter - \brief Canvas used to display the diagram. - - Provides a customized canvas with several helper methods to be used by objects. - - \sa LogarithmPlotter, PickLocationOverlay -*/ -Canvas { - id: canvas - anchors.top: separator.bottom - anchors.left: parent.left - height: parent.height - 90 - width: parent.width - - /*! - \qmlproperty double LogGraphCanvas::xmin - Minimum x of the diagram, provided from settings. - \sa Settings - */ - property double xmin: 0 - /*! - \qmlproperty double LogGraphCanvas::ymax - Maximum y of the diagram, provided from settings. - \sa Settings - */ - property double ymax: 0 - /*! - \qmlproperty double LogGraphCanvas::xzoom - Zoom on the x axis of the diagram, provided from settings. - \sa Settings - */ - property double xzoom: 10 - /*! - \qmlproperty double LogGraphCanvas::yzoom - Zoom on the y axis of the diagram, provided from settings. - \sa Settings - */ - property double yzoom: 10 - /*! - \qmlproperty string LogGraphCanvas::xaxisstep - Step of the x axis graduation, provided from settings. - \note: Only available in non-logarithmic mode. - \sa Settings - */ - property string xaxisstep: "4" - /*! - \qmlproperty string LogGraphCanvas::yaxisstep - Step of the y axis graduation, provided from settings. - \sa Settings - */ - property string yaxisstep: "4" - /*! - \qmlproperty string LogGraphCanvas::xlabel - Label used on the x axis, provided from settings. - \sa Settings - */ - property string xlabel: "" - /*! - \qmlproperty string LogGraphCanvas::ylabel - Label used on the y axis, provided from settings. - \sa Settings - */ - property string ylabel: "" - /*! - \qmlproperty double LogGraphCanvas::linewidth - Width of lines that will be drawn into the canvas, provided from settings. - \sa Settings - */ - property double linewidth: 1 - /*! - \qmlproperty double LogGraphCanvas::textsize - Font size of the text that will be drawn into the canvas, provided from settings. - \sa Settings - */ - property double textsize: 14 - /*! - \qmlproperty bool LogGraphCanvas::logscalex - true if the canvas should be in logarithmic mode, false otherwise. - Provided from settings. - \sa Settings - */ - property bool logscalex: false - /*! - \qmlproperty bool LogGraphCanvas::showxgrad - true if the x graduation should be shown, false otherwise. - Provided from settings. - \sa Settings - */ - property bool showxgrad: false - /*! - \qmlproperty bool LogGraphCanvas::showygrad - true if the y graduation should be shown, false otherwise. - Provided from settings. - \sa Settings - */ - property bool showygrad: false - - /*! - \qmlproperty int LogGraphCanvas::maxgradx - Max power of the logarithmic scaled on the x axis in logarithmic mode. - */ - property int maxgradx: 20 - - /*! - \qmlproperty var LogGraphCanvas::yaxisstepExpr - Expression for the y axis step (used to create labels). - */ - property var yaxisstepExpr: (new MathLib.Expression(`x*(${yaxisstep})`)) - /*! - \qmlproperty double LogGraphCanvas::yaxisstep1 - Value of the for the y axis step. - */ - property double yaxisstep1: yaxisstepExpr.execute(1) - /*! - \qmlproperty int LogGraphCanvas::drawMaxY - Minimum value of y that should be drawn onto the canvas. - */ - property int drawMaxY: Math.ceil(Math.max(Math.abs(ymax), Math.abs(px2y(canvasSize.height)))/yaxisstep1) - /*! - \qmlproperty var LogGraphCanvas::xaxisstepExpr - Expression for the x axis step (used to create labels). - */ - property var xaxisstepExpr: (new MathLib.Expression(`x*(${xaxisstep})`)) - /*! - \qmlproperty double LogGraphCanvas::xaxisstep1 - Value of the for the x axis step. - */ - property double xaxisstep1: xaxisstepExpr.execute(1) - /*! - \qmlproperty int LogGraphCanvas::drawMaxX - Maximum value of x that should be drawn onto the canvas. - */ - property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1) - - /*! - \qmlproperty var LogGraphCanvas::imageLoaders - Dictionary of format {image: [callback.image data]} containing data for defered image loading. - */ - property var imageLoaders: {} - /*! - \qmlproperty var LogGraphCanvas::ctx - Cache for the 2D context so that it may be used asynchronously. - */ - property var ctx - - Component.onCompleted: imageLoaders = {} - - Native.MessageDialog { - id: drawingErrorDialog - title: qsTranslate("expression", "LogarithmPlotter - Drawing error") - text: "" - function showDialog(objType, objName, error) { - text = qsTranslate("error", "Error while attempting to draw %1 %2:\n%3\n\nUndoing last change.").arg(objType).arg(objName).arg(error) - open() - } - } - - onPaint: function(rect) { - //console.log('Redrawing') - if(rect.width == canvas.width) { // Redraw full canvas - ctx = getContext("2d"); - reset(ctx) - drawGrille(ctx) - drawAxises(ctx) - drawLabels(ctx) - ctx.lineWidth = linewidth - for(var objType in Objects.currentObjects) { - for(var obj of Objects.currentObjects[objType]){ - ctx.strokeStyle = obj.color - ctx.fillStyle = obj.color - if(obj.visible) - try { - obj.draw(canvas, ctx) - } catch(e) { - // Drawing throws an error. Generally, it's due to a new modification (or the opening of a file) - drawingErrorDialog.showDialog(objType, obj.name, e.message) - history.undo() - } - } - } - ctx.lineWidth = 1 - } - } - - onImageLoaded: { - Object.keys(imageLoaders).forEach((key) => { - if(isImageLoaded(key)) { - // Calling callback - imageLoaders[key][0](canvas, ctx, imageLoaders[key][1]) - delete imageLoaders[key] - } - }) - } - - /*! - \qmlmethod void LogGraphCanvas::reset(var ctx) - Resets the canvas to a blank one with default setting using 2D \c ctx. - */ - function reset(ctx){ - // Reset - ctx.fillStyle = "#FFFFFF" - ctx.strokeStyle = "#000000" - ctx.font = `${canvas.textsize}px sans-serif` - ctx.fillRect(0,0,width,height) - } - - // Drawing the log based graph - - /*! - \qmlmethod void LogGraphCanvas::drawGrille(var ctx) - Draws the grid using 2D \c ctx. - */ - function drawGrille(ctx) { - ctx.strokeStyle = "#C0C0C0" - if(logscalex) { - for(var xpow = -maxgradx; xpow <= maxgradx; xpow++) { - for(var xmulti = 1; xmulti < 10; xmulti++) { - drawXLine(ctx, Math.pow(10, xpow)*xmulti) - } - } - } else { - for(var x = 0; x < drawMaxX; x+=1) { - drawXLine(ctx, x*xaxisstep1) - drawXLine(ctx, -x*xaxisstep1) - } - } - for(var y = 0; y < drawMaxY; y+=1) { - drawYLine(ctx, y*yaxisstep1) - drawYLine(ctx, -y*yaxisstep1) - } - } - - /*! - \qmlmethod void LogGraphCanvas::drawAxises(var ctx) - Draws the graph axises using 2D \c ctx. - */ - function drawAxises(ctx) { - ctx.strokeStyle = "#000000" - var axisypos = logscalex ? 1 : 0 - drawXLine(ctx, axisypos) - drawYLine(ctx, 0) - var axisypx = x2px(axisypos) // X coordinate of Y axis - var axisxpx = y2px(0) // Y coordinate of X axis - // Drawing arrows - drawLine(ctx, axisypx, 0, axisypx-10, 10) - drawLine(ctx, axisypx, 0, axisypx+10, 10) - drawLine(ctx, canvasSize.width, axisxpx, canvasSize.width-10, axisxpx-10) - drawLine(ctx, canvasSize.width, axisxpx, canvasSize.width-10, axisxpx+10) - } - - /*! - \qmlmethod void LogGraphCanvas::drawLabels(var ctx) - Draws all labels (graduation & axises labels) using 2D \c ctx. - */ - function drawLabels(ctx) { - var axisypx = x2px(logscalex ? 1 : 0) // X coordinate of Y axis - var axisxpx = y2px(0) // Y coordinate of X axis - // Labels - ctx.fillStyle = "#000000" - ctx.font = `${canvas.textsize}px sans-serif` - ctx.fillText(ylabel, axisypx+10, 24) - var textSize = ctx.measureText(xlabel).width - ctx.fillText(xlabel, canvasSize.width-14-textSize, axisxpx-5) - // Axis graduation labels - ctx.font = `${canvas.textsize-4}px sans-serif` - - var txtMinus = ctx.measureText('-').width - if(showxgrad) { - if(logscalex) { - for(var xpow = -maxgradx; xpow <= maxgradx; xpow+=1) { - var textSize = ctx.measureText("10"+Utils.textsup(xpow)).width - if(xpow != 0) - drawVisibleText(ctx, "10"+Utils.textsup(xpow), x2px(Math.pow(10,xpow))-textSize/2, axisxpx+16+(6*(xpow==1))) - } - } else { - for(var x = 1; x < drawMaxX; x += 1) { - var drawX = x*xaxisstep1 - var txtX = xaxisstepExpr.simplify(x).replace(/^\((.+)\)$/, '$1') - var textSize = measureText(ctx, txtX, 6).height - drawVisibleText(ctx, txtX, x2px(drawX)-4, axisxpx+textsize/2+textSize) - drawVisibleText(ctx, '-'+txtX, x2px(-drawX)-4, axisxpx+textsize/2+textSize) - } - } - } - if(showygrad) { - for(var y = 0; y < drawMaxY; y += 1) { - var drawY = y*yaxisstep1 - var txtY = yaxisstepExpr.simplify(y).replace(/^\((.+)\)$/, '$1') - var textSize = ctx.measureText(txtY).width - drawVisibleText(ctx, txtY, axisypx-6-textSize, y2px(drawY)+4+(10*(y==0))) - if(y != 0) - drawVisibleText(ctx, '-'+txtY, axisypx-6-textSize-txtMinus, y2px(-drawY)+4) - } - } - ctx.fillStyle = "#FFFFFF" - } - - /*! - \qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x) - Draws an horizontal line at \c x plot coordinate using 2D \c ctx. - */ - function drawXLine(ctx, x) { - if(isVisible(x, ymax)) { - drawLine(ctx, x2px(x), 0, x2px(x), canvasSize.height) - } - } - - /*! - \qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x) - Draws an vertical line at \c y plot coordinate using 2D \c ctx. - */ - function drawYLine(ctx, y) { - if(isVisible(xmin, y)) { - drawLine(ctx, 0, y2px(y), canvasSize.width, y2px(y)) - } - } - - /*! - \qmlmethod void LogGraphCanvas::drawVisibleText(var ctx, string text, double x, double y) - Writes multline \c text onto the canvas using 2D \c ctx. - \note The \c x and \c y properties here are relative to the canvas, not the plot. - */ - function drawVisibleText(ctx, text, x, y) { - if(x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height) { - text.toString().split("\n").forEach(function(txt, i){ - ctx.fillText(txt, x, y+(canvas.textsize*i)) - }) - } - } - - /*! - \qmlmethod void LogGraphCanvas::drawVisibleImage(var ctx, var image, double x, double y) - Draws an \c image onto the canvas using 2D \c ctx. - \note The \c x, \c y \c width and \c height properties here are relative to the canvas, not the plot. - */ - function drawVisibleImage(ctx, image, x, y, width, height) { - //console.log("Drawing image", isImageLoaded(image), isImageError(image)) - markDirty(Qt.rect(x, y, width, height)); - ctx.drawImage(image, x, y, width, height) - /*if(true || (x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height)) { - }*/ - } - - /*! - \qmlmethod var LogGraphCanvas::measureText(var ctx, string text) - Measures the wicth and height of a multiline \c text that would be drawn onto the canvas using 2D \c ctx. - Return format: dictionary {"width": width, "height": height} - */ - function measureText(ctx, text) { - let theight = 0 - let twidth = 0 - let defaultHeight = ctx.measureText("M").width // Approximate but good enough! - text.split("\n").forEach(function(txt, i){ - theight += defaultHeight - if(ctx.measureText(txt).width > twidth) twidth = ctx.measureText(txt).width - }) - return {'width': twidth, 'height': theight} - } - - /*! - \qmlmethod double LogGraphCanvas::x2px(double x) - Converts an \c x coordinate to it's relative position on the canvas. - It supports both logarithmic and non logarithmic scale depending on the currently selected mode. - */ - function x2px(x) { - if(logscalex) { - var logxmin = Math.log(xmin) - return (Math.log(x)-logxmin)*xzoom - } else return (x - xmin)*xzoom - } - - /*! - \qmlmethod double LogGraphCanvas::y2px(double y) - Converts an \c y coordinate to it's relative position on the canvas. - The y axis not supporting logarithmic scale, it only support linear convertion. - */ - function y2px(y) { - return (ymax-y)*yzoom - } - - /*! - \qmlmethod double LogGraphCanvas::px2x(double px) - Converts an x \c px position on the canvas to it's corresponding coordinate on the plot. - It supports both logarithmic and non logarithmic scale depending on the currently selected mode. - */ - function px2x(px) { - if(logscalex) { - return Math.exp(px/xzoom+Math.log(xmin)) - } else return (px/xzoom+xmin) - } - - /*! - \qmlmethod double LogGraphCanvas::px2x(double px) - Converts an x \c px position on the canvas to it's corresponding coordinate on the plot. - It supports both logarithmic and non logarithmic scale depending on the currently selected mode. - */ - function px2y(px) { - return -(px/yzoom-ymax) - } - - /*! - \qmlmethod bool LogGraphCanvas::isVisible(double x, double y) - Checks whether a plot point (\c x, \c y) is visible or not on the canvas. - */ - function isVisible(x, y) { - return (x2px(x) >= 0 && x2px(x) <= canvasSize.width) && (y2px(y) >= 0 && y2px(y) <= canvasSize.height) - } - - /*! - \qmlmethod bool LogGraphCanvas::drawLine(var ctx, double x1, double y1, double x2, double y2) - Draws a line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx. - */ - function drawLine(ctx, x1, y1, x2, y2) { - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - } - - /*! - \qmlmethod bool LogGraphCanvas::drawDashedLine2(var ctx, double x1, double y1, double x2, double y2) - Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx. - */ - function drawDashedLine2(ctx, x1, y1, x2, y2, dashPxSize = 5) { - ctx.setLineDash([dashPxSize, dashPxSize]); - drawLine(ctx, x1, y1, x2, y2) - ctx.setLineDash([]); - } - - /*! - \qmlmethod bool LogGraphCanvas::drawDashedLine(var ctx, double x1, double y1, double x2, double y2) - Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx. - (Legacy slower method) - */ - function drawDashedLine(ctx, x1, y1, x2, y2, dashPxSize = 10) { - var distance = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)) - var progPerc = dashPxSize/distance - ctx.beginPath(); - ctx.moveTo(x1, y1); - for(var i = 0; i < 1; i += progPerc) { - ctx.lineTo(x1-(x1-x2)*i, y1-(y1-y2)*i) - ctx.moveTo(x1-(x1-x2)*(i+progPerc/2), y1-(y1-y2)*(i+progPerc/2)) - } - ctx.stroke(); - } - - /*! - \qmlmethod var LogGraphCanvas::renderLatexImage(string ltxText, color) - Renders latex markup \c ltxText to an image and loads it. Returns a dictionary with three values: source, width and height. - */ - function renderLatexImage(ltxText, color, callback) { - let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, textsize, color).split(",") - let imgData = { - "source": ltxSrc, - "width": parseFloat(ltxWidth), - "height": parseFloat(ltxHeight) - }; - if(!isImageLoaded(ltxSrc) && !isImageLoading(ltxSrc)){ - // Wait until the image is loaded to callback. - loadImage(ltxSrc) - imageLoaders[ltxSrc] = [callback, imgData] - } else { - // Callback directly - callback(canvas, ctx, imgData) - } - } -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml deleted file mode 100644 index 5b77730..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml +++ /dev/null @@ -1,378 +0,0 @@ -/** - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQml -import QtQuick.Controls -import eu.ad5001.MixedMenu 1.1 -import QtQuick.Layouts 1.12 -import QtQuick -// Auto loading all objects. -import "js/objs/autoload.js" as ALObjects - -import "js/objects.js" as Objects -import "js/math/latex.js" as LatexJS -import eu.ad5001.LogarithmPlotter.History 1.0 -import eu.ad5001.LogarithmPlotter.ObjectLists 1.0 -import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup - -/*! - \qmltype LogarithmPlotter - \inqmlmodule eu.ad5001.LogarithmPlotter - \brief Main window of LogarithmPlotter - - \sa AppMenuBar, History, GreetScreen, Changelog, Alert, ObjectLists, Settings, HistoryBrowser, LogGraphCanvas, PickLocationOverlay. -*/ -ApplicationWindow { - id: root - visible: true - width: 1000 - height: 500 - color: sysPalette.window - title: "LogarithmPlotter " + (settings.saveFilename != "" ? " - " + settings.saveFilename.split('/').pop() : "") + (history.saved ? "" : "*") - - SystemPalette { - id: sysPalette; colorGroup: SystemPalette.Active - - Component.onCompleted: { - // LatexJS initialization. - LatexJS.enabled = Helper.getSettingBool("enable_latex") - LatexJS.Renderer = Latex - LatexJS.defaultColor = sysPalette.windowText - } - } - SystemPalette { id: sysPaletteIn; colorGroup: SystemPalette.Disabled } - - menuBar: appMenu.trueItem - - AppMenuBar {id: appMenu} - - History { id: history } - - Popup.GreetScreen {} - - Popup.Changelog {id: changelog} - - Popup.About {id: about} - - Popup.ThanksTo {id: thanksTo} - - Popup.Alert { - id: alert - anchors.bottom: parent.bottom - anchors.bottomMargin: 5 - z: 3 - } - - Item { - id: sidebar - width: 300 - height: parent.height - //y: root.menuBar.height - readonly property bool inPortrait: root.width < root.height - /*modal: true// inPortrait - interactive: inPortrait - position: inPortrait ? 0 : 1 - */ - visible: !inPortrait - - - TabBar { - id: sidebarSelector - width: parent.width - anchors.top: parent.top - TabButton { - text: qsTr("Objects") - icon.name: 'polygon-add-nodes' - icon.color: sysPalette.windowText - //height: 24 - } - TabButton { - text: qsTr("Settings") - icon.name: 'preferences-system-symbolic' - icon.color: sysPalette.windowText - //height: 24 - } - TabButton { - text: qsTr("History") - icon.name: 'view-history' - icon.color: sysPalette.windowText - //height: 24 - } - } - - StackLayout { - id: sidebarContents - anchors.top: sidebarSelector.bottom - anchors.left: parent.left - anchors.topMargin: 5 - anchors.leftMargin: 5 - anchors.bottom: parent.bottom - //anchors.bottomMargin: sidebarSelector.height - width: parent.width - 5 - currentIndex: sidebarSelector.currentIndex - z: -1 - clip: true - - ObjectLists { - id: objectLists - onChanged: drawCanvas.requestPaint() - } - - Settings { - id: settings - canvas: drawCanvas - onChanged: drawCanvas.requestPaint() - } - - HistoryBrowser { - id: historyBrowser - } - } - } - - LogGraphCanvas { - id: drawCanvas - anchors.top: parent.top - anchors.left: sidebar.inPortrait ? parent.left : sidebar.right - height: parent.height - width: sidebar.inPortrait ? parent.width : parent.width - sidebar.width//*sidebar.position - x: sidebar.width//*sidebar.position - - xmin: settings.xmin - ymax: settings.ymax - xzoom: settings.xzoom - yzoom: settings.yzoom - xlabel: settings.xlabel - ylabel: settings.ylabel - yaxisstep: settings.yaxisstep - xaxisstep: settings.xaxisstep - logscalex: settings.logscalex - linewidth: settings.linewidth - textsize: settings.textsize - showxgrad: settings.showxgrad - showygrad: settings.showygrad - - property bool firstDrawDone: false - - onPainted: if(!firstDrawDone) { - firstDrawDone = true; - console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms") - if(TestBuild == true) { - console.log("Plot drawn in canvas, terminating test of build in 100ms.") - testBuildTimer.start() - } - } - - ViewPositionChangeOverlay { - id: viewPositionChanger - anchors.fill: parent - canvas: parent - settingsInstance: settings - } - - PickLocationOverlay { - id: positionPicker - anchors.fill: parent - canvas: parent - } - } - - /*! - \qmlmethod void LogarithmPlotter::saveDiagram(string filename) - Saves the diagram to a certain \c filename. - */ - function saveDiagram(filename) { - if(['lpf'].indexOf(filename.split('.')[filename.split('.').length-1]) == -1) - filename += '.lpf' - settings.saveFilename = filename - var objs = {} - for(var objType in Objects.currentObjects){ - objs[objType] = [] - for(var obj of Objects.currentObjects[objType]) { - objs[objType].push(obj.export()) - } - } - Helper.write(filename, JSON.stringify({ - "xzoom": settings.xzoom, - "yzoom": settings.yzoom, - "xmin": settings.xmin, - "ymax": settings.ymax, - "xaxisstep": settings.xaxisstep, - "yaxisstep": settings.yaxisstep, - "xaxislabel": settings.xlabel, - "yaxislabel": settings.ylabel, - "logscalex": settings.logscalex, - "linewidth": settings.linewidth, - "showxgrad": settings.showxgrad, - "showygrad": settings.showygrad, - "textsize": settings.textsize, - "history": history.serialize(), - "width": root.width, - "height": root.height, - "objects": objs, - "type": "logplotv1" - })) - alert.show(qsTr("Saved plot to '%1'.").arg(filename.split("/").pop())) - history.saved = true - } - - /*! - \qmlmethod void LogarithmPlotter::saveDiagram(string filename) - Loads the diagram from a certain \c filename. - */ - function loadDiagram(filename) { - let basename = filename.split("/").pop() - alert.show(qsTr("Loading file '%1'.").arg(basename)) - let data = JSON.parse(Helper.load(filename)) - let error = ""; - if(Object.keys(data).includes("type") && data["type"] == "logplotv1") { - history.clear() - // Importing settings - settings.saveFilename = filename - settings.xzoom = data["xzoom"] - settings.yzoom = data["yzoom"] - settings.xmin = data["xmin"] - settings.ymax = data["ymax"] - settings.xaxisstep = data["xaxisstep"] - settings.yaxisstep = data["yaxisstep"] - settings.xlabel = data["xaxislabel"] - settings.ylabel = data["yaxislabel"] - settings.logscalex = data["logscalex"] - if("showxgrad" in data) - settings.showxgrad = data["showxgrad"] - if("showygrad" in data) - settings.textsize = data["showygrad"] - if("linewidth" in data) - settings.linewidth = data["linewidth"] - if("textsize" in data) - settings.textsize = data["textsize"] - root.height = data["height"] - root.width = data["width"] - - // Importing objects - Objects.currentObjects = {} - Object.keys(Objects.currentObjectsByName).forEach(key => { - delete Objects.currentObjectsByName[key]; - // Required to keep the same reference for the copy of the object used in expression variable detection. - // Another way would be to change the reference as well, but I feel like the code would be less clean. - }) - for(let objType in data['objects']) { - if(Object.keys(Objects.types).indexOf(objType) > -1) { - Objects.currentObjects[objType] = [] - for(let objData of data['objects'][objType]) { - let obj = new Objects.types[objType](...objData) - Objects.currentObjects[objType].push(obj) - Objects.currentObjectsByName[obj.name] = obj - } - } else { - error += qsTr("Unknown object type: %1.").arg(objType) + "\n"; - } - } - - // Updating object dependencies. - for(let objName in Objects.currentObjectsByName) - Objects.currentObjectsByName[objName].update() - - // Importing history - if("history" in data) - history.unserialize(...data["history"]) - - // Refreshing sidebar - if(sidebarSelector.currentIndex == 0) { - // For some reason, if we load a file while the tab is on object, - // we get stuck in a Qt-side loop? Qt bug or side-effect here, I don't know. - sidebarSelector.currentIndex = 1 - objectLists.update() - delayRefreshTimer.start() - } else { - objectLists.update() - } - } else { - error = qsTr("Invalid file provided.") - } - if(error != "") { - console.log(error) - alert.show(qsTr("Could not save file: ") + error) - // TODO: Error handling - return - } - drawCanvas.requestPaint() - alert.show(qsTr("Loaded file '%1'.").arg(basename)) - history.saved = true - } - - Timer { - id: delayRefreshTimer - repeat: false - interval: 1 - onTriggered: sidebarSelector.currentIndex = 0 - } - - Timer { - id: testBuildTimer - repeat: false - interval: 100 - onTriggered: Qt.quit() // Quit after paint on test build - } - - onClosing: function(close) { - if(!history.saved) { - close.accepted = false - appMenu.openSaveUnsavedChangesDialog() - } - } - - /*! - \qmlmethod void LogarithmPlotter::copyDiagramToClipboard() - Copies the current diagram image to the clipboard. - */ - function copyDiagramToClipboard() { - var file = Helper.gettmpfile() - drawCanvas.save(file) - Helper.copyImageToClipboard() - alert.show(qsTr("Copied plot screenshot to clipboard!")) - } - - /*! - \qmlmethod void LogarithmPlotter::showAlert(string alertText) - Shows an alert on the diagram. - */ - function showAlert(alertText) { - // This function is called from the backend and is used to show alerts from there. - alert.show(alertText) - } - - - Menu { - id: updateMenu - title: qsTr("&Update") - Action { - text: qsTr("&Update LogarithmPlotter") - icon.name: 'update' - onTriggered: Qt.openUrlExternally("https://dev.apps.ad5001.eu/logarithmplotter") - } - } - - /*! - \qmlmethod void LogarithmPlotter::showUpdateMenu() - Shows the update menu in the AppMenuBar. - */ - function showUpdateMenu() { - appMenu.addMenu(updateMenu) - } -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/GreetScreen.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/GreetScreen.qml deleted file mode 100644 index dddafab..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/GreetScreen.qml +++ /dev/null @@ -1,236 +0,0 @@ -/** - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick -import QtQuick.Controls -import "../js/math/latex.js" as Latex - -/*! - \qmltype GreetScreen - \inqmlmodule eu.ad5001.LogarithmPlotter.Popup - \brief Overlay displayed when LogarithmPlotter is launched for the first time or when it was just updated. - - It contains several settings as well as an easy access to the changelog - - \sa LogarithmPlotter, Settings, AppMenuBar, Changelog -*/ -Popup { - id: greetingPopup - x: (parent.width-width)/2 - y: Math.max(20, (parent.height-height)/2) - width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20 - height: Math.min(parent.height-40, 700) - modal: true - focus: true - clip: true - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - - ScrollView { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.bottomMargin: bottomButtons.height + 20 - clip: true - - Column { - width: greetingPopup.width - 25 - spacing: 10 - clip: true - topPadding: 35 - - Row { - id: welcome - height: logo.height - spacing: 10 - anchors.horizontalCenter: parent.horizontalCenter - - Image { - id: logo - source: "../icons/logarithmplotter.svg" - sourceSize.width: 48 - sourceSize.height: 48 - width: 48 - height: 48 - } - - Label { - id: welcomeText - anchors.verticalCenter: parent.verticalCenter - wrapMode: Text.WordWrap - font.pixelSize: 32 - text: qsTr("Welcome to LogarithmPlotter") - } - } - - Label { - id: versionText - anchors.horizontalCenter: parent.horizontalCenter - wrapMode: Text.WordWrap - width: implicitWidth - font.pixelSize: 18 - font.italic: true - text: qsTr("Version %1").arg(Helper.getVersion()) - } - - Label { - id: helpText - anchors.horizontalCenter: parent.horizontalCenter - wrapMode: Text.WordWrap - font.pixelSize: 14 - width: parent.width - 50 - text: qsTr("Take a few seconds to configure LogarithmPlotter.\nThese settings can be changed at any time from the \"Settings\" menu.") - } - - CheckBox { - id: checkForUpdatesSetting - anchors.left: parent.left - checked: Helper.getSettingBool("check_for_updates") - text: qsTr('Check for updates on startup (requires online connectivity)') - onClicked: { - Helper.setSettingBool("check_for_updates", checked) - // Set in the menu bar - appMenu.settingsMenu.children[0].checked = checked - } - } - - CheckBox { - id: resetRedoStackSetting - anchors.left: parent.left - checked: Helper.getSettingBool("reset_redo_stack") - text: qsTr('Reset redo stack when a new action is added to history') - onClicked: { - Helper.setSettingBool("reset_redo_stack", checked) - appMenu.settingsMenu.children[1].checked = checked - } - } - - CheckBox { - id: enableLatexSetting - anchors.left: parent.left - checked: Helper.getSettingBool("enable_latex") - text: qsTr('Enable LaTeX rendering') - onClicked: { - Helper.setSettingBool("enable_latex", checked) - appMenu.settingsMenu.children[2].checked = checked - } - } - - CheckBox { - id: autocloseFormulaSetting - anchors.left: parent.left - checked: Helper.getSettingBool("expression_editor.autoclose") - text: qsTr('Automatically close parenthesises and brackets in expressions') - onClicked: { - Helper.setSettingBool("expression_editor.autoclose", checked) - appMenu.settingsMenu.children[3].children[0].checked = checked - } - } - - CheckBox { - id: colorizeFormulaSetting - anchors.left: parent.left - checked: Helper.getSettingBool("expression_editor.colorize") - text: qsTr('Enable syntax highlighting for expressions') - onClicked: { - Helper.setSettingBool("expression_editor.colorize", checked) - appMenu.settingsMenu.children[3].children[1].checked = checked - } - } - - CheckBox { - id: autocompleteFormulaSetting - anchors.left: parent.left - checked: Helper.getSettingBool("autocompletion.enabled") - text: qsTr('Enable autocompletion interface in expression editor') - onClicked: { - Helper.setSettingBool("autocompletion.enabled", checked) - appMenu.settingsMenu.children[3].children[2].checked = checked - } - } - - Row { - anchors.left: parent.left - anchors.leftMargin: 10 - spacing: 10 - - Label { - id: colorSchemeLabel - anchors.verticalCenter: parent.verticalCenter - wrapMode: Text.WordWrap - text: qsTr("Color scheme:") - } - - ComboBox { - model: ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"] - currentIndex: Helper.getSettingInt("expression_editor.color_scheme") - - onActivated: function(index) { - Helper.setSettingInt("expression_editor.color_scheme", index) - } - } - } - } - } - - Rectangle { - id: bottomSeparator - opacity: 0.3 - color: sysPalette.windowText - width: parent.width * 2 / 3 - height: 1 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: bottomButtons.top - anchors.bottomMargin: 9 - } - - Row { - id: bottomButtons - anchors.bottom: parent.bottom - anchors.bottomMargin: 7 - spacing: 10 - anchors.horizontalCenter: parent.horizontalCenter - - Button { - id: userManualBtn - text: qsTr("User manual") - font.pixelSize: 18 - onClicked: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar") - } - - Button { - id: changelogBtn - text: qsTr("Changelog") - font.pixelSize: 18 - onClicked: changelog.open() - } - - Button { - id: doneBtn - text: qsTr("Done") - font.pixelSize: 18 - onClicked: greetingPopup.close() - } - } - - Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()) { - greetingPopup.open() - } - - onClosed: Helper.setSetting("last_install_greet", Helper.getVersion()) -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/ThanksTo.qml b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/ThanksTo.qml deleted file mode 100644 index 4c6d29c..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/ThanksTo.qml +++ /dev/null @@ -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 . - */ - -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) - } - } - } - } - } - } - } -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Display Mode.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Display Mode.svg deleted file mode 120000 index 7a5ba45..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Display Mode.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/appearance.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Display Style.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Display Style.svg deleted file mode 120000 index 7a5ba45..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Display Style.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/appearance.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Label Position.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Label Position.svg deleted file mode 120000 index 43aa15f..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Label Position.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/arrow.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Label X.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Label X.svg deleted file mode 120000 index 6b40925..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Label X.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/position.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Phase.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Phase.svg deleted file mode 120000 index 21699e9..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Phase.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/angle.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Point Style.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Point Style.svg deleted file mode 120000 index 7a5ba45..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Point Style.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/appearance.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Target Element.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Target Element.svg deleted file mode 120000 index 50124dc..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Target Element.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/target.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Target Value Position.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Target Value Position.svg deleted file mode 120000 index 6b40925..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Target Value Position.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/position.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Text.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Text.svg deleted file mode 120000 index 69be9e1..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Text.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/label.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Unit.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Unit.svg deleted file mode 120000 index 21699e9..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Unit.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/angle.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/X.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/X.svg deleted file mode 120000 index 6b40925..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/X.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/position.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Y.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Y.svg deleted file mode 120000 index 6b40925..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Y.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/position.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/ω_0.svg b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/ω_0.svg deleted file mode 120000 index 21699e9..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/ω_0.svg +++ /dev/null @@ -1 +0,0 @@ -../../common/angle.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js deleted file mode 100644 index 1afd66b..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js +++ /dev/null @@ -1,1852 +0,0 @@ -// https://silentmatt.com/javascript-expression-evaluator/ - -.pragma library - -var INUMBER = 'INUMBER'; -var IOP1 = 'IOP1'; -var IOP2 = 'IOP2'; -var IOP3 = 'IOP3'; -var IVAR = 'IVAR'; -var IVARNAME = 'IVARNAME'; -var IFUNCALL = 'IFUNCALL'; -var IFUNDEF = 'IFUNDEF'; -var IEXPR = 'IEXPR'; -var IEXPREVAL = 'IEXPREVAL'; -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; -} - -Instruction.prototype.toString = function () { - switch (this.type) { - case INUMBER: - case IOP1: - case IOP2: - case IOP3: - case IVAR: - case IVARNAME: - case IENDSTATEMENT: - return this.value; - case IFUNCALL: - return 'CALL ' + this.value; - case IFUNDEF: - return 'DEF ' + this.value; - case IARRAY: - return 'ARRAY ' + this.value; - case IMEMBER: - return '.' + this.value; - default: - return 'Invalid Instruction'; - } -}; - -function unaryInstruction(value) { - return new Instruction(IOP1, value); -} - -function binaryInstruction(value) { - return new Instruction(IOP2, value); -} - -function ternaryInstruction(value) { - return new Instruction(IOP3, value); -} - -function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) { - var nstack = []; - var newexpression = []; - var n1, n2, n3; - var f; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === INUMBER || type === IVARNAME) { - if (Array.isArray(item.value)) { - nstack.push.apply(nstack, simplify(item.value.map(function (x) { - return new Instruction(INUMBER, x); - }).concat(new Instruction(IARRAY, item.value.length)), unaryOps, binaryOps, ternaryOps, values)); - } else { - nstack.push(item); - } - } else if (type === IVAR && values.hasOwnProperty(item.value)) { - item = new Instruction(INUMBER, values[item.value]); - nstack.push(item); - } else if (type === IOP2 && nstack.length > 1) { - n2 = nstack.pop(); - n1 = nstack.pop(); - f = binaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value)); - nstack.push(item); - } else if (type === IOP3 && nstack.length > 2) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === '?') { - nstack.push(n1.value ? n2.value : n3.value); - } else { - f = ternaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); - nstack.push(item); - } - } else if (type === IOP1 && nstack.length > 0) { - n1 = nstack.pop(); - f = unaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value)); - nstack.push(item); - } else if (type === IEXPR) { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values))); - } else if (type === IMEMBER && nstack.length > 0) { - n1 = nstack.pop(); - //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)) - } /* else if (type === IARRAY && nstack.length >= item.value) { - var length = item.value; - while (length-- > 0) { - newexpression.push(nstack.pop()); - } - newexpression.push(new Instruction(IARRAY, item.value)); - } */ else { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - newexpression.push(item); - } - } - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - return newexpression; -} - -function substitute(tokens, variable, expr) { - var newexpression = []; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === IVAR && item.value === variable) { - for (var j = 0; j < expr.tokens.length; j++) { - var expritem = expr.tokens[j]; - var replitem; - if (expritem.type === IOP1) { - replitem = unaryInstruction(expritem.value); - } else if (expritem.type === IOP2) { - replitem = binaryInstruction(expritem.value); - } else if (expritem.type === IOP3) { - replitem = ternaryInstruction(expritem.value); - } else { - replitem = new Instruction(expritem.type, expritem.value); - } - newexpression.push(replitem); - } - } else if (type === IEXPR) { - newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr))); - } else { - newexpression.push(item); - } - } - return newexpression; -} - -function evaluate(tokens, expr, values) { - var nstack = []; - var n1, n2, n3; - var f, args, argCount; - - if (isExpressionEvaluator(tokens)) { - return resolveExpression(tokens, values); - } - - var numTokens = tokens.length; - - for (var i = 0; i < numTokens; i++) { - var item = tokens[i]; - var type = item.type; - if (type === INUMBER || type === IVARNAME) { - nstack.push(item.value); - } else if (type === IOP2) { - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === 'and') { - nstack.push(n1 ? !!evaluate(n2, expr, values) : false); - } else if (item.value === 'or') { - nstack.push(n1 ? true : !!evaluate(n2, expr, values)); - } else if (item.value === '=') { - f = expr.binaryOps[item.value]; - nstack.push(f(n1, evaluate(n2, expr, values), values)); - } else { - f = expr.binaryOps[item.value]; - nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values))); - } - } else if (type === IOP3) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === '?') { - nstack.push(evaluate(n1 ? n2 : n3, expr, values)); - } else { - f = expr.ternaryOps[item.value]; - nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values), resolveExpression(n3, values))); - } - } else if (type === IVAR) { - // Check for variable value - if (/^__proto__|prototype|constructor$/.test(item.value)) { - throw new Error('WARNING: Prototype access detected and denied. If you downloaded this file from the internet, this file might be a virus.'); - } else if (item.value in expr.functions) { - nstack.push(expr.functions[item.value]); - } else if (item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) { - nstack.push(expr.unaryOps[item.value]); - } else { - var v = values[item.value]; - if (v !== undefined) { - nstack.push(v); - } else { - throw new Error(qsTranslate('error', 'Undefined variable %1.').arg(item.value)); - } - } - } else if (type === IOP1) { - n1 = nstack.pop(); - f = expr.unaryOps[item.value]; - nstack.push(f(resolveExpression(n1, values))); - } else if (type === IFUNCALL) { - argCount = item.value; - args = []; - while (argCount-- > 0) { - args.unshift(resolveExpression(nstack.pop(), values)); - } - f = nstack.pop(); - if (f.apply && f.call) { - nstack.push(f.apply(undefined, args)); - } else if(f.execute) { - // Objects & expressions execution - if(args.length >= 1) - nstack.push(f.execute.apply(f, args)); - else - throw new Error(qsTranslate('error', 'In order to be executed, object %1 must have at least one argument.').arg(f)) - } else { - throw new Error(qsTranslate('error', '%1 cannot be executed.').arg(f)); - } - } else if (type === IFUNDEF) { - // Create closure to keep references to arguments and expression - nstack.push((function () { - var n2 = nstack.pop(); - var args = []; - var argCount = item.value; - while (argCount-- > 0) { - args.unshift(nstack.pop()); - } - var n1 = nstack.pop(); - var f = function () { - var scope = Object.assign({}, values); - for (var i = 0, len = args.length; i < len; i++) { - scope[args[i]] = arguments[i]; - } - return evaluate(n2, expr, scope); - }; - // f.name = n1 - Object.defineProperty(f, 'name', { - value: n1, - writable: false - }); - values[n1] = f; - return f; - })()); - } else if (type === IEXPR) { - nstack.push(createExpressionEvaluator(item, expr)); - } else if (type === IEXPREVAL) { - 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)) - } else if (type === IENDSTATEMENT) { - nstack.pop(); - } else if (type === IARRAY) { - argCount = item.value; - args = []; - while (argCount-- > 0) { - args.unshift(nstack.pop()); - } - nstack.push(args); - } else { - throw new Error(qsTranslate('error', 'Invalid expression.')); - } - } - if (nstack.length > 1) { - throw new Error(qsTranslate('error', 'Invalid expression (parity).')); - } - // Explicitly return zero to avoid test issues caused by -0 - return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values); -} - -function createExpressionEvaluator(token, expr, values) { - if (isExpressionEvaluator(token)) return token; - return { - type: IEXPREVAL, - value: function (scope) { - return evaluate(token.value, expr, scope); - } - }; -} - -function isExpressionEvaluator(n) { - return n && n.type === IEXPREVAL; -} - -function resolveExpression(n, values) { - return isExpressionEvaluator(n) ? n.value(values) : n; -} - -function expressionToString(tokens, toJS) { - 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; - if (type === INUMBER) { - if (typeof item.value === 'number' && item.value < 0) { - nstack.push('(' + item.value + ')'); - } else if (Array.isArray(item.value)) { - nstack.push('[' + item.value.map(escapeValue).join(', ') + ']'); - } else { - nstack.push(escapeValue(item.value)); - } - } else if (type === IOP2) { - n2 = nstack.pop(); - n1 = nstack.pop(); - f = item.value; - if (toJS) { - if (f === '^') { - nstack.push('Math.pow(' + n1 + ', ' + n2 + ')'); - } else if (f === 'and') { - nstack.push('(!!' + n1 + ' && !!' + n2 + ')'); - } else if (f === 'or') { - nstack.push('(!!' + n1 + ' || !!' + n2 + ')'); - } else if (f === '||') { - nstack.push('(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((' + n1 + '),(' + n2 + ')))'); - } else if (f === '==') { - nstack.push('(' + n1 + ' === ' + n2 + ')'); - } else if (f === '!=') { - nstack.push('(' + n1 + ' !== ' + n2 + ')'); - } else if (f === '[') { - nstack.push(n1 + '[(' + n2 + ') | 0]'); - } else { - nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')'); - } - } else { - if (f === '[') { - nstack.push(n1 + '[' + n2 + ']'); - } else { - nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')'); - } - } - } else if (type === IOP3) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - f = item.value; - if (f === '?') { - nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')'); - } else { - throw new Error(qsTranslate('error', 'Invalid expression.')); - } - } else if (type === IVAR || type === IVARNAME) { - nstack.push(item.value); - } else if (type === IOP1) { - n1 = nstack.pop(); - f = item.value; - if (f === '-' || f === '+') { - nstack.push('(' + f + n1 + ')'); - } else if (toJS) { - if (f === 'not') { - nstack.push('(' + '!' + n1 + ')'); - } else if (f === '!') { - nstack.push('fac(' + n1 + ')'); - } else { - nstack.push(f + '(' + n1 + ')'); - } - } else if (f === '!') { - nstack.push('(' + n1 + '!)'); - } else { - nstack.push('(' + f + ' ' + n1 + ')'); - } - } else if (type === IFUNCALL) { - argCount = item.value; - args = []; - while (argCount-- > 0) { - args.unshift(nstack.pop()); - } - f = nstack.pop(); - nstack.push(f + '(' + args.join(', ') + ')'); - } else if (type === IFUNDEF) { - n2 = nstack.pop(); - argCount = item.value; - args = []; - while (argCount-- > 0) { - args.unshift(nstack.pop()); - } - n1 = nstack.pop(); - if (toJS) { - nstack.push('(' + n1 + ' = function(' + args.join(', ') + ') { return ' + n2 + ' })'); - } else { - nstack.push('(' + n1 + '(' + args.join(', ') + ') = ' + n2 + ')'); - } - } else if (type === IMEMBER) { - n1 = nstack.pop(); - nstack.push(n1 + '.' + item.value); - } else if (type === IARRAY) { - argCount = item.value; - args = []; - while (argCount-- > 0) { - args.unshift(nstack.pop()); - } - nstack.push('[' + args.join(', ') + ']'); - } else if (type === IEXPR) { - nstack.push('(' + expressionToString(item.value, toJS) + ')'); - } else if (type === IENDSTATEMENT) ; else { - throw new Error(qsTranslate('error', 'Invalid expression.')); - } - } - if (nstack.length > 1) { - if (toJS) { - nstack = [ nstack.join(',') ]; - } else { - nstack = [ nstack.join(';') ]; - } - } - return String(nstack[0]); -} - -function escapeValue(v) { - if (typeof v === 'string') { - return JSON.stringify(v).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); - } - return v; -} - -function contains(array, obj) { - for (var i = 0; i < array.length; i++) { - if (array[i] === obj) { - return true; - } - } - return false; -} - -function getSymbols(tokens, symbols, options) { - options = options || {}; - var withMembers = !!options.withMembers; - var prevVar = null; - - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - if (item.type === IVAR || item.type === IVARNAME) { - if (!withMembers && !contains(symbols, item.value)) { - symbols.push(item.value); - } else if (prevVar !== null) { - if (!contains(symbols, prevVar)) { - symbols.push(prevVar); - } - prevVar = item.value; - } else { - prevVar = item.value; - } - } else if (item.type === IMEMBER && withMembers && prevVar !== null) { - prevVar += '.' + item.value; - } else if (item.type === IEXPR) { - getSymbols(item.value, symbols, options); - } else if (prevVar !== null) { - if (!contains(symbols, prevVar)) { - symbols.push(prevVar); - } - prevVar = null; - } - } - - if (prevVar !== null && !contains(symbols, prevVar)) { - symbols.push(prevVar); - } -} - -function Expression(tokens, parser) { - this.tokens = tokens; - this.parser = parser; - this.unaryOps = parser.unaryOps; - this.binaryOps = parser.binaryOps; - this.ternaryOps = parser.ternaryOps; - this.functions = parser.functions; -} - -Expression.prototype.simplify = function (values) { - values = values || {}; - return new Expression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser); -}; - -Expression.prototype.substitute = function (variable, expr) { - if (!(expr instanceof Expression)) { - expr = this.parser.parse(String(expr)); - } - - return new Expression(substitute(this.tokens, variable, expr), this.parser); -}; - -Expression.prototype.evaluate = function (values) { - values = Object.assign({}, values, this.parser.consts) - return evaluate(this.tokens, this, values); -}; - -Expression.prototype.toString = function () { - return expressionToString(this.tokens, false); -}; - -Expression.prototype.symbols = function (options) { - options = options || {}; - var vars = []; - getSymbols(this.tokens, vars, options); - return vars; -}; - -Expression.prototype.variables = function (options) { - options = 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); - }); -}; - -Expression.prototype.toJSFunction = function (param, variables) { - var expr = this; - var f = new Function(param, 'with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return ' + expressionToString(this.simplify(variables).tokens, true) + '; }'); // eslint-disable-line no-new-func - return function () { - return f.apply(expr, arguments); - }; -}; - -var TEOF = 'TEOF'; -var TOP = 'TOP'; -var TNUMBER = 'TNUMBER'; -var TSTRING = 'TSTRING'; -var TPAREN = 'TPAREN'; -var TBRACKET = 'TBRACKET'; -var TCOMMA = 'TCOMMA'; -var TNAME = 'TNAME'; -var TSEMICOLON = 'TSEMICOLON'; - -function Token(type, value, index) { - this.type = type; - this.value = value; - this.index = index; -} - -Token.prototype.toString = function () { - return this.type + ': ' + this.value; -}; - -function TokenStream(parser, expression) { - this.pos = 0; - this.current = null; - this.unaryOps = parser.unaryOps; - this.binaryOps = parser.binaryOps; - this.ternaryOps = parser.ternaryOps; - this.builtinConsts = parser.builtinConsts; - this.expression = expression; - this.savedPosition = 0; - this.savedCurrent = null; - this.options = parser.options; - this.parser = parser; -} - -TokenStream.prototype.newToken = function (type, value, pos) { - return new Token(type, value, pos != null ? pos : this.pos); -}; - -TokenStream.prototype.save = function () { - this.savedPosition = this.pos; - this.savedCurrent = this.current; -}; - -TokenStream.prototype.restore = function () { - this.pos = this.savedPosition; - this.current = this.savedCurrent; -}; - -TokenStream.prototype.next = function () { - if (this.pos >= this.expression.length) { - return this.newToken(TEOF, 'EOF'); - } - - if (this.isWhitespace() || this.isComment()) { - return this.next(); - } else if (this.isRadixInteger() || - this.isNumber() || - this.isOperator() || - this.isString() || - this.isParen() || - this.isBracket() || - this.isComma() || - this.isSemicolon() || - this.isNamedOp() || - this.isConst() || - this.isName()) { - return this.current; - } else { - this.parseError(qsTranslate('error', 'Unknown character "%1".').arg(this.expression.charAt(this.pos))); - } -}; - -TokenStream.prototype.isString = function () { - var r = false; - var startPos = this.pos; - var quote = this.expression.charAt(startPos); - - if (quote === '\'' || quote === '"') { - var index = this.expression.indexOf(quote, startPos + 1); - while (index >= 0 && this.pos < this.expression.length) { - this.pos = index + 1; - if (this.expression.charAt(index - 1) !== '\\') { - var rawString = this.expression.substring(startPos + 1, index); - this.current = this.newToken(TSTRING, this.unescape(rawString), startPos); - r = true; - break; - } - index = this.expression.indexOf(quote, index + 1); - } - } - return r; -}; - -TokenStream.prototype.isParen = function () { - var c = this.expression.charAt(this.pos); - if (c === '(' || c === ')') { - this.current = this.newToken(TPAREN, c); - this.pos++; - return true; - } - return false; -}; - -TokenStream.prototype.isBracket = function () { - var c = this.expression.charAt(this.pos); - if ((c === '[' || c === ']') && this.isOperatorEnabled('[')) { - this.current = this.newToken(TBRACKET, c); - this.pos++; - return true; - } - return false; -}; - -TokenStream.prototype.isComma = function () { - var c = this.expression.charAt(this.pos); - if (c === ',') { - this.current = this.newToken(TCOMMA, ','); - this.pos++; - return true; - } - return false; -}; - -TokenStream.prototype.isSemicolon = function () { - var c = this.expression.charAt(this.pos); - if (c === ';') { - this.current = this.newToken(TSEMICOLON, ';'); - this.pos++; - return true; - } - return false; -}; - -TokenStream.prototype.isConst = function () { - var startPos = this.pos; - 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 (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) { - break; - } - } - } - if (i > startPos) { - var str = this.expression.substring(startPos, i); - if (str in this.builtinConsts) { - this.current = this.newToken(TNUMBER, this.builtinConsts[str]); - this.pos += str.length; - return true; - } - } - return false; -}; - -TokenStream.prototype.isNamedOp = function () { - var startPos = this.pos; - var i = startPos; - for (; i < this.expression.length; i++) { - var c = this.expression.charAt(i); - if (c.toUpperCase() === c.toLowerCase()) { - if (i === this.pos || (c !== '_' && (c < '0' || c > '9'))) { - break; - } - } - } - if (i > startPos) { - var str = this.expression.substring(startPos, i); - if (this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) { - this.current = this.newToken(TOP, str); - this.pos += str.length; - return true; - } - } - return false; -}; - -TokenStream.prototype.isName = function () { - var startPos = this.pos; - var i = startPos; - 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 (i === this.pos && (c === '$' || c === '_')) { - if (c === '_') { - hasLetter = true; - } - continue; - } else if (i === this.pos || !hasLetter || (c !== '_' && (c < '0' || c > '9'))) { - break; - } - } else { - hasLetter = true; - } - } - if (hasLetter) { - var str = this.expression.substring(startPos, i); - this.current = this.newToken(TNAME, str); - this.pos += str.length; - return true; - } - return false; -}; - -TokenStream.prototype.isWhitespace = function () { - var r = false; - var c = this.expression.charAt(this.pos); - while (c === ' ' || c === '\t' || c === '\n' || c === '\r') { - r = true; - this.pos++; - if (this.pos >= this.expression.length) { - break; - } - c = this.expression.charAt(this.pos); - } - return r; -}; - -var codePointPattern = /^[0-9a-f]{4}$/i; - -TokenStream.prototype.unescape = function (v) { - var index = v.indexOf('\\'); - if (index < 0) { - return v; - } - - var buffer = v.substring(0, index); - while (index >= 0) { - var c = v.charAt(++index); - switch (c) { - case '\'': - buffer += '\''; - break; - case '"': - buffer += '"'; - break; - case '\\': - buffer += '\\'; - break; - case '/': - buffer += '/'; - break; - case 'b': - buffer += '\b'; - break; - case 'f': - buffer += '\f'; - break; - case 'n': - buffer += '\n'; - break; - case 'r': - buffer += '\r'; - break; - case 't': - buffer += '\t'; - break; - case 'u': - // interpret the following 4 characters as the hex of the unicode code point - var codePoint = v.substring(index + 1, index + 5); - if (!codePointPattern.test(codePoint)) { - this.parseError(qsTranslate('error', 'Illegal escape sequence: %1.').arg("\\u" + codePoint)); - } - buffer += String.fromCharCode(parseInt(codePoint, 16)); - index += 4; - break; - default: - throw this.parseError(qsTranslate('error', 'Illegal escape sequence: %1.').arg('\\' + c)); - } - ++index; - var backslash = v.indexOf('\\', index); - buffer += v.substring(index, backslash < 0 ? v.length : backslash); - index = backslash; - } - - return buffer; -}; - -TokenStream.prototype.isComment = function () { - var c = this.expression.charAt(this.pos); - if (c === '/' && this.expression.charAt(this.pos + 1) === '*') { - this.pos = this.expression.indexOf('*/', this.pos) + 2; - if (this.pos === 1) { - this.pos = this.expression.length; - } - return true; - } - return false; -}; - -TokenStream.prototype.isRadixInteger = function () { - var pos = this.pos; - - if (pos >= this.expression.length - 2 || this.expression.charAt(pos) !== '0') { - return false; - } - ++pos; - - var radix; - var validDigit; - if (this.expression.charAt(pos) === 'x') { - radix = 16; - validDigit = /^[0-9a-f]$/i; - ++pos; - } else if (this.expression.charAt(pos) === 'b') { - radix = 2; - validDigit = /^[01]$/i; - ++pos; - } else { - return false; - } - - var valid = false; - var startPos = pos; - - while (pos < this.expression.length) { - var c = this.expression.charAt(pos); - if (validDigit.test(c)) { - pos++; - valid = true; - } else { - break; - } - } - - if (valid) { - this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix)); - this.pos = pos; - } - return valid; -}; - -TokenStream.prototype.isNumber = function () { - var valid = false; - var pos = this.pos; - var startPos = pos; - var resetPos = pos; - var foundDot = false; - var foundDigits = false; - var c; - - while (pos < this.expression.length) { - c = this.expression.charAt(pos); - if ((c >= '0' && c <= '9') || (!foundDot && c === '.')) { - if (c === '.') { - foundDot = true; - } else { - foundDigits = true; - } - pos++; - valid = foundDigits; - } else { - break; - } - } - - if (valid) { - resetPos = pos; - } - - if (c === 'e' || c === 'E') { - pos++; - var acceptSign = true; - var validExponent = false; - while (pos < this.expression.length) { - c = this.expression.charAt(pos); - if (acceptSign && (c === '+' || c === '-')) { - acceptSign = false; - } else if (c >= '0' && c <= '9') { - validExponent = true; - acceptSign = false; - } else { - break; - } - pos++; - } - - if (!validExponent) { - pos = resetPos; - } - } - - if (valid) { - this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos))); - this.pos = pos; - } else { - this.pos = resetPos; - } - return valid; -}; - -TokenStream.prototype.isOperator = function () { - var startPos = this.pos; - var c = this.expression.charAt(this.pos); - - if (c === '+' || c === '-' || c === '*' || c === '/' || c === '%' || c === '^' || c === '?' || c === ':' || c === '.') { - this.current = this.newToken(TOP, c); - } else if (c === '∙' || c === '•') { - this.current = this.newToken(TOP, '*'); - } else if (c === '>') { - if (this.expression.charAt(this.pos + 1) === '=') { - this.current = this.newToken(TOP, '>='); - this.pos++; - } else { - this.current = this.newToken(TOP, '>'); - } - } else if (c === '<') { - if (this.expression.charAt(this.pos + 1) === '=') { - this.current = this.newToken(TOP, '<='); - this.pos++; - } else { - this.current = this.newToken(TOP, '<'); - } - } else if (c === '|') { - if (this.expression.charAt(this.pos + 1) === '|') { - this.current = this.newToken(TOP, '||'); - this.pos++; - } else { - return false; - } - } else if (c === '=') { - if (this.expression.charAt(this.pos + 1) === '=') { - this.current = this.newToken(TOP, '=='); - this.pos++; - } else { - this.current = this.newToken(TOP, c); - } - } else if (c === '!') { - if (this.expression.charAt(this.pos + 1) === '=') { - this.current = this.newToken(TOP, '!='); - this.pos++; - } else { - this.current = this.newToken(TOP, c); - } - } else { - return false; - } - this.pos++; - - if (this.isOperatorEnabled(this.current.value)) { - return true; - } else { - this.pos = startPos; - return false; - } -}; - -TokenStream.prototype.isOperatorEnabled = function (op) { - return this.parser.isOperatorEnabled(op); -}; - -TokenStream.prototype.getCoordinates = function () { - var line = 0; - var column; - var newline = -1; - do { - line++; - column = this.pos - newline; - newline = this.expression.indexOf('\n', newline + 1); - } while (newline >= 0 && newline < this.pos); - - return { - line: line, - column: column - }; -}; - -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)); -}; - -function ParserState(parser, tokenStream, options) { - this.parser = parser; - this.tokens = tokenStream; - this.current = null; - this.nextToken = null; - this.next(); - this.savedCurrent = null; - this.savedNextToken = null; - this.allowMemberAccess = options.allowMemberAccess !== false; -} - -ParserState.prototype.next = function () { - this.current = this.nextToken; - return (this.nextToken = this.tokens.next()); -}; - -ParserState.prototype.tokenMatches = function (token, value) { - if (typeof value === 'undefined') { - return true; - } else if (Array.isArray(value)) { - return contains(value, token.value); - } else if (typeof value === 'function') { - return value(token); - } else { - return token.value === value; - } -}; - -ParserState.prototype.save = function () { - this.savedCurrent = this.current; - this.savedNextToken = this.nextToken; - this.tokens.save(); -}; - -ParserState.prototype.restore = function () { - this.tokens.restore(); - this.current = this.savedCurrent; - this.nextToken = this.savedNextToken; -}; - -ParserState.prototype.accept = function (type, value) { - if (this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) { - this.next(); - return true; - } - return false; -}; - -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))); - } -}; - -ParserState.prototype.parseAtom = function (instr) { - var unaryOps = this.tokens.unaryOps; - function isPrefixOperator(token) { - return token.value in unaryOps; - } - - if (this.accept(TNAME) || this.accept(TOP, isPrefixOperator)) { - instr.push(new Instruction(IVAR, this.current.value)); - } else if (this.accept(TNUMBER)) { - instr.push(new Instruction(INUMBER, this.current.value)); - } else if (this.accept(TSTRING)) { - instr.push(new Instruction(INUMBER, this.current.value)); - } else if (this.accept(TPAREN, '(')) { - this.parseExpression(instr); - this.expect(TPAREN, ')'); - } else if (this.accept(TBRACKET, '[')) { - if (this.accept(TBRACKET, ']')) { - instr.push(new Instruction(IARRAY, 0)); - } else { - var argCount = this.parseArrayList(instr); - instr.push(new Instruction(IARRAY, argCount)); - } - } else { - throw new Error(qsTranslate('error', 'Unexpected %1').arg(this.nextToken)); - } -}; - -ParserState.prototype.parseExpression = function (instr) { - var exprInstr = []; - if (this.parseUntilEndStatement(instr, exprInstr)) { - return; - } - this.parseConditionalExpression(exprInstr); - if (this.parseUntilEndStatement(instr, exprInstr)) { - return; - } - this.pushExpression(instr, exprInstr); -}; - -ParserState.prototype.pushExpression = function (instr, exprInstr) { - for (var i = 0, len = exprInstr.length; i < len; i++) { - instr.push(exprInstr[i]); - } -}; - -ParserState.prototype.parseUntilEndStatement = function (instr, exprInstr) { - if (!this.accept(TSEMICOLON)) return false; - if (this.nextToken && this.nextToken.type !== TEOF && !(this.nextToken.type === TPAREN && this.nextToken.value === ')')) { - exprInstr.push(new Instruction(IENDSTATEMENT)); - } - if (this.nextToken.type !== TEOF) { - this.parseExpression(exprInstr); - } - instr.push(new Instruction(IEXPR, exprInstr)); - return true; -}; - -ParserState.prototype.parseArrayList = function (instr) { - var argCount = 0; - - while (!this.accept(TBRACKET, ']')) { - this.parseExpression(instr); - ++argCount; - while (this.accept(TCOMMA)) { - this.parseExpression(instr); - ++argCount; - } - } - - return argCount; -}; - -ParserState.prototype.parseConditionalExpression = function (instr) { - this.parseOrExpression(instr); - while (this.accept(TOP, '?')) { - var trueBranch = []; - var falseBranch = []; - this.parseConditionalExpression(trueBranch); - this.expect(TOP, ':'); - this.parseConditionalExpression(falseBranch); - instr.push(new Instruction(IEXPR, trueBranch)); - instr.push(new Instruction(IEXPR, falseBranch)); - instr.push(ternaryInstruction('?')); - } -}; - -ParserState.prototype.parseOrExpression = function (instr) { - this.parseAndExpression(instr); - while (this.accept(TOP, 'or')) { - var falseBranch = []; - this.parseAndExpression(falseBranch); - instr.push(new Instruction(IEXPR, falseBranch)); - instr.push(binaryInstruction('or')); - } -}; - -ParserState.prototype.parseAndExpression = function (instr) { - this.parseComparison(instr); - while (this.accept(TOP, 'and')) { - var trueBranch = []; - this.parseComparison(trueBranch); - instr.push(new Instruction(IEXPR, trueBranch)); - instr.push(binaryInstruction('and')); - } -}; - -var COMPARISON_OPERATORS = ['==', '!=', '<', '<=', '>=', '>', 'in']; - -ParserState.prototype.parseComparison = function (instr) { - this.parseAddSub(instr); - while (this.accept(TOP, COMPARISON_OPERATORS)) { - var op = this.current; - this.parseAddSub(instr); - instr.push(binaryInstruction(op.value)); - } -}; - -var ADD_SUB_OPERATORS = ['+', '-', '||']; - -ParserState.prototype.parseAddSub = function (instr) { - this.parseTerm(instr); - while (this.accept(TOP, ADD_SUB_OPERATORS)) { - var op = this.current; - this.parseTerm(instr); - instr.push(binaryInstruction(op.value)); - } -}; - -var TERM_OPERATORS = ['*', '/', '%']; - -ParserState.prototype.parseTerm = function (instr) { - this.parseFactor(instr); - while (this.accept(TOP, TERM_OPERATORS)) { - var op = this.current; - this.parseFactor(instr); - instr.push(binaryInstruction(op.value)); - } -}; - -ParserState.prototype.parseFactor = function (instr) { - var unaryOps = this.tokens.unaryOps; - function isPrefixOperator(token) { - return token.value in unaryOps; - } - - this.save(); - if (this.accept(TOP, isPrefixOperator)) { - if (this.current.value !== '-' && this.current.value !== '+') { - if (this.nextToken.type === TPAREN && this.nextToken.value === '(') { - this.restore(); - this.parseExponential(instr); - return; - } else if (this.nextToken.type === TSEMICOLON || this.nextToken.type === TCOMMA || this.nextToken.type === TEOF || (this.nextToken.type === TPAREN && this.nextToken.value === ')')) { - this.restore(); - this.parseAtom(instr); - return; - } - } - - var op = this.current; - this.parseFactor(instr); - instr.push(unaryInstruction(op.value)); - } else { - this.parseExponential(instr); - } -}; - -ParserState.prototype.parseExponential = function (instr) { - this.parsePostfixExpression(instr); - while (this.accept(TOP, '^')) { - this.parseFactor(instr); - instr.push(binaryInstruction('^')); - } -}; - -ParserState.prototype.parsePostfixExpression = function (instr) { - this.parseFunctionCall(instr); - while (this.accept(TOP, '!')) { - instr.push(unaryInstruction('!')); - } -}; - -ParserState.prototype.parseFunctionCall = function (instr) { - var unaryOps = this.tokens.unaryOps; - function isPrefixOperator(token) { - return token.value in unaryOps; - } - - if (this.accept(TOP, isPrefixOperator)) { - var op = this.current; - this.parseAtom(instr); - instr.push(unaryInstruction(op.value)); - } else { - this.parseMemberExpression(instr); - while (this.accept(TPAREN, '(')) { - if (this.accept(TPAREN, ')')) { - instr.push(new Instruction(IFUNCALL, 0)); - } else { - var argCount = this.parseArgumentList(instr); - instr.push(new Instruction(IFUNCALL, argCount)); - } - } - } -}; - -ParserState.prototype.parseArgumentList = function (instr) { - var argCount = 0; - - while (!this.accept(TPAREN, ')')) { - this.parseExpression(instr); - ++argCount; - while (this.accept(TCOMMA)) { - this.parseExpression(instr); - ++argCount; - } - } - - return argCount; -}; - -ParserState.prototype.parseMemberExpression = function (instr) { - this.parseAtom(instr); - while (this.accept(TOP, '.') || this.accept(TBRACKET, '[')) { - var op = this.current; - - if (op.value === '.') { - if (!this.allowMemberAccess) { - throw new Error(qsTranslate('error', 'Unexpected ".": member access is not permitted')); - } - - this.expect(TNAME); - instr.push(new Instruction(IMEMBER, this.current.value)); - } else if (op.value === '[') { - if (!this.tokens.isOperatorEnabled('[')) { - throw new Error(qsTranslate('error', 'Unexpected "[]": arrays are disabled.')); - } - - this.parseExpression(instr); - this.expect(TBRACKET, ']'); - instr.push(binaryInstruction('[')); - } else { - throw new Error(qsTranslate('error', 'Unexpected symbol: %1.').arg(op.value)); - } - } -}; - -function add(a, b) { - return Number(a) + Number(b); -} - -function sub(a, b) { - return a - b; -} - -function mul(a, b) { - return a * b; -} - -function div(a, b) { - return a / b; -} - -function mod(a, b) { - return a % b; -} - -function concat(a, b) { - if (Array.isArray(a) && Array.isArray(b)) { - return a.concat(b); - } - return '' + a + b; -} - -function equal(a, b) { - return a === b; -} - -function notEqual(a, b) { - return a !== b; -} - -function greaterThan(a, b) { - return a > b; -} - -function lessThan(a, b) { - return a < b; -} - -function greaterThanEqual(a, b) { - return a >= b; -} - -function lessThanEqual(a, b) { - return a <= b; -} - -function andOperator(a, b) { - return Boolean(a && b); -} - -function orOperator(a, b) { - return Boolean(a || b); -} - -function inOperator(a, b) { - return contains(b, a); -} - -function sinh(a) { - return ((Math.exp(a) - Math.exp(-a)) / 2); -} - -function cosh(a) { - return ((Math.exp(a) + Math.exp(-a)) / 2); -} - -function tanh(a) { - if (a === Infinity) return 1; - if (a === -Infinity) return -1; - return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a)); -} - -function asinh(a) { - if (a === -Infinity) return a; - return Math.log(a + Math.sqrt((a * a) + 1)); -} - -function acosh(a) { - return Math.log(a + Math.sqrt((a * a) - 1)); -} - -function atanh(a) { - return (Math.log((1 + a) / (1 - a)) / 2); -} - -function log10(a) { - return Math.log(a) * Math.LOG10E; -} - -function neg(a) { - return -a; -} - -function not(a) { - return !a; -} - -function trunc(a) { - return a < 0 ? Math.ceil(a) : Math.floor(a); -} - -function random(a) { - return Math.random() * (a || 1); -} - -function factorial(a) { // a! - return gamma(a + 1); -} - -function isInteger(value) { - return isFinite(value) && (value === Math.round(value)); -} - -var GAMMA_G = 4.7421875; -var GAMMA_P = [ - 0.99999999999999709182, - 57.156235665862923517, -59.597960355475491248, - 14.136097974741747174, -0.49191381609762019978, - 0.33994649984811888699e-4, - 0.46523628927048575665e-4, -0.98374475304879564677e-4, - 0.15808870322491248884e-3, -0.21026444172410488319e-3, - 0.21743961811521264320e-3, -0.16431810653676389022e-3, - 0.84418223983852743293e-4, -0.26190838401581408670e-4, - 0.36899182659531622704e-5 -]; - -// Gamma function from math.js -function gamma(n) { - var t, x; - - if (isInteger(n)) { - if (n <= 0) { - return isFinite(n) ? Infinity : NaN; - } - - if (n > 171) { - return Infinity; // Will overflow - } - - var value = n - 2; - var res = n - 1; - while (value > 1) { - res *= value; - value--; - } - - if (res === 0) { - res = 1; // 0! is per definition 1 - } - - return res; - } - - if (n < 0.5) { - return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n)); - } - - if (n >= 171.35) { - return Infinity; // will overflow - } - - if (n > 85.0) { // Extended Stirling Approx - var twoN = n * n; - var threeN = twoN * n; - var fourN = threeN * n; - var fiveN = fourN * n; - return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) * - (1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) - - (571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) + - (5246819 / (75246796800 * fiveN * n))); - } - - --n; - x = GAMMA_P[0]; - for (var i = 1; i < GAMMA_P.length; ++i) { - x += GAMMA_P[i] / (n + i); - } - - t = n + GAMMA_G + 0.5; - return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x; -} - -function stringOrArrayLength(s) { - if (Array.isArray(s)) { - return s.length; - } - return String(s).length; -} - -function hypot() { - var sum = 0; - var larg = 0; - for (var i = 0; i < arguments.length; i++) { - var arg = Math.abs(arguments[i]); - var div; - if (larg < arg) { - div = larg / arg; - sum = (sum * div * div) + 1; - larg = arg; - } else if (arg > 0) { - div = arg / larg; - sum += div * div; - } else { - sum += arg; - } - } - return larg === Infinity ? Infinity : larg * Math.sqrt(sum); -} - -function condition(cond, yep, nope) { - return cond ? yep : nope; -} - -/** -* Decimal adjustment of a number. -* From @escopecz. -* -* @param {Number} value The number. -* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base). -* @return {Number} The adjusted value. -*/ -function roundTo(value, exp) { - // If the exp is undefined or zero... - if (typeof exp === 'undefined' || +exp === 0) { - return Math.round(value); - } - value = +value; - exp = -(+exp); - // If the value is not a number or the exp is not an integer... - if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { - return NaN; - } - // Shift - value = value.toString().split('e'); - value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); - // Shift back - value = value.toString().split('e'); - return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); -} - -function setVar(name, value, variables) { - if (variables) variables[name] = value; - return value; -} - -function arrayIndex(array, index) { - return array[index | 0]; -} - -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')) - } -} - -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')) - } -} - -function arrayMap(f, a) { - if (typeof f !== 'function') { - throw new EvalError(qsTranslate('error', 'First argument to map is not a function.')); - } - if (!Array.isArray(a)) { - throw new EvalError(qsTranslate('error', 'Second argument to map is not an array.')); - } - return a.map(function (x, i) { - return f(x, i); - }); -} - -function arrayFold(f, init, a) { - if (typeof f !== 'function') { - throw new EvalError(qsTranslate('error', 'First argument to fold is not a function.')); - } - if (!Array.isArray(a)) { - throw new EvalError(qsTranslate('error', 'Second argument to fold is not an array.')); - } - return a.reduce(function (acc, x, i) { - return f(acc, x, i); - }, init); -} - -function arrayFilter(f, a) { - if (typeof f !== 'function') { - throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.')); - } - if (!Array.isArray(a)) { - throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.')); - } - return a.filter(function (x, i) { - return f(x, i); - }); -} - -function stringOrArrayIndexOf(target, s) { - if (!(Array.isArray(s) || typeof s === 'string')) { - throw new Error(qsTranslate('error', 'Second argument to indexOf is not a string or array.')); - } - - return s.indexOf(target); -} - -function arrayJoin(sep, a) { - if (!Array.isArray(a)) { - throw new Error(qsTranslate('error', 'Second argument to join is not an array.')); - } - - return a.join(sep); -} - -function sign(x) { - return ((x > 0) - (x < 0)) || +x; -} - -var ONE_THIRD = 1/3; -function cbrt(x) { - return x < 0 ? -Math.pow(-x, ONE_THIRD) : Math.pow(x, ONE_THIRD); -} - -function expm1(x) { - return Math.exp(x) - 1; -} - -function log1p(x) { - return Math.log(1 + x); -} - -function log2(x) { - return Math.log(x) / Math.LN2; -} - -class Parser { - constructor(options) { - this.options = options || {}; - this.unaryOps = { - sin: Math.sin, - cos: Math.cos, - tan: Math.tan, - asin: Math.asin, - acos: Math.acos, - atan: Math.atan, - sinh: Math.sinh || sinh, - cosh: Math.cosh || cosh, - tanh: Math.tanh || tanh, - asinh: Math.asinh || asinh, - acosh: Math.acosh || acosh, - atanh: Math.atanh || atanh, - sqrt: Math.sqrt, - cbrt: Math.cbrt || cbrt, - log: Math.log, - log2: Math.log2 || log2, - ln: Math.log, - lg: Math.log10 || log10, - log10: Math.log10 || log10, - expm1: Math.expm1 || expm1, - log1p: Math.log1p || log1p, - abs: Math.abs, - ceil: Math.ceil, - floor: Math.floor, - round: Math.round, - trunc: Math.trunc || trunc, - '-': neg, - '+': Number, - exp: Math.exp, - not: not, - length: stringOrArrayLength, - '!': factorial, - sign: Math.sign || sign - }; - - this.binaryOps = { - '+': add, - '-': sub, - '*': mul, - '/': div, - '%': mod, - '^': Math.pow, - '||': concat, - '==': equal, - '!=': notEqual, - '>': greaterThan, - '<': lessThan, - '>=': greaterThanEqual, - '<=': lessThanEqual, - and: andOperator, - or: orOperator, - 'in': inOperator, - '=': setVar, - '[': arrayIndex - }; - - this.ternaryOps = { - '?': condition - }; - - this.functions = { - random: random, - fac: factorial, - min: min, - max: max, - hypot: Math.hypot || hypot, - pyt: Math.hypot || hypot, // backward compat - pow: Math.pow, - atan2: Math.atan2, - 'if': condition, - gamma: gamma, - 'Γ': gamma, - roundTo: roundTo, - map: arrayMap, - fold: arrayFold, - filter: arrayFilter, - indexOf: stringOrArrayIndexOf, - 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 = {} - - } - - parse(expr) { - var instr = []; - var parserState = new ParserState( - this, - new TokenStream(this, expr), - { allowMemberAccess: this.options.allowMemberAccess } - ); - - parserState.parseExpression(instr); - parserState.expect(TEOF, QT_TRANSLATE_NOOP('error','EOF')); - - return new Expression(instr, this); - } - - evaluate(expr, variables) { - return this.parse(expr).evaluate(variables); - } -}; - - -var sharedParser = new Parser(); - -Parser.parse = function (expr) { - return sharedParser.parse(expr); -}; - -Parser.evaluate = function (expr, variables) { - return sharedParser.parse(expr).evaluate(variables); -}; - -var optionNameMap = { - '+': 'add', - '-': 'subtract', - '*': 'multiply', - '/': 'divide', - '%': 'remainder', - '^': 'power', - '!': 'factorial', - '<': 'comparison', - '>': 'comparison', - '<=': 'comparison', - '>=': 'comparison', - '==': 'comparison', - '!=': 'comparison', - '||': 'concatenate', - 'and': 'logical', - 'or': 'logical', - 'not': 'logical', - '?': 'conditional', - ':': 'conditional', - //'=': 'assignment', // Disable assignment - '[': 'array', - //'()=': 'fndef' // Diable function definition -}; - -function getOptionName(op) { - return optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op; -} - -Parser.prototype.isOperatorEnabled = function (op) { - var optionName = getOptionName(op); - var operators = this.options.operators || {}; - - return !(optionName in operators) || !!operators[optionName]; -}; - -/*! - Based on ndef.parser, by Raphael Graf - http://www.undefined.ch/mparser/index.html - - Ported to JavaScript and modified by Matthew Crumley (http://silentmatt.com/) - - Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu) - - You are free to use and modify this code in anyway you find useful. Please leave this comment in the code - to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, - but don't feel like you have to let me know or ask permission. -*/ - - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/position.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/position.js deleted file mode 100644 index c171124..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/position.js +++ /dev/null @@ -1,95 +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 . - */ - -.pragma library - -.import "../objects.js" as Objects -.import "../mathlib.js" as MathLib -.import "../math/latex.js" as Latex -.import "../utils.js" as Utils -.import "../objs/common.js" as Common -.import "common.js" as C - -class EditedPosition extends C.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 = ' '+Utils.escapeHTML(this.prevString)+' ' - this.nextHTML = ' '+Utils.escapeHTML(this.nextString)+' ' - } - - } - - 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(' ' + this.targetName + ' ') - .arg(this.prevHTML) - .arg(this.nextHTML) - } -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/common.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/common.js deleted file mode 100644 index 0858522..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/common.js +++ /dev/null @@ -1,99 +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 . - */ - -.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 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 - -} - -var currentVars = {} -var currentObjectsByName = {} // Mirror of currentObjectsByName in objects.js - -const parser = new ExprEval.Parser() - -parser.consts = Object.assign({}, parser.consts, evalVariables) - -/** - * 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 {callable} JS function to call.. - */ -function 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 = parser.parse(f).toJSFunction(variable, currentVars) - } else - throw EvalError(qsTranslate('usage', 'Usage: %1 or\n%2').arg(usage1).arg(usage2)) - return f -} - -// Function definition -parser.functions.integral = function(a, b, ...args) { - let usage1 = qsTranslate('usage', 'integral(, , )') - let usage2 = qsTranslate('usage', 'integral(, , , )') - let f = 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)) -} - -parser.functions.derivative = function(...args) { - let usage1 = qsTranslate('usage', 'derivative(, )') - let usage2 = qsTranslate('usage', 'derivative(, , )') - let x = args.pop() - let f = 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 -} - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/expression.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/expression.js deleted file mode 100644 index 4a57746..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/expression.js +++ /dev/null @@ -1,99 +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 . - */ - -.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 = Utils.exponentsToExpression(expr) - this.calc = C.parser.parse(this.expr).simplify() - this.cached = this.isConstant() - this.cachedValue = this.cached && this.allRequirementsFullfilled() ? this.calc.evaluate(C.currentObjectsByName) : null - 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 C.currentObjectsByName) - } - - undefinedVariables() { - return this.requiredObjects().filter(objName => !(objName in C.currentObjectsByName)) - } - - recache() { - if(this.cached) - this.cachedValue = this.calc.evaluate(C.currentObjectsByName) - } - - execute(x = 1) { - if(this.cached) { - if(this.cachedValue == null) - this.cachedValue = this.calc.evaluate(C.currentObjectsByName) - return this.cachedValue - } - C.currentVars = Object.assign({'x': x}, C.currentObjectsByName) - return this.calc.evaluate(C.currentVars) - } - - simplify(x) { - var expr = this.calc.substitute('x', x).simplify() - if(expr.evaluate() == 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) { - let str = Utils.makeExpressionReadable(this.calc.toString()) - if(str[0] != '-' && forceSign) str = '+' + str - return str - } -} - -function executeExpression(expr){ - return (new Expression(expr.toString())).execute() -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js deleted file mode 100644 index 50883b2..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/latex.js +++ /dev/null @@ -1,282 +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 . - */ - -.pragma library - -.import "../expr-eval.js" as ExprEval - - -/** - * true if latex has been enabled by the user, false otherwise. - */ -var enabled = false -/** - * LaTeX python backend QObject. - */ -var Renderer = null -/** - * Default window color used to render LaTeX formulas. - */ -var defaultColor = "black" - -/** - * 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": - 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 {bool} wrapIn$ - checks whether the escaped chars should be escaped - * @returns {string} - */ -function variable(vari, wrapIn$ = false) { - 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", "\\infty"] - 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} - */ -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(item.value == Infinity) { - nstack.push("\\infty") - } else 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]); -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objects.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objects.js deleted file mode 100644 index 90dd468..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objects.js +++ /dev/null @@ -1,96 +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 . - */ - -.pragma library - -.import "utils.js" as Utils -.import "math/common.js" as MathCommons -.import "parameters.js" as P - -var types = {} - -var currentObjects = {} -var currentObjectsByName = {} -MathCommons.currentObjectsByName = currentObjectsByName // Required for using objects in variables. - -function renameObject(oldName, newName) { - /** - * 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. - */ - let obj = currentObjectsByName[oldName] - delete currentObjectsByName[oldName] - currentObjectsByName[newName] = obj - obj.name = newName -} - -function deleteObject(objName) { - /** - * Deletes an object by its given name. - * @param {string} objName - Current name of the object. - */ - let obj = currentObjectsByName[objName] - currentObjects[obj.type].splice(currentObjects[obj.type].indexOf(obj),1) - obj.delete() - delete currentObjectsByName[objName] -} - -function getObjectsName(objType) { - /** - * 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. - * @return {array} List of names of the objects. - */ - if(objType == "ExecutableObject") { - // NOTE: QMLJS does not support flatMap. - // return getExecutableTypes().flatMap(elemType => currentObjects[elemType].map(obj => obj.name)) - let types = getExecutableTypes() - let elementNames = [''] - for(let elemType of types) - 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() { - /** - * Returns a list of all object types which are executable objects. - * @return {array} List of all object types which are executable objects. - */ - return Object.keys(currentObjects).filter(objType => types[objType].executable()) -} - -function createNewRegisteredObject(objType, args=[]) { - /** - * 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 {[objType]} Newly created object. - */ - 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) - currentObjectsByName[newobj.name] = newobj - return newobj -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/gainbode.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/gainbode.js deleted file mode 100644 index 9604656..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/gainbode.js +++ /dev/null @@ -1,146 +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 . - */ - -.pragma library - -.import "common.js" as Common -.import "function.js" as F -.import "../objects.js" as Objects -.import "../utils.js" as Utils -.import "../mathlib.js" as MathLib -.import "../historylib.js" as HistoryLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex - - -class GainBode extends Common.ExecutableObject { - static type(){return 'Gain Bode'} - static displayType(){return qsTr('Bode Magnitude')} - static displayTypeMultiple(){return qsTr('Bode Magnitudes')} - static properties() {return { - [QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'), - [QT_TRANSLATE_NOOP('prop','pass')]: P.Enum.BodePass, - [QT_TRANSLATE_NOOP('prop','gain')]: new P.Expression(), - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, - [QT_TRANSLATE_NOOP('prop','labelX')]: 'number', - [QT_TRANSLATE_NOOP('prop','omGraduation')]: 'boolean' - }} - - constructor(name = null, visible = true, color = null, labelContent = 'name + value', - om_0 = '', pass = 'high', gain = '20', labelPosition = 'above', labelX = 1, omGraduation = false) { - if(name == null) name = Common.getNewName('G') - if(name == 'G') name = 'G₀' // G is reserved for sum of BODE magnitudes (Somme gains Bode). - super(name, visible, color, labelContent) - if(typeof om_0 == "string") { - // Point name or create one - om_0 = Objects.currentObjectsByName[om_0] - if(om_0 == null) { - // Create new point - om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), true, this.color, 'name']) - HistoryLib.history.addToHistory(new HistoryLib.CreateNewObject(om_0.name, 'Point', om_0.export())) - om_0.update() - labelPosition = 'below' - } - om_0.requiredBy.push(this) - } - this.om_0 = om_0 - this.pass = pass - if(typeof gain == 'number' || typeof gain == 'string') gain = new MathLib.Expression(gain.toString()) - this.gain = gain - this.labelPosition = labelPosition - this.labelX = labelX - this.omGraduation = omGraduation - } - - getReadableString() { - let pass = this.pass == "low" ? qsTr("low-pass") : qsTr("high-pass"); - return `${this.name}: ${pass}; ${this.om_0.name} = ${this.om_0.x}\n ${' '.repeat(this.name.length)}${this.gain.toString(true)} dB/dec` - } - - getLatexString() { - let pass = this.pass == "low" ? qsTr("low-pass") : qsTr("high-pass"); - return `\\mathrm{${Latex.variable(this.name)}:}\\begin{array}{l} - \\textsf{${pass}};${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup} \\\\ - ${this.gain.latexMarkup}\\textsf{ dB/dec} - \\end{array}` - } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, - this.om_0.name, this.pass.toString(), this.gain.toEditableString(), this.labelPosition, this.labelX, this.omGraduation] - } - - execute(x=1) { - if(typeof x == 'string') x = MathLib.executeExpression(x) - if((this.pass == 'high' && x < this.om_0.x) || (this.pass == 'low' && x > this.om_0.x)) { - var dbfn = new MathLib.Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`) - return dbfn.execute(x) - } else { - return this.om_0.y.execute() - } - } - - simplify(x = 1) { - var xval = x - if(typeof x == 'string') xval = MathLib.executeExpression(x) - if((this.pass == 'high' && xval < this.om_0.x) || (this.pass == 'low' && xval > this.om_0.x)) { - var dbfn = new MathLib.Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`) - return dbfn.simplify(x) - } else { - return this.om_0.y.toString() - } - } - - canExecute(x = 1) { - return true - } - - draw(canvas, ctx) { - var base = [canvas.x2px(this.om_0.x), canvas.y2px(this.om_0.y)] - var dbfn = new MathLib.Expression(`${this.gain.execute()}*(log10(x)-log10(${this.om_0.x}))+${this.om_0.y}`) - var inDrawDom = new MathLib.EmptySet() - - if(this.pass == 'high') { - // High pass, linear line from begining, then constant to the end. - canvas.drawLine(ctx, base[0], base[1], canvas.canvasSize.width, base[1]) - inDrawDom = MathLib.parseDomain(`]-inf;${this.om_0.x}[`) - } else { - // Low pass, constant from the beginning, linear line to the end. - canvas.drawLine(ctx, base[0], base[1], 0, base[1]) - inDrawDom = MathLib.parseDomain(`]${this.om_0.x};+inf[`) - } - F.Function.drawFunction(canvas, ctx, dbfn, inDrawDom, MathLib.Domain.R) - // Dashed line representing break in function - var xpos = canvas.x2px(this.om_0.x.execute()) - var dashPxSize = 10 - for(var i = 0; i < canvas.canvasSize.height && this.omGraduation; i += dashPxSize*2) - canvas.drawLine(ctx, xpos, i, xpos, i+dashPxSize) - - // Label - this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) - } - - update() { - super.update() - if(Objects.currentObjects['Somme gains Bode'] != undefined && Objects.currentObjects['Somme gains Bode'].length > 0) { - Objects.currentObjects['Somme gains Bode'][0].recalculateCache() - } else { - Objects.createNewRegisteredObject('Somme gains Bode') - } - } -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/phasebode.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/phasebode.js deleted file mode 100644 index edf9d68..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/phasebode.js +++ /dev/null @@ -1,128 +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 . - */ - -.pragma library - -.import "common.js" as Common -.import "../objects.js" as Objects -.import "../mathlib.js" as MathLib -.import "../historylib.js" as HistoryLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex - - -class PhaseBode extends Common.ExecutableObject { - static type(){return 'Phase Bode'} - static displayType(){return qsTr('Bode Phase')} - static displayTypeMultiple(){return qsTr('Bode Phases')} - static properties() {return { - [QT_TRANSLATE_NOOP('prop','om_0')]: new P.ObjectType('Point'), - [QT_TRANSLATE_NOOP('prop','phase')]: new P.Expression(), - [QT_TRANSLATE_NOOP('prop','unit')]: new P.Enum('°', 'deg', 'rad'), - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, - [QT_TRANSLATE_NOOP('prop','labelX')]: 'number' - }} - - constructor(name = null, visible = true, color = null, labelContent = 'name + value', - om_0 = '', phase = 90, unit = '°', labelPosition = 'above', labelX = 1) { - if(name == null) name = Common.getNewName('φ') - if(name == 'φ') name = 'φ₀' // φ is reserved for sum of BODE phases (Somme phases Bode). - super(name, visible, color, labelContent) - if(typeof phase == 'number' || typeof phase == 'string') phase = new MathLib.Expression(phase.toString()) - this.phase = phase - if(typeof om_0 == "string") { - // Point name or create one - om_0 = Objects.currentObjectsByName[om_0] - if(om_0 == null) { - // Create new point - om_0 = Objects.createNewRegisteredObject('Point', [Common.getNewName('ω'), this.color, 'name']) - om_0.labelPosition = this.phase.execute() >= 0 ? 'above' : 'below' - HistoryLib.history.addToHistory(new HistoryLib.CreateNewObject(om_0.name, 'Point', om_0.export())) - labelPosition = 'below' - } - om_0.requiredBy.push(this) - } - this.om_0 = om_0 - this.unit = unit - this.labelPosition = labelPosition - this.labelX = labelX - } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, - this.om_0.name, this.phase.toEditableString(), this.unit, this.labelPosition, this.labelX] - } - - getReadableString() { - return `${this.name}: ${this.phase.toString(true)}${this.unit} at ${this.om_0.name} = ${this.om_0.x}` - } - - getLatexString() { - return `${Latex.variable(this.name)}: ${this.phase.latexMarkup}\\textsf{${this.unit} at }${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup}` - } - - execute(x=1) { - if(typeof x == 'string') x = MathLib.executeExpression(x) - if(x < this.om_0.x) { - return this.om_0.y.execute() - } else { - return this.om_0.y.execute() + this.phase.execute() - } - } - - simplify(x = 1) { - var xval = x - if(typeof x == 'string') xval = MathLib.executeExpression(x) - if(xval < this.om_0.x) { - return this.om_0.y.toString() - } else { - var newExp = this.om_0.y.toEditableString() + ' + ' + this.phase.toEditableString() - return (new MathLib.Expression(newExp)).toString() - } - } - - canExecute(x = 1) { - return true - } - - draw(canvas, ctx) { - var baseX = canvas.x2px(this.om_0.x.execute()) - var omy = this.om_0.y.execute() - var augmt = this.phase.execute() - var baseY = canvas.y2px(omy) - var augmtY = canvas.y2px(omy+augmt) - // Before change line. - canvas.drawLine(ctx, 0, baseY, Math.min(baseX, canvas.canvasSize.height), baseY) - // Transition line. - canvas.drawLine(ctx, baseX, baseY, baseX, augmtY) - // After change line - canvas.drawLine(ctx, Math.max(0, baseX), augmtY, canvas.canvasSize.width, augmtY) - - // Label - this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) - } - - update() { - if(Objects.currentObjects['Somme phases Bode'] != undefined && Objects.currentObjects['Somme phases Bode'].length > 0) { - Objects.currentObjects['Somme phases Bode'][0].recalculateCache() - } else { - Objects.createNewRegisteredObject('Somme phases Bode') - } - } -} - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/point.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/point.js deleted file mode 100644 index effdee1..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/point.js +++ /dev/null @@ -1,83 +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 . - */ - -.pragma library - -.import "common.js" as Common -.import "../mathlib.js" as MathLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex - - -class Point extends Common.DrawableObject { - static type(){return 'Point'} - static displayType(){return qsTr('Point')} - static displayTypeMultiple(){return qsTr('Points')} - - static properties() {return { - [QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(), - [QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(), - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, - [QT_TRANSLATE_NOOP('prop','pointStyle')]: new P.Enum('●', '✕', '+') - }} - - constructor(name = null, visible = true, color = null, labelContent = 'name + value', - x = 1, y = 0, labelPosition = 'above', pointStyle = '●') { - if(name == null) name = Common.getNewName('ABCDEFJKLMNOPQRSTUVW') - super(name, visible, color, labelContent) - if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString()) - this.x = x - if(typeof y == 'number' || typeof y == 'string') y = new MathLib.Expression(y.toString()) - this.y = y - this.labelPosition = labelPosition - this.pointStyle = pointStyle - } - - getReadableString() { - return `${this.name} = (${this.x}, ${this.y})` - } - - getLatexString() { - return `${Latex.variable(this.name)} = \\left(${this.x.latexMarkup}, ${this.y.latexMarkup}\\right)` - } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, this.x.toEditableString(), this.y.toEditableString(), this.labelPosition, this.pointStyle] - } - - draw(canvas, ctx) { - var [canvasX, canvasY] = [canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute())] - var pointSize = 8+(ctx.lineWidth*2) - switch(this.pointStyle) { - case '●': - ctx.beginPath(); - ctx.ellipse(canvasX-pointSize/2, canvasY-pointSize/2, pointSize, pointSize) - ctx.fill(); - break; - case '✕': - canvas.drawLine(ctx, canvasX-pointSize/2, canvasY-pointSize/2, canvasX+pointSize/2, canvasY+pointSize/2) - canvas.drawLine(ctx, canvasX-pointSize/2, canvasY+pointSize/2, canvasX+pointSize/2, canvasY-pointSize/2) - break; - case '+': - ctx.fillRect(canvasX-pointSize/2, canvasY-1, pointSize, 2) - ctx.fillRect(canvasX-1, canvasY-pointSize/2, 2, pointSize) - break; - } - this.drawLabel(canvas, ctx, this.labelPosition, canvasX, canvasY) - } -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/repartition.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/repartition.js deleted file mode 100644 index e872876..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/repartition.js +++ /dev/null @@ -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 . - */ - -.pragma library - -.import "common.js" as Common -.import "../parameters.js" as P -.import "../math/latex.js" as Latex - - -class RepartitionFunction extends Common.ExecutableObject { - static type(){return 'Repartition'} - static displayType(){return qsTr('Repartition')} - static displayTypeMultiple(){return qsTr('Repartition functions')} - static properties() {return { - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, - [QT_TRANSLATE_NOOP('prop','labelX')]: 'number', - 'comment1': QT_TRANSLATE_NOOP( - 'comment', - 'Note: Specify the probability for each value.' - ), - [QT_TRANSLATE_NOOP('prop','probabilities')]: new P.Dictionary('string', 'float', /^-?[\d.,]+$/, /^-?[\d\.,]+$/, 'P({name_} = ', ') = '), - }} - - constructor(name = null, visible = true, color = null, labelContent = 'name + value', - beginIncluded = true, drawLineEnds = true, probabilities = {'0': '0'}, labelPosition = 'above', labelX = 1) { - if(name == null) name = Common.getNewName('XYZUVW', "F_") - super(name, visible, color, labelContent) - this.beginIncluded = beginIncluded - this.drawLineEnds = drawLineEnds - this.probabilities = probabilities - this.labelPosition = labelPosition - this.labelX = labelX - this.update() - } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, - this.beginIncluded, this.drawLineEnds, this.probabilities, this.labelPosition, this.labelX] - } - - - getReadableString() { - let keys = Object.keys(this.probabilities).sort((a,b) => a-b); - let varname = this.name.substring(this.name.indexOf("_")+1) - return `${this.name}(x) = P(${varname} ≤ x)\n` + keys.map(idx => `P(${varname}=${idx})=${this.probabilities[idx]}`).join("; ") - } - - getLatexString() { - let keys = Object.keys(this.probabilities).sort((a,b) => a-b); - let funcName = Latex.variable(this.name) - let varName = Latex.variable(this.name.substring(this.name.indexOf("_")+1)) - return `\\begin{array}{l}{${funcName}}(x) = P(${varName} \\le x)\\\\` + keys.map(idx => `P(${varName}=${idx})=${this.probabilities[idx]}`).join("; ") + '\\end{array}' - } - - execute(x = 1) { - var ret = 0; - Object.keys(this.probabilities).sort((a,b) => a-b).forEach(idx => { - if(x >= idx) ret += parseFloat(this.probabilities[idx].replace(/,/g, '.')) - }) - return ret - } - - canExecute(x = 1) {return true} - // Simplify returns the simplified string of the expression. - simplify(x = 1) { - return this.execute(x) - } - - getLabel() { - switch(this.labelContent) { - case 'name': - return `${this.name}(x)` - case 'name + value': - return this.getReadableString() - case 'null': - return '' - } - } - - draw(canvas, ctx) { - var currentY = 0; - var keys = Object.keys(this.probabilities).map(idx => parseInt(idx)).sort((a,b) => a-b) - if(canvas.isVisible(keys[0],this.probabilities[keys[0]].replace(/,/g, '.'))) { - canvas.drawLine(ctx, - 0, - canvas.y2px(0), - canvas.x2px(keys[0]), - canvas.y2px(0) - ) - if(canvas.isVisible(keys[0],0)) { - ctx.beginPath(); - ctx.arc(canvas.x2px(keys[0])+4,canvas.y2px(0), 4, Math.PI / 2, 3 * Math.PI / 2); - ctx.stroke(); - } - } - for(var i = 0; i < keys.length-1; i++) { - var idx = keys[i]; - currentY += parseFloat(this.probabilities[idx].replace(/,/g, '.')); - if(canvas.isVisible(idx,currentY) || canvas.isVisible(keys[i+1],currentY)) { - canvas.drawLine(ctx, - Math.max(0,canvas.x2px(idx)), - canvas.y2px(currentY), - Math.min(canvas.canvasSize.width,canvas.x2px(keys[i+1])), - canvas.y2px(currentY) - ) - if(canvas.isVisible(idx,currentY)) { - ctx.beginPath(); - ctx.arc(canvas.x2px(idx),canvas.y2px(currentY), 4, 0, 2 * Math.PI); - ctx.fill(); - } - if(canvas.isVisible(keys[i+1],currentY)) { - ctx.beginPath(); - ctx.arc(canvas.x2px(keys[i+1])+4,canvas.y2px(currentY), 4, Math.PI / 2, 3 * Math.PI / 2); - ctx.stroke(); - } - } - } - if(canvas.isVisible(keys[keys.length-1],currentY+parseFloat(this.probabilities[keys[keys.length-1]]))) { - canvas.drawLine(ctx, - Math.max(0,canvas.x2px(keys[keys.length-1])), - canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))), - canvas.canvasSize.width, - canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))) - ) - ctx.beginPath(); - ctx.arc( - canvas.x2px(keys[keys.length-1]), - canvas.y2px(currentY+parseFloat(this.probabilities[keys[keys.length-1]].replace(/,/g, '.'))), - 4, 0, 2 * Math.PI); - ctx.fill(); - } - - // Label - this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) - } -} - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sequence.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sequence.js deleted file mode 100644 index c08aac8..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sequence.js +++ /dev/null @@ -1,131 +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 . - */ - -.pragma library - -.import "common.js" as Common -.import "function.js" as F -.import "../mathlib.js" as MathLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex - - -class Sequence extends Common.ExecutableObject { - static type(){return 'Sequence'} - static displayType(){return qsTr('Sequence')} - static displayTypeMultiple(){return qsTr('Sequences')} - - static properties() {return { - [QT_TRANSLATE_NOOP('prop','drawPoints')]: 'boolean', - [QT_TRANSLATE_NOOP('prop','drawDashedLines')]: 'boolean', - [QT_TRANSLATE_NOOP('prop','defaultExpression')]: new P.Dictionary('string', 'int', /^.+$/, /^\d+$/, '{name}[n+', '] = ', true), - 'comment1': QT_TRANSLATE_NOOP( - 'comment', - 'Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁...' - ), - [QT_TRANSLATE_NOOP('prop','baseValues')]: new P.Dictionary('string', 'int', /^.+$/, /^\d+$/, '{name}[', '] = '), - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, - [QT_TRANSLATE_NOOP('prop','labelX')]: 'number', - }} - - constructor(name = null, visible = true, color = null, labelContent = 'name + value', - drawPoints = true, drawDashedLines = true, defaultExp = {1: "n"}, - baseValues = {0: 0}, labelPosition = 'above', labelX = 1) { - if(name == null) name = Common.getNewName('uvwPSUVWabcde') - super(name, visible, color, labelContent) - this.drawPoints = drawPoints - this.drawDashedLines = drawDashedLines - this.defaultExpression = defaultExp - this.baseValues = baseValues - this.labelPosition = labelPosition - this.labelX = labelX - this.update() - } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, - this.drawPoints, this.drawDashedLines, this.defaultExpression, this.baseValues, this.labelPosition, this.labelX] - } - - update() { - super.update() - if( - this.sequence == null || this.baseValues != this.sequence.baseValues || - this.sequence.name != this.name || - this.sequence.expr != Object.values(this.defaultExpression)[0] || - this.sequence.valuePlus != Object.keys(this.defaultExpression)[0] - ) - this.sequence = new MathLib.Sequence( - this.name, this.baseValues, - Object.keys(this.defaultExpression)[0], - Object.values(this.defaultExpression)[0] - ) - } - - getReadableString() { - return this.sequence.toString() - } - - getLatexString() { - return this.sequence.toLatexString() - } - - execute(x = 1) { - if(x % 1 == 0) - return this.sequence.execute(x) - return null - } - canExecute(x = 1) {return x%1 == 0} - - // Simplify returns the simplified string of the expression. - simplify(x = 1) { - if(x % 1 == 0) - return this.sequence.simplify(x) - return null - } - - getLabel() { - switch(this.labelContent) { - case 'name': - return `(${this.name}ₙ)` - case 'name + value': - return this.getReadableString() - case 'null': - return '' - } - } - - getLatexLabel() { - switch(this.labelContent) { - case 'name': - return `(${Latex.variable(this.name)}_n)` - case 'name + value': - return this.getLatexString() - case 'null': - return '' - } - } - - draw(canvas, ctx) { - F.Function.drawFunction(canvas, ctx, this.sequence, canvas.logscalex ? MathLib.Domain.NE : MathLib.Domain.N, MathLib.Domain.R, this.drawPoints, this.drawDashedLines) - - // Label - this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) - } -} - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sommegainsbode.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sommegainsbode.js deleted file mode 100644 index f8b3fee..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sommegainsbode.js +++ /dev/null @@ -1,145 +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 . - */ - -.pragma library - -.import "common.js" as Common -.import "function.js" as F -.import "../objects.js" as Objects -.import "../mathlib.js" as MathLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex - - -class SommeGainsBode extends Common.ExecutableObject { - static type(){return 'Somme gains Bode'} - static displayType(){return qsTr('Bode Magnitudes Sum')} - static displayTypeMultiple(){return qsTr('Bode Magnitudes Sum')} - static createable() {return false} - static properties() {return { - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, - [QT_TRANSLATE_NOOP('prop','labelX')]: 'number', - }} - - constructor(name = null, visible = true, color = null, labelContent = 'name + value', - labelPosition = 'above', labelX = 1) { - if(name == null) name = 'G' - super(name, visible, color, labelContent) - this.labelPosition = labelPosition - this.labelX = labelX - this.recalculateCache() - } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, this.labelPosition, this.labelX] - } - - getReadableString() { - return `${this.name} = ${Objects.getObjectsName('Gain Bode').join(' + ')}` - } - - getLatexString() { - return `${Latex.variable(this.name)} = ${Objects.getObjectsName('Gain Bode').map(name => Latex.variable(name)).join(' + ')}` - } - - execute(x = 0) { - for(var [dbfn, inDrawDom] of this.cachedParts) { - if(inDrawDom.includes(x)) { - return dbfn.execute(x) - } - } - return null - } - - canExecute(x = 1) { - return true // Should always be true - } - - simplify(x = 1) { - for(var [dbfn, inDrawDom] of this.cachedParts) { - if(inDrawDom.includes(x)) { - return dbfn.simplify(x) - } - } - return '' - } - - recalculateCache() { - this.cachedParts = [] - // Calculating this is fairly resource expansive so it's cached. - if(Objects.currentObjects['Gain Bode'] != undefined) { - console.log('Recalculating cache gain') - // Minimum to draw (can be expended if needed, just not infinite or it'll cause issues. - var drawMin = 0.001 - - var baseY = 0 - var om0xGains = {1000000000: 0} // To draw the last part - var om0xPass = {1000000000: 'high'} // To draw the last part - Objects.currentObjects['Gain Bode'].forEach(function(gainObj) { // Sorting by their om_0 position. - var om0x = gainObj.om_0.x.execute() - if(om0xGains[om0x] == undefined) { - om0xGains[om0x] = gainObj.gain.execute() - om0xPass[om0x] = gainObj.pass == 'high' - } else { - om0xGains[om0x+0.001] = gainObj.gain.execute() - om0xPass[om0x+0.001] = gainObj.pass == 'high' - } - baseY += gainObj.execute(drawMin) - }) - // Sorting the om_0x - var om0xList = Object.keys(om0xGains).map(x => parseFloat(x)) // THEY WERE CONVERTED TO STRINGS... - om0xList.sort((a,b) => a - b) - // Adding the total gains. - var gainsBeforeP = [] - var gainsAfterP = [] - var gainTotal = 0 - for(var om0x of om0xList){ - if(om0xPass[om0x]) { // High-pass - gainsBeforeP.push(om0xGains[om0x]) - gainsAfterP.push(0) - gainTotal += om0xGains[om0x] // Gain at first - } else { - gainsBeforeP.push(0) - gainsAfterP.push(om0xGains[om0x]) - } - } - // Calculating parts - var previousPallier = drawMin - for(var pallier = 0; pallier < om0xList.length; pallier++) { - var dbfn = new MathLib.Expression(`${gainTotal}*(ln(x)-ln(${previousPallier}))/ln(10)+${baseY}`) - var inDrawDom = MathLib.parseDomain(`]${previousPallier};${om0xList[pallier]}]`) - this.cachedParts.push([dbfn, inDrawDom]) - previousPallier = om0xList[pallier] - baseY = dbfn.execute(om0xList[pallier]) - gainTotal += gainsAfterP[pallier] - gainsBeforeP[pallier] - } - } - } - - draw(canvas, ctx) { - if(this.cachedParts.length > 0) { - for(var [dbfn, inDrawDom] of this.cachedParts) { - F.Function.drawFunction(canvas, ctx, dbfn, inDrawDom, MathLib.Domain.R) - if(inDrawDom.includes(this.labelX)) { - // Label - this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) - } - } - } - } -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sommephasesbode.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sommephasesbode.js deleted file mode 100644 index eb1c3e0..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/sommephasesbode.js +++ /dev/null @@ -1,129 +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 . - */ - -.pragma library - -.import "common.js" as Common -.import "../objects.js" as Objects -.import "../mathlib.js" as MathLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex - - -class SommePhasesBode extends Common.ExecutableObject { - static type(){return 'Somme phases Bode'} - static displayType(){return qsTr('Bode Phases Sum')} - static displayTypeMultiple(){return qsTr('Bode Phases Sum')} - static createable() {return false} - static properties() {return { - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, - [QT_TRANSLATE_NOOP('prop','labelX')]: 'number', - }} - - constructor(name = null, visible = true, color = null, labelContent = 'name + value', - labelPosition = 'above', labelX = 1) { - if(name == null) name = 'φ' - super(name, visible, color, labelContent) - this.labelPosition = labelPosition - this.labelX = labelX - this.recalculateCache() - } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, this.labelPosition, this.labelX] - } - - getReadableString() { - return `${this.name} = ${Objects.getObjectsName('Phase Bode').join(' + ')}` - } - - getLatexString() { - return `${Latex.variable(this.name)} = ${Objects.getObjectsName('Phase Bode').map(name => Latex.variable(name)).join(' + ')}` - } - - execute(x=1) { - if(typeof x == 'string') x = MathLib.executeExpression(x) - for(var i = 0; i < this.om0xList.length-1; i++) { - if(x >= this.om0xList[i] && x < this.om0xList[i+1]) return this.phasesList[i] - } - } - - simplify(x = 1) { - var xval = x - if(typeof x == 'string') xval = MathLib.executeExpression(x) - for(var i = 0; i < this.om0xList.length-1; i++) { - if(xval >= this.om0xList[i] && xval < this.om0xList[i+1]) { - return (new MathLib.Expression(this.phasesExprList[i])).simplify() - } - } - return '0' - } - - canExecute(x = 1) { - return true - } - - recalculateCache() { - // Minimum to draw (can be expended if needed, just not infinite or it'll cause issues. - var drawMin = 0.001 - var drawMax = 100000 - this.om0xList = [drawMin, drawMax] - this.phasesList = [0] - this.phasesExprList = ['0'] - var phasesDict = {} - var phasesExprDict = {} - phasesDict[drawMax] = 0 - - if(Objects.currentObjects['Phase Bode'] != undefined) { - console.log('Recalculating cache phase') - for(var obj of Objects.currentObjects['Phase Bode']) { - this.om0xList.push(obj.om_0.x.execute()) - if(phasesDict[obj.om_0.x.execute()] == undefined) { - phasesDict[obj.om_0.x.execute()] = obj.phase.execute() - phasesExprDict[obj.om_0.x.execute()] = obj.phase.toEditableString() - } else { - phasesDict[obj.om_0.x.execute()] += obj.phase.execute() - phasesExprDict[obj.om_0.x.execute()] += '+' + obj.phase.toEditableString() - } - this.phasesList[0] += obj.om_0.y.execute() - this.phasesExprList[0] += '+' + obj.om_0.y.toEditableString() - } - this.om0xList.sort((a,b) => a - b) - var totalAdded = this.phasesList[0] - for(var i = 1; i < this.om0xList.length; i++) { - this.phasesList[i] = this.phasesList[i-1] + phasesDict[this.om0xList[i]] - this.phasesExprList[i] = this.phasesExprList[i-1] + '+' + phasesDict[this.om0xList[i]] - } - } - } - - draw(canvas, ctx) { - for(var i = 0; i < this.om0xList.length-1; i++) { - var om0xBegin = canvas.x2px(this.om0xList[i]) - var om0xEnd = canvas.x2px(this.om0xList[i+1]) - var baseY = canvas.y2px(this.phasesList[i]) - var nextY = canvas.y2px(this.phasesList[i+1]) - canvas.drawLine(ctx, om0xBegin, baseY, om0xEnd, baseY) - canvas.drawLine(ctx, om0xEnd, baseY, om0xEnd, nextY) - } - - // Label - this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) - } -} - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/text.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/text.js deleted file mode 100644 index b513ea2..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/text.js +++ /dev/null @@ -1,100 +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 . - */ - -.pragma library - -.import "common.js" as Common -.import "../mathlib.js" as MathLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex - - - -class Text extends Common.DrawableObject { - static type(){return 'Text'} - static displayType(){return qsTr('Text')} - static displayTypeMultiple(){return qsTr('Texts')} - static properties() {return { - [QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(), - [QT_TRANSLATE_NOOP('prop','y')]: new P.Expression(), - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Positioning, - [QT_TRANSLATE_NOOP('prop','text')]: 'string', - 'comment1': QT_TRANSLATE_NOOP( - 'comment', - 'If you have latex enabled, you can use use latex markup in between $$ to create equations.' - ), - [QT_TRANSLATE_NOOP('prop','disableLatex')]: 'boolean' - }} - - constructor(name = null, visible = true, color = null, labelContent = 'null', - x = 1, y = 0, labelPosition = 'center', text = 'New text', disableLatex = false) { - if(name == null) name = Common.getNewName('t') - super(name, visible, color, labelContent) - if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString()) - this.x = x - if(typeof y == 'number' || typeof y == 'string') y = new MathLib.Expression(y.toString()) - this.y = y - this.labelPosition = labelPosition - this.text = text - this.disableLatex = disableLatex - } - - getReadableString() { - return `${this.name} = "${this.text}"` - } - - latexMarkupText() { - // Check whether the text contains latex escaped elements. - let txt = [] - this.text.split('$$').forEach(function(t) { txt = txt.concat(Latex.variable(t, true).replace(/\$\$/g, '').split('$')) }) - let newTxt = txt[0] - let i - // Split between normal text and latex escaped. - for(i = 0; i < txt.length-1; i++) - if(i & 0x01) // Every odd number - newTxt += '\\textsf{'+Latex.variable(txt[i+1]) - else - newTxt += '}'+txt[i+1] - // Finished by a } - if(i & 0x01) - newTxt += "{" - return newTxt - } - - getLatexString() { - return `${Latex.variable(this.name)} = "\\textsf{${this.latexMarkupText()}}"` - } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, this.x.toEditableString(), this.y.toEditableString(), this.labelPosition, this.text, this.disableLatex] - } - - getLabel() { - return this.text - } - - getLatexLabel() { - return `\\textsf{${this.latexMarkupText()}}` - } - - draw(canvas, ctx) { - let yOffset = this.disableLatex ? canvas.textsize-4 : 0 - this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute())+yOffset, this.disableLatex) - } -} - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parameters.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parameters.js deleted file mode 100644 index 2ef194a..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parameters.js +++ /dev/null @@ -1,128 +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 . - */ - -class Expression { - constructor(...variables) { - this.type = 'Expression' - this.variables = variables - } - - toString() { - return this.variables.length == 0 ? 'Number' : `Expression(${this.variables.join(', ')})` - } -} - -class Enum { - constructor(...values) { - this.type = 'Enum' - this.values = values - this.translatedValues = values.map(x => qsTr(x)) - } - - toString() { - return this.type - } -} - -class ObjectType { - constructor(objType) { - this.type = 'ObjectType' - this.objType = objType - } - - toString() { - return this.objType - } -} - -class List { - constructor(type, format = /^.+$/, label = '', forbidAdding = false) { - // type can be string, int and double. - this.type = 'List' - this.valueType = type - this.format = format - this.label = label - this.forbidAdding = forbidAdding - } - - toString() { - return this.objType - } -} - -class Dictionary { - constructor(type, keyType = 'string', format = /^.+$/, keyFormat = /^.+$/, preKeyLabel = '', postKeyLabel = ': ', forbidAdding = false) { - // type & keyType can be string, int and double. - this.type = 'Dict' - this.valueType = type - this.keyType = keyType - this.format = format - this.keyFormat = keyFormat - this.preKeyLabel = preKeyLabel - this.postKeyLabel = postKeyLabel - this.forbidAdding = forbidAdding - } - - toString() { - return 'Dictionary' - } -} - -// Common parameters for Enums - -Enum.Position = new Enum( - QT_TR_NOOP('above'), - QT_TR_NOOP('below'), - QT_TR_NOOP('left'), - QT_TR_NOOP('right'), - QT_TR_NOOP('above-left'), - QT_TR_NOOP('above-right'), - QT_TR_NOOP('below-left'), - QT_TR_NOOP('below-right') -) - -Enum.Positioning = new Enum( - QT_TR_NOOP('center'), - QT_TR_NOOP('top'), - QT_TR_NOOP('bottom'), - QT_TR_NOOP('left'), - QT_TR_NOOP('right'), - QT_TR_NOOP('top-left'), - QT_TR_NOOP('top-right'), - QT_TR_NOOP('bottom-left'), - QT_TR_NOOP('bottom-right') -) - -Enum.FunctionDisplayType = new Enum( - QT_TR_NOOP('application'), - QT_TR_NOOP('function') -) - -Enum.BodePass = new Enum( - QT_TR_NOOP('high'), - QT_TR_NOOP('low') -) - - -Enum.XCursorValuePosition = new Enum( - QT_TR_NOOP('Next to target'), - QT_TR_NOOP('With label'), - QT_TR_NOOP('Hidden') -) - - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/polyfill.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/polyfill.js deleted file mode 100644 index df880c5..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/polyfill.js +++ /dev/null @@ -1,136 +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 . - */ - -// Contains polyfill math functions used for reference. - -.pragma library - -function factorial(x) { - if (x < 0) // Integrating by less than 0 - if(isFinite(n)) - return Infinity - else - throw new EvalError("Cannot calculate the factorial of -∞.") - - return gamma(x+1) -} - -let GAMMA_G = 4.7421875 -let GAMMA_P = [ - 0.99999999999999709182, - 57.156235665862923517, -59.597960355475491248, - 14.136097974741747174, -0.49191381609762019978, - 0.33994649984811888699e-4, - 0.46523628927048575665e-4, -0.98374475304879564677e-4, - 0.15808870322491248884e-3, -0.21026444172410488319e-3, - 0.21743961811521264320e-3, -0.16431810653676389022e-3, - 0.84418223983852743293e-4, -0.26190838401581408670e-4, - 0.36899182659531622704e-5 -] - -function gamma(n) { - if(n <= 0) // Integrating by less than 0 - if(isFinite(n)) - return Infinity - else - throw new EvalError("Cannot calculate Γ(-∞).") - - if(n >= 171.35) - return Infinity // Would return more than 2^1024 - 1 (aka Number.INT_MAX) - - if(n === Math.round(n) && isFinite(n)) { - // Calculating (n-1)! - let res = n - 1 - - for(let i = n - 2; i > 1; i++) - res *= i - - if(res === 0) - res = 1 // 0! is per definition 1 - - return res - } - - // Section below adapted function adapted from math.js - if(n < 0.5) - return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n)) - - if(n > 85.0) { // Extended Stirling Approx - let twoN = n * n - let threeN = twoN * n - let fourN = threeN * n - let fiveN = fourN * n - return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) * - (1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) - - (571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) + - (5246819 / (75246796800 * fiveN * n))) - } - - --n - let x = GAMMA_P[0] - for (let i = 1; i < GAMMA_P.length; ++i) { - x += GAMMA_P[i] / (n + i) - } - - let t = n + GAMMA_G + 0.5 - return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x -} - -function arrayMap(f, arr) { - if (typeof f != 'function') - throw new EvalError(qsTranslate('error', 'First argument to map is not a function.')) - if (!Array.isArray(arr)) - throw new EvalError(qsTranslate('error', 'Second argument to map is not an array.')) - return arr.map(f) -} - -function arrayFold(f, init, arr) { - if (typeof f != 'function') - throw new EvalError(qsTranslate('error', 'First argument to fold is not a function.')) - if (!Array.isArray(arr)) - throw new EvalError(qsTranslate('error', 'Second argument to fold is not an array.')) - return arr.reduce(f, init) -} - -function arrayFilter(f, arr) { - if (typeof f != 'function') - throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.')) - if (!Array.isArray(arr)) - throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.')) - return arr.filter(f) -} - -function arrayFilter(f, arr) { - if (typeof f != 'function') - throw new EvalError(qsTranslate('error', 'First argument to filter is not a function.')) - if (!Array.isArray(arr)) - throw new EvalError(qsTranslate('error', 'Second argument to filter is not an array.')) - return arr.filter(f) -} - -function arrayJoin(sep, arr) { - if (!Array.isArray(arr)) - throw new Error(qsTranslate('error', 'Second argument to join is not an array.')) - return arr.join(sep) -} - -function indexOf(target, s) { - if (!(Array.isArray(s) || typeof s === 'string')) - throw new Error(qsTranslate('error', 'Second argument to indexOf is not a string or array.')) - return s.indexOf(target) -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js deleted file mode 100644 index 5d1cb9f..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/reference.js +++ /dev/null @@ -1,177 +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 . - */ - -.pragma library - -.import "polyfill.js" as Polyfill - - -const CONSTANTS = { - "π": Math.PI, - "pi": Math.PI, - "inf": Infinity, - "infinity": Infinity, - "∞": Infinity, - "e": Math.E -}; -const CONSTANTS_LIST = Object.keys(CONSTANTS); - -const FUNCTIONS = { - // The functions commented are the one either not implemented - // in the parser, or not to be used for autocompletion. - // Unary operators - //'+': Number, - //'-': (x) => -x, - //'!' - // Other operations - 'length': (s) => Array.isArray(s) ? s.length : String(s).length, - // Boolean functions - 'not': (x) => !x, - // Math functions - 'abs': Math.abs, - 'acos': Math.acos, - 'acosh': Math.acosh, - 'asin': Math.asin, - 'asinh': Math.asinh, - 'atan': Math.atan, - 'atan2': Math.atan2, - 'atanh': Math.atanh, - 'cbrt': Math.cbrt, - 'ceil': Math.ceil, - //'clz32': Math.clz32, - 'cos': Math.cos, - 'cosh': Math.cosh, - 'exp': Math.exp, - 'expm1': Math.expm1, - 'floor': Math.floor, - //'fround': Math.fround, - 'hypot': Math.hypot, - //'imul': Math.imul, - 'lg': Math.log10, - 'ln': Math.log, - 'log': Math.log, - 'log10': Math.log10, - 'log1p': Math.log1p, - 'log2': Math.log2, - 'max': Math.max, - 'min': Math.min, - 'pow': Math.log2, - 'random': Math.random, - 'round': Math.round, - 'sign': Math.sign, - 'sin': Math.sin, - 'sinh': Math.sinh, - 'sqrt': Math.sqrt, - 'tan': Math.tan, - 'tanh': Math.tanh, - 'trunc': Math.trunc, - // Functions in expr-eval, ported here. - 'fac': Polyfill.factorial, - 'gamma': Polyfill.gamma, - 'Γ': Polyfill.gamma, - 'roundTo': (x, exp) => Number(x).toFixed(exp), - // 'map': Polyfill.arrayMap, - // 'fold': Polyfill.arrayFold, - // 'filter': Polyfill.arrayFilter, - // 'indexOf': Polyfill.indexOf, - // 'join': Polyfill.arrayJoin, - // Integral & derivative (only here for autocomplete). - 'integral': () => 0, // TODO: Implement - 'derivative': () => 0, -} -const FUNCTIONS_LIST = Object.keys(FUNCTIONS); - -class P { - // Parameter class. - constructor(type, name = '', optional = false, multipleAllowed = false) { - this.name = name - this.type = type - this.optional = optional - this.multipleAllowed = multipleAllowed - } - - toString() { - let base_string = this.type - if(this.name != '') - base_string = `${this.name}: ${base_string}` - if(this.multipleAllowed) - base_string += '...' - if(!this.optional) - base_string = `<${base_string}>` - else - base_string = `[${base_string}]` - return base_string - } -} - -let string = new P('string') -let bool = new P('bool') -let number = new P('number') -let array = new P('array') - -const FUNCTIONS_USAGE = { - 'length': [string], - 'not': [bool], - // Math functions - 'abs': [number], - 'acos': [number], - 'acosh': [number], - 'asin': [number], - 'asinh': [number], - 'atan': [number], - 'atan2': [number], - 'atanh': [number], - 'cbrt': [number], - 'ceil': [number], - //'clz32': [number], - 'cos': [number], - 'cosh': [number], - 'exp': [number], - 'expm1': [number], - 'floor': [number], - //'fround': [number], - 'hypot': [number], - //'imul': [number], - 'lg': [number], - 'ln': [number], - 'log': [number], - 'log10': [number], - 'log1p': [number], - 'log2': [number], - 'max': [number, number, new P('numbers', '', true, true)], - 'min': [number, number, new P('numbers', '', true, true)], - 'pow': [number, new P('number', 'exp')], - 'random': [number, number], - 'round': [number], - 'sign': [number], - 'sin': [number], - 'sinh': [number], - 'sqrt': [number], - 'tan': [number], - 'tanh': [number], - 'trunc': [number], - // Functions in expr-eval, ported here. - 'fac': [number], - 'gamma': [number], - 'Γ': [number], - 'roundTo': [number, new P('number')], - // Function manipulation - 'derivative': [new P('f'), new P('string', 'var', true), number], - 'integral': [new P('from'), new P('to'), new P('f'), new P('string', 'var', true)], -} - diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/utils.js b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/utils.js deleted file mode 100644 index 0b62f8f..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/utils.js +++ /dev/null @@ -1,369 +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 . - */ - -.pragma library - -var powerpos = { - "-": "⁻", - "+": "⁺", - "=": "⁼", - " ": " ", - "(": "⁽", - ")": "⁾", - "0": "⁰", - "1": "¹", - "2": "²", - "3": "³", - "4": "⁴", - "5": "⁵", - "6": "⁶", - "7": "⁷", - "8": "⁸", - "9": "⁹", - "a": "ᵃ", - "b": "ᵇ", - "c": "ᶜ", - "d": "ᵈ", - "e": "ᵉ", - "f": "ᶠ", - "g": "ᵍ", - "h": "ʰ", - "i": "ⁱ", - "j": "ʲ", - "k": "ᵏ", - "l": "ˡ", - "m": "ᵐ", - "n": "ⁿ", - "o": "ᵒ", - "p": "ᵖ", - "r": "ʳ", - "s": "ˢ", - "t": "ᵗ", - "u": "ᵘ", - "v": "ᵛ", - "w": "ʷ", - "x": "ˣ", - "y": "ʸ", - "z": "ᶻ" -} - -var exponents = [ - "⁰","¹","²","³","⁴","⁵","⁶","⁷","⁸","⁹" -] -var exponentReg = new RegExp('(['+exponents.join('')+']+)', 'g') - -var indicepos = { - "-": "₋", - "+": "₊", - "=": "₌", - "(": "₍", - ")": "₎", - " ": " ", - "0": "₀", - "1": "₁", - "2": "₂", - "3": "₃", - "4": "₄", - "5": "₅", - "6": "₆", - "7": "₇", - "8": "₈", - "9": "₉", - "a": "ₐ", - "e": "ₑ", - "h": "ₕ", - "i": "ᵢ", - "j": "ⱼ", - "k": "ₖ", - "l": "ₗ", - "m": "ₘ", - "n": "ₙ", - "o": "ₒ", - "p": "ₚ", - "r": "ᵣ", - "s": "ₛ", - "t": "ₜ", - "u": "ᵤ", - "v": "ᵥ", - "x": "ₓ", -} -// Put a text in sup position -function textsup(text) { - var ret = "" - text = text.toString() - for (var i = 0; i < text.length; i++) { - if(Object.keys(powerpos).indexOf(text[i]) >= 0) { - ret += powerpos[text[i]] - } else { - ret += text[i] - } - } - return ret -} - -// Put a text in sub position -function textsub(text) { - var ret = "" - text = text.toString() - for (var i = 0; i < text.length; i++) { - if(Object.keys(indicepos).indexOf(text[i]) >= 0) { - ret += indicepos[text[i]] - } else { - ret += text[i] - } - } - return ret -} - -function simplifyExpression(str) { - var replacements = [ - // Operations not done by parser. - // [// Decomposition way 2 - // /(^|[+-] |\()([-.\d\w]+) ([*/]) \((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\)($| [+-]|\))/g, - // "$1$2 $3 $4 $6 $2 $3 $7$9" - // ], - // [ // Decomposition way 2 - // /(^|[+-] |\()\((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\) ([*/]) ([-.\d\w]+)($| [+-]|\))/g, - // "$1$2 $7 $8 $4 $5 $7 $8$9" - // ], - [ // Factorisation of π elements. - /(([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*) ([+-]) (([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*)?/g, - function(match, m1, n1, pi1, m2, ope2, n2, opeM, m3, n3, pi2, m4, ope4, n4) { - // g1, g2, g3 , g4, g5, g6, g7, g8, g9, g10, g11,g12 , g13 - // We don't care about mx & pix, ope2 & ope4 are either / or * for n2 & n4. - // n1 & n3 are multiplied, opeM is the main operation (- or +). - // Putting all n in form of number - //n2 = n2 == undefined ? 1 : parseFloat(n) - n1 = m1 == undefined ? 1 : eval(m1 + '1') - n2 = m2 == undefined ? 1 : eval('1' + m2) - n3 = m3 == undefined ? 1 : eval(m3 + '1') - n4 = m4 == undefined ? 1 : eval('1' + m4) - //var [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n)) - // Falling back to * in case it does not exist (the corresponding n would be 1) - var [ope2, ope4] = [ope2, ope4].map(ope => ope == '/' ? '/' : '*') - var coeff1 = n1*n2 - var coeff2 = n3*n4 - var coefficient = coeff1+coeff2-(opeM == '-' ? 2*coeff2 : 0) - - return `${coefficient} * π` - } - ], - [ // Removing parenthesis when content is only added from both sides. - /(^|[+-] |\()\(([^)(]+)\)($| [+-]|\))/g, - function(match, b4, middle, after) {return `${b4}${middle}${after}`} - ], - [ // Removing parenthesis when content is only multiplied. - /(^|[*\/] |\()\(([^)(+-]+)\)($| [*\/+-]|\))/g, - function(match, b4, middle, after) {return `${b4}${middle}${after}`} - ], - [ // Removing parenthesis when content is only multiplied. - /(^|[*\/-+] |\()\(([^)(+-]+)\)($| [*\/]|\))/g, - function(match, b4, middle, after) {return `${b4}${middle}${after}`} - ], - [// Simplification additions/substractions. - /(^|[^*\/] |\()([-.\d]+) (\+|\-) (\([^)(]+\)|[^)(]+) (\+|\-) ([-.\d]+)($| [^*\/]|\))/g, - function(match, b4, n1, op1, middle, op2, n2, after) { - var total - if(op2 == '+') { - total = parseFloat(n1) + parseFloat(n2) - } else { - total = parseFloat(n1) - parseFloat(n2) - } - return `${b4}${total} ${op1} ${middle}${after}` - } - ], - [// Simplification multiplications/divisions. - /([-.\d]+) (\*|\/) (\([^)(]+\)|[^)(+-]+) (\*|\/) ([-.\d]+)/g, - function(match, n1, op1, middle, op2, n2) { - if(parseInt(n1) == n1 && parseInt(n2) == n2 && op2 == '/' && - (parseInt(n1) / parseInt(n2)) % 1 != 0) { - // Non int result for int division. - return `(${n1} / ${n2}) ${op1} ${middle}` - } else { - if(op2 == '*') { - return `${parseFloat(n1) * parseFloat(n2)} ${op1} ${middle}` - } else { - return `${parseFloat(n1) / parseFloat(n2)} ${op1} ${middle}` - } - } - } - ], - [// Starting & ending parenthesis if not needed. - /^\s*\((.*)\)\s*$/g, - function(match, middle) { - var str = middle - // Replace all groups - while(/\([^)(]+\)/g.test(str)) - str = str.replace(/\([^)(]+\)/g, '') - // There shouldn't be any more parenthesis - // If there is, that means the 2 parenthesis are needed. - if(!str.includes(')') && !str.includes('(')) { - return middle - } else { - return `(${middle})` - } - - } - ], - // Simple simplifications - // [/(\s|^|\()0(\.0+)? \* (\([^)(]+\))/g, '$10'], - // [/(\s|^|\()0(\.0+)? \* ([^)(+-]+)/g, '$10'], - // [/(\([^)(]\)) \* 0(\.0+)?(\s|$|\))/g, '0$3'], - // [/([^)(+-]) \* 0(\.0+)?(\s|$|\))/g, '0$3'], - // [/(\s|^|\()1(\.0+)? (\*|\/) /g, '$1'], - // [/(\s|^|\()0(\.0+)? (\+|\-) /g, '$1'], - // [/ (\*|\/) 1(\.0+)?(\s|$|\))/g, '$3'], - // [/ (\+|\-) 0(\.0+)?(\s|$|\))/g, '$3'], - // [/(^| |\() /g, '$1'], - // [/ ($|\))/g, '$1'], - ] - - // Replacements - var found - do { - found = false - for(var replacement of replacements) - while(replacement[0].test(str)) { - found = true - str = str.replace(replacement[0], replacement[1]) - } - } while(found) - return str -} - - -function makeExpressionReadable(str) { - var replacements = [ - // variables - [/pi/g, 'π'], - [/Infinity/g, '∞'], - [/inf/g, '∞'], - // Other - [/ \* /g, '×'], - [/ \^ /g, '^'], - [/\^\(([\d\w+-]+)\)/g, function(match, p1) { return textsup(p1) }], - [/\^([\d\w+-]+)/g, function(match, p1) { return textsup(p1) }], - [/_\(([\d\w+-]+)\)/g, function(match, p1) { return textsub(p1) }], - [/_([\d\w+-]+)/g, function(match, p1) { return textsub(p1) }], - [/\[([^\[\]]+)\]/g, function(match, p1) { return textsub(p1) }], - [/(\d|\))×/g, '$1'], - //[/×(\d|\()/g, '$1'], - [/([^a-z])\(([^)(+.\/-]+)\)/g, "$1×$2"], - [/integral\((.+),\s?(.+),\s?("|')(.+)("|'),\s?("|')(.+)("|')\)/g, function(match, a, b, p1, body, p2, p3, by, p4) { - if(a.length < b.length) { - return `∫${textsub(a)}${textsup(b)} ${body} d${by}` - } else { - return `∫${textsup(b)}${textsub(a)} ${body} d${by}` - } - }], - [/derivative\(?("|')(.+)("|'), ?("|')(.+)("|'), ?(.+)\)?/g, function(match, p1, body, p2, p3, by, p4, x) { - return `d(${body.replace(new RegExp(by, 'g'), 'x')})/dx` - }] - ] - - // str = simplifyExpression(str) - // Replacements - for(var replacement of replacements) - while(replacement[0].test(str)) - str = str.replace(replacement[0], replacement[1]) - return str -} - -function parseName(str, removeUnallowed = true) { - var replacements = [ - // Greek letters - [/([^a-z]|^)al(pha)?([^a-z]|$)/g, '$1α$3'], - [/([^a-z]|^)be(ta)?([^a-z]|$)/g, '$1β$3'], - [/([^a-z]|^)ga(mma)?([^a-z]|$)/g, '$1γ$3'], - [/([^a-z]|^)de(lta)?([^a-z]|$)/g, '$1δ$3'], - [/([^a-z]|^)ep(silon)?([^a-z]|$)/g, '$1ε$3'], - [/([^a-z]|^)ze(ta)?([^a-z]|$)/g, '$1ζ$3'], - [/([^a-z]|^)et(a)?([^a-z]|$)/g, '$1η$3'], - [/([^a-z]|^)th(eta)?([^a-z]|$)/g, '$1θ$3'], - [/([^a-z]|^)io(ta)?([^a-z]|$)/g, '$1ι$3'], - [/([^a-z]|^)ka(ppa)([^a-z]|$)?/g, '$1κ$3'], - [/([^a-z]|^)la(mbda)?([^a-z]|$)/g, '$1λ$3'], - [/([^a-z]|^)mu([^a-z]|$)/g, '$1μ$2'], - [/([^a-z]|^)nu([^a-z]|$)/g, '$1ν$2'], - [/([^a-z]|^)xi([^a-z]|$)/g, '$1ξ$2'], - [/([^a-z]|^)rh(o)?([^a-z]|$)/g, '$1ρ$3'], - [/([^a-z]|^)si(gma)?([^a-z]|$)/g, '$1σ$3'], - [/([^a-z]|^)ta(u)?([^a-z]|$)/g, '$1τ$3'], - [/([^a-z]|^)up(silon)?([^a-z]|$)/g, '$1υ$3'], - [/([^a-z]|^)ph(i)?([^a-z]|$)/g, '$1φ$3'], - [/([^a-z]|^)ch(i)?([^a-z]|$)/g, '$1χ$3'], - [/([^a-z]|^)ps(i)?([^a-z]|$)/g, '$1ψ$3'], - [/([^a-z]|^)om(ega)?([^a-z]|$)/g, '$1ω$3'], - // Capital greek letters - [/([^a-z]|^)gga(mma)?([^a-z]|$)/g, '$1Γ$3'], - [/([^a-z]|^)gde(lta)?([^a-z]|$)/g, '$1Δ$3'], - [/([^a-z]|^)gth(eta)?([^a-z]|$)/g, '$1Θ$3'], - [/([^a-z]|^)gla(mbda)?([^a-z]|$)/g, '$1Λ$3'], - [/([^a-z]|^)gxi([^a-z]|$)/g, '$1Ξ$2'], - [/([^a-z]|^)gpi([^a-z]|$)/g, '$1Π$2'], - [/([^a-z]|^)gsi(gma)([^a-z]|$)?/g, '$1Σ$3'], - [/([^a-z]|^)gph(i)?([^a-z]|$)/g, '$1Φ$3'], - [/([^a-z]|^)gps(i)?([^a-z]|$)/g, '$1Ψ$3'], - [/([^a-z]|^)gom(ega)?([^a-z]|$)/g, '$1Ω$3'], - // Underscores - // [/_\(([^_]+)\)/g, function(match, p1) { return textsub(p1) }], - // [/_([^" ]+)/g, function(match, p1) { return textsub(p1) }], - // Array elements - [/\[([^\]\[]+)\]/g, function(match, p1) { return textsub(p1) }], - // Removing - [/[xπℝℕ\\∪∩\]\[ ()^/÷*×+=\d-]/g , ''], - ] - if(!removeUnallowed) replacements.pop() - // Replacements - for(var replacement of replacements) - str = str.replace(replacement[0], replacement[1]) - return str -} - -String.prototype.toLatinUppercase = function() { - return this.replace(/[a-z]/g, function(match){return match.toUpperCase()}) -} - -function camelCase2readable(label) { - var parsed = parseName(label, false) - return parsed.charAt(0).toLatinUppercase() + parsed.slice(1).replace(/([A-Z])/g," $1") -} - -function getRandomColor() { - var clrs = '0123456789ABCDEF'; - var color = '#'; - for(var i = 0; i < 6; i++) { - color += clrs[Math.floor(Math.random() * (16-5*(i%2==0)))]; - } - return color; -} - -function escapeHTML(str) { - return str.replace(/&/g,'&').replace(//g,'>') ; -} - - - -/** - * Parses exponents and replaces them with expression values - * @param {string} expression - The expression to replace in. - * @return {string} The parsed expression - */ -function exponentsToExpression(expression) { - return expression.replace(exponentReg, (m, exp) => '^' + exp.split('').map((x) => exponents.indexOf(x)).join('')) -} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/qmldir b/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/qmldir deleted file mode 100644 index ac68e47..0000000 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module eu.ad5001.LogarithmPlotter - -Settings 1.0 Settings.qml -Alert 1.0 Alert.qml diff --git a/LogarithmPlotter/util/helper.py b/LogarithmPlotter/util/helper.py deleted file mode 100644 index 127e587..0000000 --- a/LogarithmPlotter/util/helper.py +++ /dev/null @@ -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 . -""" - -from PySide6.QtWidgets import QMessageBox, QApplication -from PySide6.QtCore import QRunnable, QThreadPool, QThread, QObject, Signal, Slot, QCoreApplication -from PySide6.QtQml import QQmlApplicationEngine -from PySide6.QtGui import QImage -from PySide6 import __version__ as PySide6_version - -from os import chdir, path -from json import loads -from sys import version as sys_version -from urllib.request import urlopen -from urllib.error import HTTPError, URLError - -from LogarithmPlotter import __VERSION__ -from LogarithmPlotter.util import config - -class ChangelogFetcher(QRunnable): - def __init__(self, helper): - QRunnable.__init__(self) - self.helper = helper - - def run(self): - msg_text = "Unknown changelog error." - try: - # Fetching version - r = urlopen("https://api.ad5001.eu/changelog/logarithmplotter/?version=" + __VERSION__) - lines = r.readlines() - r.close() - msg_text = "".join(map(lambda x: x.decode('utf-8'), lines)).strip() - except HTTPError as e: - msg_text = QCoreApplication.translate("changelog","Could not fetch changelog: Server error {}.").format(str(e.code)) - except URLError as e: - msg_text = QCoreApplication.translate("changelog","Could not fetch update: {}.").format(str(e.reason)) - self.helper.gotChangelog.emit(msg_text) - -class Helper(QObject): - changelogFetched = Signal(str) - gotChangelog = Signal(str) - - def __init__(self, cwd: str, tmpfile: str): - QObject.__init__(self) - self.cwd = cwd - self.tmpfile = tmpfile - self.gotChangelog.connect(self.fetched) - - def fetched(self, changelog: str): - self.changelogFetched.emit(changelog) - - @Slot(str, str) - def write(self, filename, filedata): - chdir(self.cwd) - if path.exists(path.dirname(path.realpath(filename))): - if filename.split(".")[-1] == "lpf": - # Add header to file - filedata = "LPFv1" + filedata - f = open(path.realpath(filename), 'w', -1, 'utf8') - f.write(filedata) - f.close() - chdir(path.dirname(path.realpath(__file__))) - - @Slot(str, result=str) - def load(self, filename): - chdir(self.cwd) - data = '{}' - if path.exists(path.realpath(filename)): - f = open(path.realpath(filename), 'r', -1, 'utf8') - data = f.read() - f.close() - try: - if data[:5] == "LPFv1": - # V1 version of the file - data = data[5:] - elif data[0] == "{" and "type" in loads(data) and loads(data)["type"] == "logplotv1": - pass - elif data[:3] == "LPF": - # More recent version of LogarithmPlotter file, but incompatible with the current format - raise Exception(QCoreApplication.translate("This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}.\nPlease update LogarithmPlotter to open this file.".format(__VERSION__))) - else: - raise Exception("Invalid LogarithmPlotter file.") - except Exception as e: # If file can't be loaded - QMessageBox.warning(None, 'LogarithmPlotter', QCoreApplication.translate('main','Could not open file "{}":\n{}').format(filename, e), QMessageBox.Ok) # Cannot parse file - else: - QMessageBox.warning(None, 'LogarithmPlotter', QCoreApplication.translate('main','Could not open file: "{}"\nFile does not exist.').format(filename), QMessageBox.Ok) # Cannot parse file - try: - chdir(path.dirname(path.realpath(__file__))) - except NotADirectoryError as e: - # Triggered on bundled versions of MacOS when it shouldn't. Prevents opening files. - # See more at https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues/1 - pass - return data - - @Slot(result=str) - def gettmpfile(self): - return self.tmpfile - - @Slot() - def copyImageToClipboard(self): - clipboard = QApplication.clipboard() - clipboard.setImage(QImage(self.tmpfile)) - - @Slot(result=str) - def getVersion(self): - return __VERSION__ - - @Slot(str, result=str) - def getSetting(self, namespace): - return config.getSetting(namespace) - - @Slot(str, result=int) - def getSettingInt(self, namespace): - return config.getSetting(namespace) - - @Slot(str, result=bool) - def getSettingBool(self, namespace): - return config.getSetting(namespace) - - @Slot(str, str) - def setSetting(self, namespace, value): - return config.setSetting(namespace, value) - - @Slot(str, bool) - def setSettingBool(self, namespace, value): - return config.setSetting(namespace, value) - - @Slot(str, int) - def setSettingInt(self, namespace, value): - return config.setSetting(namespace, value) - - @Slot(str) - def setLanguage(self, new_lang): - config.setSetting("language", new_lang) - - @Slot(result=str) - def getDebugInfos(self): - """ - Returns the version info about Qt, PySide6 & Python - """ - return QCoreApplication.translate('main',"Built with PySide6 (Qt) v{} and python v{}").format(PySide6_version, sys_version.split("\n")[0]) - - @Slot() - def fetchChangelog(self): - changelog_cache_path = path.join(path.dirname(path.realpath(__file__)), "CHANGELOG.md") - print(changelog_cache_path) - if path.exists(changelog_cache_path): - # We have a cached version of the changelog, for env that don't have access to the internet. - f = open(changelog_cache_path); - self.changelogFetched.emit("".join(f.readlines()).strip()) - f.close() - else: - # Fetch it from the internet. - runnable = ChangelogFetcher(self) - QThreadPool.globalInstance().start(runnable) - diff --git a/LogarithmPlotter/util/latex.py b/LogarithmPlotter/util/latex.py deleted file mode 100644 index ce22bb7..0000000 --- a/LogarithmPlotter/util/latex.py +++ /dev/null @@ -1,195 +0,0 @@ -""" - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -""" - -from PySide6.QtCore import QObject, Slot, Property, QCoreApplication -from PySide6.QtGui import QImage, QColor -from PySide6.QtWidgets import QApplication, QMessageBox - -from os import path, remove -from string import Template -from tempfile import TemporaryDirectory -from subprocess import Popen, TimeoutExpired, PIPE -from platform import system -from shutil import which -from sys import argv - -""" -Searches for a valid Latex and DVIPNG (http://savannah.nongnu.org/projects/dvipng/) -installation and collects the binary path in the DVIPNG_PATH variable. -If not found, it will send an alert to the user. -""" -LATEX_PATH = which('latex') -DVIPNG_PATH = which('dvipng') - -DEFAULT_LATEX_DOC = Template(r""" -\documentclass[]{minimal} -\usepackage[utf8]{inputenc} -\usepackage{calligra} -\usepackage{amsfonts} - -\title{} -\author{} - -\begin{document} - -$$$$ $markup $$$$ - -\end{document} -""") - -class Latex(QObject): - """ - Base class to convert Latex equations into PNG images with custom font color and size. - It doesn't have any python dependency, but requires a working latex installation and - dvipng to be installed on the system. - """ - def __init__(self, tempdir: TemporaryDirectory): - QObject.__init__(self) - self.tempdir = tempdir - - def check_latex_install(self): - """ - Checks if the current latex installation is valid. - """ - if LATEX_PATH is None: - print("No Latex installation found.") - if "--test-build" not in argv: - QMessageBox.warning(None, "LogarithmPlotter - Latex setup", QCoreApplication.translate("latex", "No Latex installation found.\nIf you already have a latex distribution installed, make sure it's installed on your path.\nOtherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/.")) - elif DVIPNG_PATH is None: - print("DVIPNG not found.") - if "--test-build" not in argv: - QMessageBox.warning(None, "LogarithmPlotter - Latex setup", QCoreApplication.translate("latex", "DVIPNG was not found. Make sure you include it from your Latex distribution.")) - - @Property(bool) - def latexSupported(self): - return LATEX_PATH is not None and DVIPNG_PATH is not None - - @Slot(str, float, QColor, result=str) - def render(self, latex_markup: str, font_size: float, color: QColor) -> str: - """ - Prepares and renders a latex string into a png file. - """ - markup_hash = "render"+str(hash(latex_markup)) - export_path = path.join(self.tempdir.name, f'{markup_hash}_{int(font_size)}_{color.rgb()}') - if self.latexSupported and not path.exists(export_path + ".png"): - print("Rendering", latex_markup, export_path) - # Generating file - try: - latex_path = path.join(self.tempdir.name, str(markup_hash)) - # If the formula is just recolored or the font is just changed, no need to recreate the DVI. - if not path.exists(latex_path + ".dvi"): - self.create_latex_doc(latex_path, latex_markup) - self.convert_latex_to_dvi(latex_path) - self.cleanup(latex_path) - # Creating four pictures of different sizes to better handle dpi. - self.convert_dvi_to_png(latex_path, export_path, font_size, color) - # self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color) - # self.convert_dvi_to_png(latex_path, export_path+"@3", font_size*3, color) - # self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color) - except Exception as e: # One of the processes failed. A message will be sent every time. - raise e - img = QImage(export_path); - # Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded - return f'{export_path}.png,{img.width()},{img.height()}' - - def create_latex_doc(self, export_path: str, latex_markup: str): - """ - Creates a temporary latex document with base file_hash as file name and a given expression markup latex_markup. - """ - ltx_path = export_path + ".tex" - f = open(export_path + ".tex", 'w') - f.write(DEFAULT_LATEX_DOC.substitute(markup = latex_markup)) - f.close() - - def convert_latex_to_dvi(self, export_path: str): - """ - Converts a TEX file to a DVI file. - """ - self.run([ - LATEX_PATH, - export_path + ".tex" - ]) - - def convert_dvi_to_png(self, dvi_path: str, export_path: str, font_size: float, color: QColor): - """ - Converts a DVI file to a PNG file. - Documentation: https://linux.die.net/man/1/dvipng - """ - fg = color.convertTo(QColor.Rgb) - fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}' - depth = int(font_size * 72.27 / 100) * 10 - self.run([ - DVIPNG_PATH, - '-T', 'tight', # Make sure image borders are as tight around the equation as possible to avoid blank space. - '--truecolor', # Make sure it's rendered in 24 bit colors. - '-D',f'{depth}', # Depth of the image - '-bg', 'Transparent', # Transparent background - '-fg',f'{fg}', # Foreground of the wanted color. - f'{dvi_path}.dvi', # Input file - '-o',f'{export_path}.png', # Output file - ]) - - def run(self, process: list): - """ - Runs a subprocess and handles exceptions and messages them to the user. - """ - proc = Popen(process, stdout=PIPE, stderr=PIPE, cwd=self.tempdir.name) - try: - out, err = proc.communicate(timeout=2) # 2 seconds is already FAR too long. - if proc.returncode != 0: - # Process errored - QMessageBox.warning(None, "LogarithmPlotter - Latex", - QCoreApplication.translate("latex", "An exception occured within the creation of the latex formula.\nProcess '{}' ended with a non-zero return code {}:\n\n{}\nPlease make sure your latex installation is correct and report a bug if so.") - .format(" ".join(process), proc.returncode, str(out, 'utf8')+"\n"+str(err,'utf8'))) - raise Exception(" ".join(process) + " process exited with return code " + str(proc.returncode) + ":\n" + str(out, 'utf8')+"\n"+str(err,'utf8')) - except TimeoutExpired as e: - # Process timed out - proc.kill() - out, err = proc.communicate() - QMessageBox.warning(None, "LogarithmPlotter - Latex", - QCoreApplication.translate("latex", "An exception occured within the creation of the latex formula.\nProcess '{}' took too long to finish:\n{}\nPlease make sure your latex installation is correct and report a bug if so.") - .format(" ".join(process), str(out, 'utf8')+"\n"+str(err,'utf8'))) - raise Exception(" ".join(process) + " process timed out:\n" + str(out, 'utf8')+"\n"+str(err,'utf8')) - - def cleanup(self, export_path): - """ - Removes auxiliary, logs and Tex temporary files. - """ - for i in [".tex", ".aux", ".log"]: - remove(export_path + i) - - """ - @Slot(str, float, QColor, result=str) - def render_legacy(self, latexstring, font_size, color = True): - exprpath = path.join(self.tempdir.name, f'{hash(latexstring)}_{font_size}_{color.rgb()}.png') - print("Rendering", latexstring, exprpath) - if not path.exists(exprpath): - fg = color.convertTo(QColor.Rgb) - fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}' - preview('$${' + latexstring + '}$$', viewer='file', filename=exprpath, dvioptions=[ - "-T", "tight", - "-z", "0", - "--truecolor", - f"-D {int(font_size * 72.27 / 100) * 10}", # See https://linux.die.net/man/1/dvipng#-D for convertion - "-bg", "Transparent", - "-fg", fg], - euler=False) - img = QImage(exprpath); - # Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded - return f'{exprpath},{img.width()},{img.height()}' - """ diff --git a/README.md b/README.md index 026528f..f06d009 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# ![icon](https://git.ad5001.eu/Ad5001/LogarithmPlotter/raw/branch/master/logplotter.svg) LogarithmPlotter +# ![icon](https://apps.ad5001.eu/icons/apps/svg/logarithmplotter.svg) LogarithmPlotter + [![Build Status](https://ci.ad5001.eu/api/badges/Ad5001/LogarithmPlotter/status.svg)](https://ci.ad5001.eu/Ad5001/LogarithmPlotter) [![Translation status](https://hosted.weblate.org/widgets/logarithmplotter/-/logarithmplotter/svg-badge.svg)](https://hosted.weblate.org/engage/logarithmplotter/) [![On flathub](https://img.shields.io/flathub/v/eu.ad5001.LogarithmPlotter?label=on%20flathub&logo=Flathub&logoColor=white&color=4A86CF)](https://flathub.org/apps/details/eu.ad5001.LogarithmPlotter) @@ -7,56 +8,79 @@ 2D plotter software to make Bode plots, sequences and distribution functions. ## Screenshots + ![Magnitude example](https://apps.ad5001.eu/img/full/logarithmplotter.png) ![Phase example](https://apps.ad5001.eu/img/en/logarithmplotter/phase.png) ![Object settings](https://apps.ad5001.eu/img/en/logarithmplotter/object-settings.webp) -You can find more screenshots on the [app website](https://apps.ad5001.eu/logarithmplotter/). +You can find more screenshots on the [app's website](https://apps.ad5001.eu/logarithmplotter/). -## Run +## Build & Run -You can simply run LogarithmPlotter using `python3 run.py`. +First, you'll need to install all the required dependencies: -In order to test translations, you can use the `--lang=` command line option to force the detected locale of LogarithmPlotter. +- [Python 3](https://python.org) with [poetry](https://python-poetry.org/), setup a virtual environment, go to the `runtime-pyside6` directory, and call + `poetry install`. +- [npm](https://npmjs.com) (or [yarn](https://yarnpkg.com/)), go to the `common` directory, and run `npm install` (or `yarn install`). + +You can simply run LogarithmPlotter using `python3 run.py`. It automatically compiles the language files (requires +`pyside6-lrelease` to be installed and in path), and the JavaScript modules. + +If you do not wish do recompile the files again on every run, you can use the build script (`scripts/build.sh`) and run +`python3 build/runtime-pyside6/LogarithmPlotter/logarithmplotter.py`. + +In order to test translations, you can use the `--lang=` commandline option to force the locale. ## Install ### Generate installers: + All scripts noted here can be found in the `scripts` directory. -You can generate installers from LogarithmPlotter after installing all the dependencies: -For all builds, you need [Python 3](https://python.org) with [PySide6](https://pypi.org/project/PySide6/) installable with `pip install PySide6`. -- Windows installer: - - You need `pyinstaller`. You can install it using `pip install pyinstaller`. - - Run the `build-windows.bat` script (or `build-wine.sh` if you're cross-compiling with wine on Linux) to build an exe for LogarithmPlotter. - - You also need [NSIS](https://nsis.sourceforge.io/Main_Page) (Linux users can install the [nsis](https://pkgs.org/download/nsis) package). - - Run the `package-windows.bat` script (or `package-wine.sh`if you're cross-compiling on Linux). You will find a logarithmplotter-setup.exe installer in the dist/accountfree/ folder. -- MacOS Archive creator installer: - - You need `pyinstaller`. You can install it using `pip install pyinstaller`. - - Run the `build-macosx.sh` script to build an .app for LogarithmPlotter which can be found in the dist directory. - - Run the `package-macosx.sh` script. You will find a LogarithmPlotter-v0.1-dev-setup.dmg installer in the dist/ folder. +You can generate installers for LogarithmPlotter after installing all the dependencies. + +- Windows installer (crosscompiling from Linux): + - Run `build-wine.sh` (requires wine) to build an exe for LogarithmPlotter in build/runtime-pyside6/dist. + - You also need [NSIS](https://nsis.sourceforge.io/Main_Page) (the [nsis](https://pkgs.org/download/nsis) package is available on linux). + - Run the `package-wine.sh` script. You will find a logarithmplotter-setup.exe installer in the build/runtime-pyside6/dist/logarithmplotter/ folder. +- MacOS Archive creator installer: + - Run the `build-macosx.sh` script to build an .app for LogarithmPlotter which can be found in the build/runtime-pyside6/dist directory. + - Run the `package-macosx.sh` script. You will find a LogarithmPlotter-v<version>-setup.dmg installer in the + build/runtime-pyside6/build/pysdist/ folder. - Linux packages: - - To build a DEB, you need DPKG and stdeb. You can install the later by using `pip install stdeb`. - - To build and install the flatpak, you need [flatpak-builder](https://docs.flatpak.org/en/latest/flatpak-builder.html) installed. - - To build the snap, you need [snapcraft](https://snapcraft.io) installed. - - Run `package-linux.sh`. - - -### Linux - -Run `bash linux/install_local.sh` + - Run `package-deb.sh`. It will create an DSC and a DEB in build/runtime-pyside6/deb_dist/ + - Run `scripts/build.sh` followed by `snapcraft`. It .snap file in the root directory. + - See [the flatpak repo](https://github.com/Ad5001/eu.ad5001.LogarithmPlotter) for instrutions on how to build the flatpak. ## Contribute -There are several ways to contribute to LogarithmPlotter. -- You can help to translate [the project on Hosted Weblate](https://hosted.weblate.org/engage/logarithmplotter/): -[![Translation status](https://hosted.weblate.org/widgets/logarithmplotter/-/logarithmplotter/multi-auto.svg)](https://hosted.weblate.org/engage/logarithmplotter/) +There are several ways you can contribute to LogarithmPlotter. -- You can help the development of LogarithmPlotter. In order to get started, take a look at the [wiki](https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_pages). +- You can help to translate [the project on Hosted Weblate](https://hosted.weblate.org/engage/logarithmplotter/): + [![Translation status](https://hosted.weblate.org/widgets/logarithmplotter/-/logarithmplotter/multi-auto.svg)](https://hosted.weblate.org/engage/logarithmplotter/) + +- You can help the development of LogarithmPlotter. In order to get started, take a look at + the [wiki](https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_pages). + +## Tests + +To run LogarithmPlotter's tests, follow these steps: + +- Python + - Install python3 and [poetry](https://python-poetry.org/) + - Create and activate virtual env (recommended) + - Go into `runtime-pyside6` and run `poetry install --with test` +- ECMAScript + - Install node with npm + - Go into `common` and run `npm install -D` + +Finally, to actually run the tests: + - Run `scripts/run-tests.sh` ## Legal notice - LogarithmPlotter - 2D plotter software to make BODE plots, sequences and repartition functions. - Copyright (C) 2021-2024 Ad5001 + + LogarithmPlotter - 2D plotter software to make Bode plots, sequences and repartition functions. + Copyright (C) 2021-2025 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 @@ -71,12 +95,19 @@ There are several ways to contribute to LogarithmPlotter. You should have received a copy of the GNU General Public License along with this program. If not, see . -Language files translations located at LogarithmPlotter/i18n are licensed under GNU GPL3.0+ and are copyrighted by their original authors. See LICENSE.md for more details: -- 🇳🇴 Norwegian translation by [Allan Nordhøy](https://github.com/comradekingu) +See LICENSE.md for more details. Language files translations located at assets/i18n are licensed under GNU GPL3.0+ and +are copyrighted by their original authors: + - 🇭🇺 Hungarian translation by [Óvári](https://github.com/ovari) +- 🇳🇴 Norwegian translation by [Allan Nordhøy](https://github.com/comradekingu) +- 🇪🇸 Spanish translation by gallegonovato and [IngrownMink4](https://github.com/IngrownMink4) ### Libraries used -LogarithmPlotter includes [expr-eval](https://github.com/silentmatt/expr-eval) a port of [ndef.parser](https://web.archive.org/web/20111023001618/http://www.undefined.ch/mparser/index.html) by Raphael Graf <r@undefined.ch>, ported to javascript by Matthew Crumley <email@matthewcrumley.com> (http://silentmatt.com/), and then to QMLJS by Ad5001. +LogarithmPlotter includes [expr-eval](https://github.com/silentmatt/expr-eval) a port +of [ndef.parser](https://web.archive.org/web/20111023001618/http://www.undefined.ch/mparser/index.html) by Raphael Graf +<r@undefined.ch>, ported to javascript by Matthew Crumley +<email@matthewcrumley.com> (http://silentmatt.com/), and then to QMLJS by Ad5001. -The specific file (LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/expr-eval.js) is licensed under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt). +All files in (common/src/lib/expr-eval/) except integration.mjs are licensed +under the [MIT License](https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt). diff --git a/assets/i18n/lp_de.ts b/assets/i18n/lp_de.ts new file mode 100644 index 0000000..9ed0e8c --- /dev/null +++ b/assets/i18n/lp_de.ts @@ -0,0 +1,1996 @@ + + + + + About + + + About LogarithmPlotter + Über LogarithmPlotter + + + + LogarithmPlotter v%1 + LogarithmPlotter v%1 + + + + 2D plotter software to make BODE plots, sequences and repartition functions. + 2D-Grafiksoftware zur Erstellung von Bode-Diagramms, Folgen und Verteilungsfunktionen. + + + + Report a bug + Bug melden + + + + Official website + Offizielle Website + + + + AppMenuBar + + + &File + &Datei + + + + &Load... + &Laden… + + + + &Save + &Speichern + + + + Save &As... + Speichern &Unter… + + + + &Quit + &Ausfahrt + + + + &Edit + &Bearbeiten + + + + &Undo + &Lösen + + + + &Redo + &Wiederherstellen + + + + &Copy plot + Grafik &Kopieren + + + + &Preferences + &Einstellung + + + + &Create + &Erstellen + + + &Settings + &Einstellungen + + + Check for updates on startup + Beim Starten auf Updates prüfen + + + Reset redo stack automaticly + Wiederherstellen-Stapel automatisch zurücksetzen + + + Enable LaTeX rendering + LaTeX-Rendering aktivieren + + + Expression editor + Ausdruckseditor + + + Automatically close parenthesises and brackets + Klammern automatisch schließen + + + Enable syntax highlighting + Syntaxhervorhebung einschalten + + + Enable autocompletion + Automatische Vervollständigung einschalten + + + Color Scheme + Syntaktische Färbung + + + + &Help + &Hilfe + + + + &Source code + &Quellcode + + + + &Report a bug + Fehler &Melden + + + + &User manual + &Benutzerhandbuch + + + + &Changelog + &Versionshinweise + + + + &Help translating! + &Hilfe beim Übersetzen! + + + + &Thanks + &Danksagungen + + + + &About + &Übrigens + + + + Save unsaved changes? + Änderungen speichern? + + + + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + Diese Grafik enthält ungespeicherte Änderungen. Dadurch gehen alle ungespeicherten Daten verloren. Fortfahren? + + + + BaseDialog + + + Close + Schließen + + + + BoolSetting + + Check for updates on startup + Beim Starten auf Updates prüfen + + + + Browser + + + Filter... + Filtern… + + + + Redo > + Wiederherstellen > + + + + > Now + > Aktueller Stand + + + + < Undo + < Rückgängig + + + + Changelog + + + Fetching changelog... + Changelog abrufen… + + + + Close + Schließen + + + + CustomPropertyList + + + + + Create new %1 + + Neues %1objekt erstellen + + + + Pick on graph + Aufnehmen auf Graph + + + + Dialog + + + Edit properties of %1 %2 + Eigenschaften von %1 %2 bearbeiten + + + + LogarithmPlotter - Invalid object name + LogarithmPlotter - Ungültiger Objektname + + + + An object with the name '%1' already exists. + Ein Objekt mit dem Namen '%1' existiert bereits. + + + + Name + Name + + + Label content + Etikett + + + + null + leer + + + + name + Name + + + + name + value + Name + Wert + + + + EditorDialog + + Edit properties of %1 %2 + Eigenschaften von %1 %2 bearbeiten + + + Name + Name + + + Label content + Etikett + + + null + leer + + + name + Name + + + name + value + Name + Wert + + + + Create new %1 + + Neues %1objekt erstellen + + + + ExpressionEditor + + + Object Properties + Objekteigenschaften + + + + Variables + Variablen + + + + Constants + Konstanten + + + + Functions + Funktionen + + + + Executable Objects + Funktionsobjekte + + + + Objects + Objekte + + + + FileDialog + + + Export Logarithm Plot file + Logarithmusgrafik exportieren + + + + Import Logarithm Plot file + Logarithmusgrafik importieren + + + + GreetScreen + + + Welcome to LogarithmPlotter + Willkommen bei LogarithmPlotter + + + + Version %1 + Version %1 + + + Take a few seconds to configure LogarithmPlotter. +These settings can be changed at any time from the "Settings" menu. + Nehmen Sie sich ein paar Sekunden Zeit, um LogarithmPlotter zu konfigurieren. +Diese Einstellungen können jederzeit über das Menü "Einstellungen" geändert werden. + + + Check for updates on startup (requires online connectivity) + Beim Start nach Updates suchen (Online-Verbindung erforderlich) + + + Reset redo stack when a new action is added to history + Redo-Stapel zurücksetzen, wenn eine neue Aktion zur Historie hinzugefügt wird + + + Enable LaTeX rendering + LaTeX-Rendering aktivieren + + + Automatically close parenthesises and brackets in expressions + Automatisches Schließen von Klammern in Ausdrücken + + + Enable syntax highlighting for expressions + Syntaxhervorhebung für Ausdrücke einschalten + + + Enable autocompletion interface in expression editor + Schnittstelle zur automatischen Vervollständigung im Ausdruckseditor aktivieren + + + Color scheme: + Syntaktische Färbung Thema: + + + + User manual + Benutzerhandbuch + + + + Changelog + Versionshinweise + + + + Preferences + Einstellung + + + + Close + Schließen + + + + HistoryBrowser + + Filter... + Filtern… + + + Redo > + Wiederherstellen > + + + > Now + > Aktueller Stand + + + < Undo + < Rückgängig + + + + ListSetting + + + + Add Entry + + Neuer Eintrag + + + + Loading + + + Loading... + Laden… + + + + Finished rendering of %1 + Beendetes Rendering von %1 + + + + LogarithmPlotter + + + untitled + unbetitelt + + + + Objects + Objekte + + + + Settings + Einstellungen + + + + History + Verlauf + + + Saved plot to '%1'. + Gespeicherte Grafik auf '%1'. + + + Loading file '%1'. + Laden der Datei '%1'. + + + Unknown object type: %1. + Unbekannter Objekttyp: %1. + + + Invalid file provided. + Ungültige Datei angegeben. + + + Could not save file: + Die Datei konnte nicht gespeichert werden: + + + Loaded file '%1'. + Geladene Datei '%1'. + + + + Copied plot screenshot to clipboard! + Grafik in die Zwischenablage kopiert! + + + + &Update + &Aktualisieren + + + + &Update LogarithmPlotter + LogarithmPlotter &aktualisieren + + + + ObjectCreationGrid + + + + Create new: + + Neu erstellen: + + + + ObjectLists + + + Hide all %1 + Alle %1 ausblenden + + + + Show all %1 + Alle %1 anzeigen + + + Hide %1 %2 + Ausblenden %1 %2 + + + Show %1 %2 + Anzeigen %1 %2 + + + Set %1 %2 position + Position von %1 %2 einstellen + + + Delete %1 %2 + %1 %2 löschen + + + Pick new color for %1 %2 + Neue Farbe für %1 %2 auswählen + + + + ObjectRow + + + Hide %1 %2 + Ausblenden %1 %2 + + + + Show %1 %2 + Anzeigen %1 %2 + + + + Set %1 %2 position + Position von %1 %2 einstellen + + + + Delete %1 %2 + %1 %2 löschen + + + + Pick new color for %1 %2 + Neue Farbe für %1 %2 auswählen + + + + PickLocation + + + Pointer precision: + Genauigkeit des Zeigers: + + + + Snap to grid: + Am Raster einrasten: + + + + Pick X + X nehmen + + + + Pick Y + Y nehmen + + + + Open picker settings + Zeigereinstellungen öffnen + + + + Hide picker settings + Zeigereinstellungen ausblenden + + + + (no pick selected) + (keine Auswahl ausgewählt) + + + + PickLocationOverlay + + Pointer precision: + Genauigkeit des Zeigers: + + + Snap to grid + Am Gitter einrasten + + + Snap to grid: + Am Raster einrasten: + + + Pick X + X nehmen + + + Pick Y + Y nehmen + + + Open picker settings + Zeigereinstellungen öffnen + + + Hide picker settings + Zeigereinstellungen ausblenden + + + (no pick selected) + (keine Auswahl ausgewählt) + + + + Preferences + + + Close + Schließen + + + + Settings + + + + X Zoom + Zoom auf X + + + + + Y Zoom + Zoom auf Y + + + + + Min X + Minimum X + + + + + Max Y + Maximum Y + + + + Max X + Maximum X + + + + Min Y + Minimum Y + + + + + X Axis Step + X-Achsen-Schritt + + + + + Y Axis Step + Y-Achsen-Schritt + + + + + Line width + Linienbreite + + + + + Text size (px) + Textgröße (px) + + + + + X Label + Etikett der X-Achse + + + + + Y Label + Etikett der Y-Achse + + + + + X Log scale + Logarithmische Skala in X + + + + + Show X graduation + X-Teilung anzeigen + + + + + Show Y graduation + Y-Teilung anzeigen + + + + Copy to clipboard + Kopieren in die Zwischenablage + + + + Save plot + Grafik speichern… + + + + Save plot as + Grafik speichern unter… + + + + Load plot + Grafik laden… + + + Close + Schließen + + + + ThanksTo + + + Thanks and Contributions - LogarithmPlotter + Danksagungen und Beiträge - LogarithmPlotter + + + + Source code + Quellcode + + + + Original library by Raphael Graf + Originalbibliothek von Raphael Graf + + + + Source + Quelle + + + + Ported to Javascript by Matthew Crumley + Portiert auf JavaScript von Matthew Crumley + + + + + + Website + Website + + + + Ported to QMLJS by Ad5001 + Portiert auf QMLJS von Ad5001 + + + + Libraries included + Einschließlich Bibliotheken + + + + Email + E-Mail + + + + English + Englisch + + + + French + Französisch + + + + German + Deutsch + + + + Hungarian + Ungarisch + + + + + + + Github + GitHub + + + + Norwegian + Norwegisch + + + + Spanish + Spanisch + + + + Tamil + Tamil + + + + Translations included + Einschließlich Übersetzungen + + + + Improve + Verbessern + + + + bodemagnitude + + + Bode Magnitude + Bode-Magnitude + + + + Bode Magnitudes + Bode-Magnituden + + + + + low-pass + Tiefpass + + + + + high-pass + Hochpass + + + + bodemagnitudesum + + + + Bode Magnitudes Sum + Bode-Magnituden Summe + + + + bodephase + + + Bode Phase + Bode-Phase + + + + Bode Phases + Bode-Phasen + + + + bodephasesum + + + + Bode Phases Sum + Bode-Phasen Summe + + + + changelog + + + Could not fetch changelog: Server error {}. + Changelog konnte nicht geholt werden: Server-Fehler {}. + + + + + Could not fetch update: {}. + Changelog konnte nicht geholt werden: {}. + + + + color + + + + %1 %2's color changed from %3 to %4. + %1 %2 wurde von %3 bis %4 umgefärbt. + + + + comment + + + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + Beispiel: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ-*), ]0;1[, {3;4;5} + + + + The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) + Die folgenden Parameter werden verwendet, wenn der Definitionsbereich eine nicht kontinuierliche Menge ist. (Beispiel: ℕ, ℤ, Mengen wie {0;3}...) + + + + Note: Specify the probability for each value. + Hinweis: Geben Sie die Wahrscheinlichkeit für jeden Wert an. + + + + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... + Hinweis: Verwenden Sie %1[n], um sich auf %1ₙ zu beziehen, %1[n+1] für %1ₙ₊₁… + + + + If you have latex enabled, you can use use latex markup in between $$ to create equations. + Wenn Sie Latex aktiviert haben, können Sie Latex-Auszeichnungen zwischen $$ verwenden, um Gleichungen zu erstellen. + + + + control + + + + + + + %1: + %1: + + + + create + + + + New %1 %2 created. + Neu %1 %2 erstellt. + + + + delete + + + + %1 %2 deleted. + %1 %2 gelöscht. + + + + distribution + + + Repartition + Verteilungsfunktion + + + + Repartition functions + Verteilungsfunktionen + + + + editproperty + + + %1 of %2 %3 changed from "%4" to "%5". + %1 von %2 %3 wurde von "%4" auf "%5" geändert. + + + + %1 of %2 changed from %3 to %4. + %1 von %2 wurde von %3 auf %4 geändert. + + + + error + + + + Cannot find property %1 of object %2. + Eigenschaft %1 von Objekt %2 kann nicht gefunden werden. + + + + Undefined variable %1. + Die Variable %1 ist nicht definiert. + + + + In order to be executed, object %1 must have at least one argument. + Um als Funktion verwendet zu werden, benötigt das Objekt %1 mindestens ein Parameter. + + + + %1 cannot be executed. + %1 ist keine Formel. + + + + + + Invalid expression. + Ungültiger Ausdruck. + + + + Invalid expression (parity). + Ungültiger Ausdruck (Parität). + + + + Unknown character "%1". + Unbekanntes Schriftzeichen "%1". + + + + + Illegal escape sequence: %1. + Unzulässige Escapesequenz: %1. + + + Parse error [%1:%2]: %3 + Analysefehler [%1:%2]: %3 + + + + Expected %1 + Erwartet %1 + + + + Unexpected %1 + Unerwartetes %1 + + + Function definition is not permitted. + Funktionsdefinition ist nicht erlaubt. + + + Expected variable for assignment. + Erwartete Variable für Zuweisung. + + + + + Parse error [position %1]: %2 + Analysefehler [Posten %1]: %2 + + + + Unexpected ".": member access is not permitted + Unerwartetes ".": Mitgliederzugriff ist nicht erlaubt + + + + Unexpected "[]": arrays are disabled. + Unerwartetes "[]": Arrays sind deaktiviert. + + + + Unexpected symbol: %1. + Unerwartetes Symbol: %1. + + + + + Function %1 must have at least one argument. + Die Funktion %1 benötigt mindestens ein Parameter. + + + First argument to map is not a function. + Der erste Parameter von map ist keine Formel. + + + Second argument to map is not an array. + Der zweite Parameter von map ist kein Array. + + + First argument to fold is not a function. + Der erste Parameter für fold ist keine Formel. + + + Second argument to fold is not an array. + Der zweite Parameter für fold ist kein Array. + + + First argument to filter is not a function. + Der erste Parameter für filter ist keine Formel. + + + Second argument to filter is not an array. + Der zweite Parameter von filter ist kein Array. + + + Second argument to indexOf is not a string or array. + Der zweite Parameter von indexOf ist kein String oder Array. + + + Second argument to join is not an array. + Der zweite Parameter von join ist kein Array. + + + + EOF + Ende des Ausdrucks + + + + No object found with names %1. + Kein Objekt mit Namen %1 gefunden. + + + + No object found with name %1. + Kein Objekt mit dem Namen %1 gefunden. + + + + Object cannot be dependent on itself. + Ein Objekt kann nicht von sich selbst abhängen. + + + + Circular dependency detected. Object %1 depends on %2. + Zirkuläre Abhängigkeit entdeckt. Objekt %1 hängt von %2 ab. + + + + Circular dependency detected. Objects %1 depend on %2. + Zirkuläre Abhängigkeit entdeckt. Objekte %1 hängen von %2 ab. + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Fehler beim Analysieren des Ausdrucks für die Eigenschaft %1: +%2 + +Ausdruck analysiert: %3 + + + + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + Fehler beim Versuch, das %1 %2 zu zeichnen: +%3 + +Die letzte Änderung wurde rückgängig gemacht. + + + + expression + + + + LogarithmPlotter - Parsing error + LogarithmPlotter - Analysefehler + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Fehler beim Analysieren des Ausdrucks für die Eigenschaft %1: +%2 + +Ausdruck analysiert: %3 + + + + LogarithmPlotter - Drawing error + LogarithmPlotter - Fehler + + + + Automatically close parenthesises and brackets + Klammern automatisch schließen + + + + Enable syntax highlighting + Syntaxhervorhebung einschalten + + + + Enable autocompletion + Automatische Vervollständigung einschalten + + + + Color Scheme + Syntaktische Färbung + + + + function + + + Function + Funktion + + + + Functions + Funktionen + + + + gainbode + + Bode Magnitude + Bode-Magnitude + + + Bode Magnitudes + Bode-Magnituden + + + low-pass + Tiefpass + + + high-pass + Hochpass + + + + general + + + Check for updates on startup + Beim Starten auf Updates prüfen + + + + Reset redo stack automaticly + Wiederherstellen-Stapel automatisch zurücksetzen + + + + Enable LaTeX rendering + LaTeX-Rendering aktivieren + + + + Enable threaded LaTeX renderer (experimental) + LaTeX-Renderer mit Threads aktivieren (experimentell) + + + + historylib + + New %1 %2 created. + Neu %1 %2 erstellt. + + + %1 %2 deleted. + %1 %2 gelöscht. + + + %1 of %2 %3 changed from "%4" to "%5". + %1 von %2 %3 wurde von "%4" auf "%5" geändert. + + + %1 %2 shown. + %1 %2 angezeigt. + + + %1 %2 hidden. + %1 %2 ausgeblendet. + + + Name of %1 %2 changed to %3. + Der Name von %1 %2 wurde in %3 geändert. + + + + io + + Objects + Objekte + + + Settings + Einstellungen + + + History + Verlauf + + + Saved plot to '%1'. + Gespeicherte Grafik auf '%1'. + + + Loading file '%1'. + Laden der Datei '%1'. + + + Unknown object type: %1. + Unbekannter Objekttyp: %1. + + + Invalid file provided. + Ungültige Datei angegeben. + + + Could not load file: + Datei konnte nicht geladen werden: + + + Could not save file: + Die Datei konnte nicht gespeichert werden: + + + Loaded file '%1'. + Geladene Datei '%1'. + + + Copied plot screenshot to clipboard! + Grafik in die Zwischenablage kopiert! + + + &Update + &Aktualisieren + + + &Update LogarithmPlotter + LogarithmPlotter &aktualisieren + + + + latex + + + No Latex installation found. +If you already have a latex distribution installed, make sure it's installed on your path. +Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. + Keine LaTeX-Installation gefunden. +Wenn Sie bereits eine LaTeX-Distribution installiert haben, vergewissern Sie sich, dass sie in Ihrem Pfad installiert ist. +Andernfalls können Sie eine LaTeX-Distribution wie TeX Live unter https://tug.org/texlive/ herunterladen. + + + + DVIPNG was not found. Make sure you include it from your Latex distribution. + DVIPNG wurde nicht gefunden. Stellen Sie sicher, dass Sie es aus Ihrer LaTeX-Distribution einbinden. + + + + An exception occured within the creation of the latex formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your latex installation is correct and report a bug if so. + Bei der Erstellung der LaTeX-Formel ist eine Exception aufgetreten. +Der Prozess '{}' wurde mit einem Rückgabecode ungleich Null beendet {}: + +{} +Bitte vergewissern Sie sich, dass Ihre LaTeX-Installation korrekt ist, und melden Sie einen Fehler, falls dies der Fall ist. + + + + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + Ihre LaTeX-Installation enthält einige erforderliche Pakete nicht: + +- {} (https://ctan.org/pkg/{}) + +Stellen Sie sicher, dass diese Pakete installiert sind, oder deaktivieren Sie das LaTeX-Rendering in LogarithmPlotter. + + + + An exception occured within the creation of the latex formula. +Process '{}' took too long to finish: +{} +Please make sure your latex installation is correct and report a bug if so. + Bei der Erstellung der LaTeX-Formel ist eine Exception aufgetreten. +Der Prozess '{}' brauchte zu lange, um beendet zu werden: +{} +Bitte vergewissern Sie sich, dass Ihre LaTeX-Installation korrekt ist, und melden Sie einen Fehler, falls dies der Fall ist. + + + + main + + + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + Diese Datei wurde mit einer neueren Version von LogarithmPlotter erstellt und kann nicht in LogarithmPlotter v{} zurückgeladen werden. +Bitte aktualisieren Sie LogarithmPlotter, um diese Datei zu öffnen. + + + + Could not open file "{}": +{} + Die Datei „{}“ konnte nicht geöffnet werden: +{} + + + + Could not open file: "{}" +File does not exist. + Die Datei „{}“ konnte nicht geöffnet werden: +Die Datei existiert nicht. + + + + Built with PySide6 (Qt) v{} and python v{} + Kompiliert mit PySide6 (Qt) v{} und python v{} + + + + name + + + + %1 %2 renamed to %3. + %1 %2 umbenannt in %3. + + + + parameters + + + above + ↑ Über + + + + below + ↓ Unter + + + + + left + ← Links + + + + + right + → Rechts + + + + above-left + ↖ Oben links + + + + above-right + ↗ Oben rechts + + + + below-left + ↙ Unten links + + + + below-right + ↘ Unten rechts + + + + center + >|< Zentrum + + + + top + ↑ Über + + + + bottom + ↓ Unter + + + + top-left + ↖ Oben links + + + + top-right + ↗ Oben rechts + + + + bottom-left + ↙ Unten links + + + + bottom-right + ↘ Unten rechts + + + + application + Anwendung + + + + function + Funktion + + + + high + Hoch + + + + low + Tief + + + + Next to target + Neben dem Ziel + + + + With label + Mit Etikett + + + + Hidden + Versteckt + + + + phasebode + + Bode Phase + Bode-Phase + + + Bode Phases + Bode-Phasen + + + + point + + + Point + Punkt + + + + Points + Punkte + + + + position + + + Position of %1 %2 set from "%3" to "%4". + %1 %2 wurde von "%3" nach "%4" verschoben. + + + + Position of %1 set from %2 to %3. + %1 wurde von %2 nach %3 verschoben. + + + + prop + + + expression + Ausdruck + + + + definitionDomain + Definitionsbereich + + + + destinationDomain + Reichweite + + + + + + + + + + + + + labelPosition + Position des Etiketts + + + + displayMode + Anzeigemodus + + + + + + + + + + labelX + X-Position des Etiketts + + + + + drawPoints + Unentschiedene Punkte + + + + + drawDashedLines + Gestrichelte Linien anzeigen + + + + + om_0 + ω₀ + + + + pass + Pass + + + + gain + Größenordnung + + + + omGraduation + Teilung auf ω zeigen + + + + phase + Phase + + + + unit + Einheit + + + + + + x + X + + + + + y + Y + + + + pointStyle + Punkt-Stil + + + + probabilities + Wahrscheinlichkeiten + + + + text + Inhalt + + + + disableLatex + LaTeX-Rendering für diesen Text deaktivieren + + + + targetElement + Zielobjekt + + + + approximate + Berechneten Wert gerundet anzeigen + + + + rounding + Rundung + + + + displayStyle + Stil + + + + targetValuePosition + Wertposition des Ziels + + + + defaultExpression + Standardausdruck + + + + baseValues + Initialisierungswerte + + + color + Farbe + + + + labelContent + Etikett + + + + repartition + + Repartition + Verteilungsfunktion + + + Repartition functions + Verteilungsfunktionen + + + + sequence + + + Sequence + Folge + + + + Sequences + Folgen + + + + settingCategory + + + default + Standardeinstellungen + + + + general + Allgemeine + + + + editor + Ausdruckseditor + + + + sommegainsbode + + Bode Magnitudes Sum + Bode-Magnituden Summe + + + + sommephasesbode + + Bode Phases Sum + Bode-Phasen Summe + + + + text + + + Text + Text + + + + Texts + Texte + + + + update + + + An update for LogarithmPlotter (v{}) is available. + Ein Update für LogarithmPlotter (v{}) ist verfügbar. + + + + No update available. + Keine Aktualisierung verfügbar. + + + + Could not fetch update information: Server error {}. + Es konnten keine Aktualisierungsinformationen abgerufen werden: Server-Fehler {}. + + + + Could not fetch update information: {}. + Es konnten keine Aktualisierungsinformationen abgerufen werden:{}. + + + + usage + + + integral(<from: number>, <to: number>, <f: ExecutableObject>) + integral(<von: Zahl>, <bis: Zahl>, <f: Funktionsähnliches Objekt>) + + + + + Usage: +%1 + Verwendung: +%1 + + + + + + Usage: +%1 +%2 + Verwendung: +%1 +%2 + + + + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + integral(<von: Zahl>, <bis: Zahl>, <f: String>, <Variablen: String>) + + + + derivative(<f: ExecutableObject>, <x: number>) + derivative(<f: Funktionsähnliches Objekt>, <x: Zahl>) + + + + derivative(<f: string>, <variable: string>, <x: number>) + derivative(<f: String>, <Variablen: String>, <x: Zahl>) + + + + visibility + + + + %1 %2 shown. + %1 %2 angezeigt. + + + + + %1 %2 hidden. + %1 %2 ausgeblendet. + + + + xcursor + + + X Cursor + X Zeiger + + + + X Cursors + X Zeiger + + + diff --git a/assets/i18n/lp_en.ts b/assets/i18n/lp_en.ts new file mode 100644 index 0000000..68590af --- /dev/null +++ b/assets/i18n/lp_en.ts @@ -0,0 +1,1996 @@ + + + + + About + + + About LogarithmPlotter + About LogarithmPlotter + + + + LogarithmPlotter v%1 + LogarithmPlotter v%1 + + + + 2D plotter software to make BODE plots, sequences and repartition functions. + 2D plotter software to make Bode plots, sequences and distribution functions. + + + + Report a bug + Report a bug + + + + Official website + Official website + + + + AppMenuBar + + + &File + &File + + + + &Load... + &Open… + + + + &Save + &Save + + + + Save &As... + Save &As… + + + + &Quit + &Quit + + + + &Edit + &Edit + + + + &Undo + &Undo + + + + &Redo + &Redo + + + + &Copy plot + &Copy plot + + + + &Preferences + &Preferences + + + + &Create + &Create + + + &Settings + &Settings + + + Check for updates on startup + Check for updates on startup + + + Reset redo stack automaticly + Reset redo stack automatically + + + Enable LaTeX rendering + Enable LaTeX rendering + + + Expression editor + Expression editor + + + Automatically close parenthesises and brackets + Automatically close parentheses and brackets + + + Enable syntax highlighting + Enable syntax highlighting + + + Enable autocompletion + Enable autocompletion + + + Color Scheme + Color Scheme + + + + &Help + &Help + + + + &Source code + &Source code + + + + &Report a bug + &Report a bug + + + + &User manual + &User manual + + + + &Changelog + &Changelog + + + + &Help translating! + &Help translating! + + + + &Thanks + &Thanks + + + + &About + &About + + + + Save unsaved changes? + Save unsaved changes? + + + + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + + + + BaseDialog + + + Close + Close + + + + BoolSetting + + Check for updates on startup + Check for updates on startup + + + + Browser + + + Filter... + Filter… + + + + Redo > + Redo > + + + + > Now + > Now + + + + < Undo + < Undo + + + + Changelog + + + Fetching changelog... + Fetching changelog… + + + + Close + Close + + + + CustomPropertyList + + + + + Create new %1 + + Create new %1 + + + + Pick on graph + Pick on graph + + + + Dialog + + + Edit properties of %1 %2 + Edit properties of %1 %2 + + + + LogarithmPlotter - Invalid object name + LogarithmPlotter - Invalid object name + + + + An object with the name '%1' already exists. + An object with the name '%1' already exists. + + + + Name + Name + + + Label content + Label content + + + + null + null + + + + name + name + + + + name + value + name + value + + + + EditorDialog + + Edit properties of %1 %2 + Edit properties of %1 %2 + + + Name + Name + + + Label content + Label content + + + null + null + + + name + name + + + name + value + name + value + + + + Create new %1 + + Create new %1 + + + + ExpressionEditor + + + Object Properties + Object Properties + + + + Variables + Variables + + + + Constants + Constants + + + + Functions + Functions + + + + Executable Objects + Function Objects + + + + Objects + Objects + + + + FileDialog + + + Export Logarithm Plot file + Export Logarithm Plot file + + + + Import Logarithm Plot file + Import Logarithm Plot file + + + + GreetScreen + + + Welcome to LogarithmPlotter + Welcome to LogarithmPlotter + + + + Version %1 + Version %1 + + + Take a few seconds to configure LogarithmPlotter. +These settings can be changed at any time from the "Settings" menu. + Take a few seconds to configure LogarithmPlotter. +These settings can be changed at any time from the "Settings" menu. + + + Check for updates on startup (requires online connectivity) + Check for updates on startup (requires online connectivity) + + + Reset redo stack when a new action is added to history + Reset redo stack when a new action is added to history + + + Enable LaTeX rendering + Enable LaTeX rendering + + + Automatically close parenthesises and brackets in expressions + Automatically close parentheses and brackets in expressions + + + Enable syntax highlighting for expressions + Enable syntax highlighting for expressions + + + Enable autocompletion interface in expression editor + Enable autocompletion interface in expression editor + + + Color scheme: + Color scheme: + + + + User manual + User manual + + + + Changelog + Changelog + + + + Preferences + Preferences + + + + Close + Close + + + + HistoryBrowser + + Filter... + Filter… + + + Redo > + Redo > + + + > Now + > Now + + + < Undo + < Undo + + + + ListSetting + + + + Add Entry + + Add Entry + + + + Loading + + + Loading... + Loading… + + + + Finished rendering of %1 + Finished rendering of %1 + + + + LogarithmPlotter + + + untitled + untitled + + + + Objects + Objects + + + + Settings + Settings + + + + History + History + + + Saved plot to '%1'. + Saved plot to '%1'. + + + Loading file '%1'. + Loading file '%1'. + + + Unknown object type: %1. + Unknown object type: %1. + + + Invalid file provided. + Invalid file provided. + + + Could not save file: + Could not save file: + + + Loaded file '%1'. + Loaded file '%1'. + + + + Copied plot screenshot to clipboard! + Copied plot screenshot to clipboard! + + + + &Update + &Update + + + + &Update LogarithmPlotter + &Update LogarithmPlotter + + + + ObjectCreationGrid + + + + Create new: + + Create new: + + + + ObjectLists + + + Hide all %1 + Hide all %1 + + + + Show all %1 + Show all %1 + + + Hide %1 %2 + Hide %1 %2 + + + Show %1 %2 + Show %1 %2 + + + Set %1 %2 position + Set %1 %2 position + + + Delete %1 %2 + Delete %1 %2 + + + Pick new color for %1 %2 + Pick new color for %1 %2 + + + + ObjectRow + + + Hide %1 %2 + Hide %1 %2 + + + + Show %1 %2 + Show %1 %2 + + + + Set %1 %2 position + Set %1 %2 position + + + + Delete %1 %2 + Delete %1 %2 + + + + Pick new color for %1 %2 + Pick new color for %1 %2 + + + + PickLocation + + + Pointer precision: + Pointer precision: + + + + Snap to grid: + Snap to grid: + + + + Pick X + Pick X + + + + Pick Y + Pick Y + + + + Open picker settings + Open picker settings + + + + Hide picker settings + Hide picker settings + + + + (no pick selected) + (no pick selected) + + + + PickLocationOverlay + + Pointer precision: + Pointer precision: + + + Snap to grid + Snap to grid + + + Snap to grid: + Snap to grid: + + + Pick X + Pick X + + + Pick Y + Pick Y + + + Open picker settings + Open picker settings + + + Hide picker settings + Hide picker settings + + + (no pick selected) + (no pick selected) + + + + Preferences + + + Close + Close + + + + Settings + + + + X Zoom + X Zoom + + + + + Y Zoom + Y Zoom + + + + + Min X + Min X + + + + + Max Y + Max Y + + + + Max X + Max X + + + + Min Y + Min Y + + + + + X Axis Step + X Axis Step + + + + + Y Axis Step + Y Axis Step + + + + + Line width + Line width + + + + + Text size (px) + Text size (px) + + + + + X Label + X Label + + + + + Y Label + Y Label + + + + + X Log scale + X Log scale + + + + + Show X graduation + Show X graduation + + + + + Show Y graduation + Show Y graduation + + + + Copy to clipboard + Copy to clipboard + + + + Save plot + Save plot… + + + + Save plot as + Save plot as… + + + + Load plot + Open plot… + + + Close + Close + + + + ThanksTo + + + Thanks and Contributions - LogarithmPlotter + Thanks and Contributions - LogarithmPlotter + + + + Source code + Source code + + + + Original library by Raphael Graf + Original library by Raphael Graf + + + + Source + Source + + + + Ported to Javascript by Matthew Crumley + Ported to JavaScript by Matthew Crumley + + + + + + Website + Website + + + + Ported to QMLJS by Ad5001 + Ported to QMLJS by Ad5001 + + + + Libraries included + Libraries included + + + + Email + Email + + + + English + English + + + + French + French + + + + German + German + + + + Hungarian + Hungarian + + + + + + + Github + GitHub + + + + Norwegian + Norwegian + + + + Spanish + Spanish + + + + Tamil + Tamil + + + + Translations included + Translations included + + + + Improve + Improve + + + + bodemagnitude + + + Bode Magnitude + Bode Magnitude + + + + Bode Magnitudes + Bode Magnitudes + + + + + low-pass + low-pass + + + + + high-pass + high-pass + + + + bodemagnitudesum + + + + Bode Magnitudes Sum + Bode Magnitudes Sum + + + + bodephase + + + Bode Phase + Bode Phase + + + + Bode Phases + Bode Phases + + + + bodephasesum + + + + Bode Phases Sum + Bode Phases Sum + + + + changelog + + + Could not fetch changelog: Server error {}. + Could not fetch changelog: Server error {}. + + + + + Could not fetch update: {}. + Could not fetch changelog: {}. + + + + color + + + + %1 %2's color changed from %3 to %4. + %1 %2's color changed from %3 to %4. + + + + comment + + + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + + + + The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) + The following parameters are used when the domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}…) + + + + Note: Specify the probability for each value. + Note: Specify the probability for each value. + + + + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁… + + + + If you have latex enabled, you can use use latex markup in between $$ to create equations. + If you have latex enabled, you can use use latex markup in between $$ to create equations. + + + + control + + + + + + + %1: + %1: + + + + create + + + + New %1 %2 created. + New %1 %2 created. + + + + delete + + + + %1 %2 deleted. + %1 %2 deleted. + + + + distribution + + + Repartition + Distribution + + + + Repartition functions + Distribution functions + + + + editproperty + + + %1 of %2 %3 changed from "%4" to "%5". + %1 of %2 %3 changed from "%4" to "%5". + + + + %1 of %2 changed from %3 to %4. + %1 of %2 changed from %3 to %4. + + + + error + + + + Cannot find property %1 of object %2. + Cannot find property %1 of object %2. + + + + Undefined variable %1. + Undefined variable %1. + + + + In order to be executed, object %1 must have at least one argument. + In order to be executed, object %1 must have at least one argument. + + + + %1 cannot be executed. + %1 is not a function. + + + + + + Invalid expression. + Invalid expression. + + + + Invalid expression (parity). + Invalid expression (parity). + + + + Unknown character "%1". + Unknown character "%1". + + + + + Illegal escape sequence: %1. + Illegal escape sequence: %1. + + + Parse error [%1:%2]: %3 + Parse error [%1:%2]: %3 + + + + Expected %1 + Expected %1 + + + + Unexpected %1 + Unexpected %1 + + + Function definition is not permitted. + Function definition is not permitted. + + + Expected variable for assignment. + Expected variable for assignment. + + + + + Parse error [position %1]: %2 + Parse error [position %1]: %2 + + + + Unexpected ".": member access is not permitted + Unexpected ".": member access is not permitted + + + + Unexpected "[]": arrays are disabled. + Unexpected "[]": arrays are disabled. + + + + Unexpected symbol: %1. + Unexpected symbol: %1. + + + + + Function %1 must have at least one argument. + Function %1 must have at least one argument. + + + First argument to map is not a function. + First argument to map is not a function. + + + Second argument to map is not an array. + Second argument to map is not an array. + + + First argument to fold is not a function. + First argument to fold is not a function. + + + Second argument to fold is not an array. + Second argument to fold is not an array. + + + First argument to filter is not a function. + First argument to filter is not a function. + + + Second argument to filter is not an array. + Second argument to filter is not an array. + + + Second argument to indexOf is not a string or array. + Second argument to indexOf is not a string or array. + + + Second argument to join is not an array. + Second argument to join is not an array. + + + + EOF + End of expression + + + + No object found with names %1. + No object found with names %1. + + + + No object found with name %1. + No object found with name %1. + + + + Object cannot be dependent on itself. + Object cannot be dependent on itself. + + + + Circular dependency detected. Object %1 depends on %2. + Circular dependency detected. Object %1 depends on %2. + + + + Circular dependency detected. Objects %1 depend on %2. + Circular dependency detected. Objects %1 depend on %2. + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + + + + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + + + + expression + + + + LogarithmPlotter - Parsing error + LogarithmPlotter - Parsing error + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + + + + LogarithmPlotter - Drawing error + LogarithmPlotter - Drawing error + + + + Automatically close parenthesises and brackets + Automatically close parentheses and brackets + + + + Enable syntax highlighting + Enable syntax highlighting + + + + Enable autocompletion + Enable autocompletion + + + + Color Scheme + Color Scheme + + + + function + + + Function + Function + + + + Functions + Functions + + + + gainbode + + Bode Magnitude + Bode Magnitude + + + Bode Magnitudes + Bode Magnitudes + + + low-pass + low-pass + + + high-pass + high-pass + + + + general + + + Check for updates on startup + Check for updates on startup + + + + Reset redo stack automaticly + Reset redo stack automatically + + + + Enable LaTeX rendering + Enable LaTeX rendering + + + + Enable threaded LaTeX renderer (experimental) + Enable threaded LaTeX renderer (experimental) + + + + historylib + + New %1 %2 created. + New %1 %2 created. + + + %1 %2 deleted. + %1 %2 deleted. + + + %1 of %2 %3 changed from "%4" to "%5". + %1 of %2 %3 changed from "%4" to "%5". + + + %1 %2 shown. + %1 %2 shown. + + + %1 %2 hidden. + %1 %2 hidden. + + + Name of %1 %2 changed to %3. + Name of %1 %2 changed to %3. + + + + io + + Objects + Objects + + + Settings + Settings + + + History + History + + + Saved plot to '%1'. + Saved plot to '%1'. + + + Loading file '%1'. + Loading file '%1'. + + + Unknown object type: %1. + Unknown object type: %1. + + + Invalid file provided. + Invalid file provided. + + + Could not load file: + Could not load file: + + + Could not save file: + Could not save file: + + + Loaded file '%1'. + Loaded file '%1'. + + + Copied plot screenshot to clipboard! + Copied plot screenshot to clipboard! + + + &Update + &Update + + + &Update LogarithmPlotter + &Update LogarithmPlotter + + + + latex + + + No Latex installation found. +If you already have a latex distribution installed, make sure it's installed on your path. +Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. + No LaTeX installation found. +If you already have a LaTeX distribution installed, make sure it's installed on your path. +Otherwise, you can download a LaTeX distribution like TeX Live at https://tug.org/texlive/. + + + + DVIPNG was not found. Make sure you include it from your Latex distribution. + DVIPNG was not found. Make sure you include it from your LaTeX distribution. + + + + An exception occured within the creation of the latex formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your latex installation is correct and report a bug if so. + An exception occurred within the creation of the LaTeX formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your LaTeX installation is correct and report a bug if so. + + + + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + + + + An exception occured within the creation of the latex formula. +Process '{}' took too long to finish: +{} +Please make sure your latex installation is correct and report a bug if so. + An exception occurred within the creation of the LaTeX formula. +Process '{}' took too long to finish: +{} +Please make sure your LaTeX installation is correct and report a bug if so. + + + + main + + + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + + + + Could not open file "{}": +{} + Could not open file "{}": +{} + + + + Could not open file: "{}" +File does not exist. + Could not open file: "{}" +File does not exist. + + + + Built with PySide6 (Qt) v{} and python v{} + Built with PySide6 (Qt) v{} and python v{} + + + + name + + + + %1 %2 renamed to %3. + %1 %2 renamed to %3. + + + + parameters + + + above + ↑ Above + + + + below + ↓ Below + + + + + left + ← Left + + + + + right + → Right + + + + above-left + ↖ Above left + + + + above-right + ↗ Above right + + + + below-left + ↙ Below left + + + + below-right + ↘ Below right + + + + center + >|< Center + + + + top + ↑ Top + + + + bottom + ↓ Bottom + + + + top-left + ↖ Top left + + + + top-right + ↗ Top right + + + + bottom-left + ↙ Bottom left + + + + bottom-right + ↘ Bottom right + + + + application + Application + + + + function + Function + + + + high + High + + + + low + Low + + + + Next to target + Next to target + + + + With label + With label + + + + Hidden + Hidden + + + + phasebode + + Bode Phase + Bode Phase + + + Bode Phases + Bode Phases + + + + point + + + Point + Point + + + + Points + Points + + + + position + + + Position of %1 %2 set from "%3" to "%4". + %1 %2 moved from "%3" to "%4". + + + + Position of %1 set from %2 to %3. + %1 moved from %2 to %3. + + + + prop + + + expression + Expression + + + + definitionDomain + Domain + + + + destinationDomain + Range + + + + + + + + + + + + + labelPosition + Label position + + + + displayMode + Display mode + + + + + + + + + + labelX + Label's X position + + + + + drawPoints + Show points + + + + + drawDashedLines + Show dashed lines + + + + + om_0 + ω₀ + + + + pass + Pass + + + + gain + Magnitude gain + + + + omGraduation + Show graduation on ω₀ + + + + phase + Phase + + + + unit + Unit to use + + + + + + x + X + + + + + y + Y + + + + pointStyle + Point style + + + + probabilities + Probabilities list + + + + text + Content + + + + disableLatex + Disable LaTeX rendering for this text + + + + targetElement + Object to target + + + + approximate + Show rounded calculated value + + + + rounding + Rounding + + + + displayStyle + Display style + + + + targetValuePosition + Target's value position + + + + defaultExpression + Default expression + + + + baseValues + Initialization values + + + color + Color + + + + labelContent + Label content + + + + repartition + + Repartition + Distribution + + + Repartition functions + Distribution functions + + + + sequence + + + Sequence + Sequence + + + + Sequences + Sequences + + + + settingCategory + + + general + General + + + + editor + Expression Editor + + + + default + Default settings + + + + sommegainsbode + + Bode Magnitudes Sum + Bode Magnitudes Sum + + + + sommephasesbode + + Bode Phases Sum + Bode Phases Sum + + + + text + + + Text + Text + + + + Texts + Texts + + + + update + + + An update for LogarithmPlotter (v{}) is available. + An update for LogarithmPlotter (v{}) is available. + + + + No update available. + No update available. + + + + Could not fetch update information: Server error {}. + Could not fetch update information: Server error {}. + + + + Could not fetch update information: {}. + Could not fetch update information: {}. + + + + usage + + + integral(<from: number>, <to: number>, <f: ExecutableObject>) + integral(<from: number>, <to: number>, <f: Function-like object>) + + + + + Usage: +%1 + Usage: +%1 + + + + + + Usage: +%1 +%2 + Usage: +%1 +%2 + + + + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + + + + derivative(<f: ExecutableObject>, <x: number>) + derivative(<f: Function-like object>, <x: number>) + + + + derivative(<f: string>, <variable: string>, <x: number>) + derivative(<f: string>, <variable: string>, <x: number>) + + + + visibility + + + + %1 %2 shown. + %1 %2 shown. + + + + + %1 %2 hidden. + %1 %2 hidden. + + + + xcursor + + + X Cursor + X Cursor + + + + X Cursors + X Cursors + + + diff --git a/assets/i18n/lp_es.ts b/assets/i18n/lp_es.ts new file mode 100644 index 0000000..b1e5d0a --- /dev/null +++ b/assets/i18n/lp_es.ts @@ -0,0 +1,1985 @@ + + + + + About + + + About LogarithmPlotter + Sobre LogarithmPlotter + + + + LogarithmPlotter v%1 + LogarithmPlotter v%1 + + + + 2D plotter software to make BODE plots, sequences and repartition functions. + Software de trazado 2D para realizar gráficos de Bode, secuencias y funciones de distribución. + + + + Report a bug + Informar de un error + + + + Official website + Sitio web oficial + + + + AppMenuBar + + + &File + &Archivo + + + + &Load... + &Abrir… + + + + &Save + &Guardar + + + + Save &As... + Guardar &como… + + + + &Quit + &Salida + + + + &Edit + &Editar + + + + &Undo + &Cancelar + + + + &Redo + &Reiniciar + + + + &Copy plot + &Copiar el gráfico + + + + &Preferences + &Preferencias + + + + &Create + &Crear + + + &Settings + &Ajustes + + + Check for updates on startup + Comprobación de las actualizaciones al arrancar + + + Reset redo stack automaticly + Restablecer la pila de rehacer automáticamente + + + Enable LaTeX rendering + Activar el renderizado de LaTeX + + + Automatically close parenthesises and brackets + Cerrar automáticamente paréntesis y corchetes + + + Enable syntax highlighting + Activar el resaltado sintáctico + + + Enable autocompletion + Activar autocompletar + + + Color Scheme + Esquema de colores + + + + &Help + &Ayuda + + + + &Source code + &Código fuente + + + + &Report a bug + &Informar de un error + + + + &User manual + &Manual del usuario + + + + &Changelog + &Registro de cambios + + + + &Help translating! + &¡Ayuda a la traducción! + + + + &Thanks + &Agradecimientos + + + + &About + &Acerca de + + + + Save unsaved changes? + ¿Guardar los cambios no guardados? + + + + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + Este gráfico contiene cambios sin guardar. Al hacer esto, se perderán todos los datos no guardados. ¿Continuar? + + + Expression editor + Editor de expresiones + + + + BaseDialog + + + Close + Cerrar + + + + BoolSetting + + Check for updates on startup + Comprobación de las actualizaciones al arrancar + + + + Browser + + + Filter... + + + + + Redo > + Rehacer > + + + + > Now + > Ahora + + + + < Undo + < Deshacer + + + + Changelog + + + Fetching changelog... + Obteniendo el registro de cambios… + + + + Close + Cerrar + + + + CustomPropertyList + + + + + Create new %1 + + Crear nuevo %1 + + + + Pick on graph + Elegir en el gráfico + + + + Dialog + + + Edit properties of %1 %2 + Editar las propiedades de %1 %2 + + + + LogarithmPlotter - Invalid object name + LogarithmPlotter - Nombre de objeto no válido + + + + An object with the name '%1' already exists. + Ya existe un objeto con el nombre '%1'. + + + + Name + + + + + null + + + + + name + + + + + name + value + nombre + valor + + + + EditorDialog + + Label content + Contenido de la etiqueta + + + null + nulo + + + name + nombre + + + name + value + nombre + valor + + + + Create new %1 + + Crear nuevo %1 + + + Edit properties of %1 %2 + Editar propiedades de %1 %2 + + + Name + Nombre + + + + ExpressionEditor + + + Object Properties + Propiedades de los objetos + + + + Variables + + + + + Constants + Constantes + + + + Functions + Funciones + + + + Executable Objects + Objetos de función + + + + Objects + Objetos + + + + FileDialog + + + Export Logarithm Plot file + Exportar archivo de gráfico de logaritmos + + + + Import Logarithm Plot file + Importar archivo de gráfico de logaritmos + + + + GreetScreen + + + Welcome to LogarithmPlotter + Bienvenid@ a LogarithmPlotter + + + + Version %1 + + + + Enable LaTeX rendering + Activar el renderizado de LaTeX + + + + User manual + + + + + Changelog + Registro de cambios + + + + Preferences + Preferencias + + + + Close + Cerrar + + + Color scheme: + Esquema de colores: + + + Take a few seconds to configure LogarithmPlotter. +These settings can be changed at any time from the "Settings" menu. + Tómate unos segundos para configurar LogarithmPlotter. +Estos ajustes se pueden cambiar en cualquier momento desde el menú “Ajustes”. + + + Check for updates on startup (requires online connectivity) + Buscar actualizaciones al iniciar (requiere conexión a Internet) + + + Reset redo stack when a new action is added to history + Restablecer el historial de deshacer cuando se agrega una nueva acción + + + Automatically close parenthesises and brackets in expressions + Cerrar automáticamente paréntesis y corchetes en expresiones + + + Enable autocompletion interface in expression editor + Habilitar el autocompletado en el editor de expresiones + + + Enable syntax highlighting for expressions + Habilitar el resaltado de sintaxis para expresiones + + + + HistoryBrowser + + Redo > + Rehacer > + + + > Now + > Ahora + + + < Undo + < Deshacer + + + + ListSetting + + + + Add Entry + + Añadir entrada + + + + Loading + + + Loading... + + + + + Finished rendering of %1 + + + + + LogarithmPlotter + + + untitled + + + + + Objects + Objetos + + + + Settings + Ajustes + + + + History + Historial + + + + Copied plot screenshot to clipboard! + ¡Captura de pantalla del gráfico copiada al portapapeles! + + + + &Update + &Actualizar + + + + &Update LogarithmPlotter + &Actualizar LogarithmPlotter + + + Unknown object type: %1. + Tipo de objeto desconocido: %1 . + + + Saved plot to '%1'. + Gráfico guardado en '%1'. + + + Loading file '%1'. + Cargando el archivo '%1'. + + + Invalid file provided. + Se ha proporcionado un archivo no válido. + + + Could not save file: + No se ha podido guardar el archivo: + + + Loaded file '%1'. + Archivo cargado '%1'. + + + + ObjectCreationGrid + + + + Create new: + + Crear nuevo: + + + + ObjectLists + + + Hide all %1 + Ocultar todo %1 + + + + Show all %1 + Mostrar todo %1 + + + Delete %1 %2 + Borrar %1 %2 + + + Hide %1 %2 + Ocultar %1 %2 + + + Show %1 %2 + Mostrar %1 %2 + + + Set %1 %2 position + Fijar la posición %1 %2 + + + Pick new color for %1 %2 + Elegir nuevo color para %1 %2 + + + + ObjectRow + + + Hide %1 %2 + Ocultar %1 %2 + + + + Show %1 %2 + Mostrar %1 %2 + + + + Set %1 %2 position + + + + + Delete %1 %2 + + + + + Pick new color for %1 %2 + Elegir nuevo color para %1 %2 + + + + PickLocation + + + Pointer precision: + Precisión del puntero: + + + + Snap to grid: + Ajustar a la cuadrícula: + + + + Pick X + Elige X + + + + Pick Y + Elige Y + + + + Open picker settings + Abrir los ajustes del selector + + + + Hide picker settings + Ocultar ajustes del selector + + + + (no pick selected) + (sin selección) + + + + PickLocationOverlay + + Pointer precision: + Precisión del puntero: + + + Snap to grid: + Ajustar a la cuadrícula: + + + Pick X + Elige X + + + Pick Y + Elige Y + + + Open picker settings + Abrir los ajustes del selector + + + Hide picker settings + Ocultar ajustes del selector + + + (no pick selected) + (sin selección) + + + Snap to grid + Ajustar a la cuadrícula + + + + Preferences + + + Close + Cerrar + + + + Settings + + + + X Zoom + + + + + + Y Zoom + + + + + + Min X + + + + + + Max Y + + + + + Max X + + + + + Min Y + + + + + + X Axis Step + Paso por eje X + + + + + Y Axis Step + Paso por eje Y + + + + + Line width + Anchura de la línea + + + + + Text size (px) + Tamaño del texto (px) + + + + + X Label + + + + + + Y Label + + + + + + X Log scale + Escala logarítmica en X + + + + + Show X graduation + Mostrar graduación del eje X + + + + + Show Y graduation + Mostrar graduación del eje Y + + + + Copy to clipboard + Copiar al portapapeles + + + + Save plot + Guardar gráfico… + + + + Save plot as + Guardar gráfico como… + + + + Load plot + Abrir gráfico… + + + Close + Cerrar + + + + ThanksTo + + + Thanks and Contributions - LogarithmPlotter + Agradecimientos y colaboraciones - LogarithmPlotter + + + + Source code + + + + + Original library by Raphael Graf + Biblioteca original de Raphael Graf + + + + Source + + + + + Ported to Javascript by Matthew Crumley + Portado a JavaScript por Matthew Crumley + + + + + + Website + + + + + Ported to QMLJS by Ad5001 + Portado a QMLJS por Ad5001 + + + + Libraries included + Bibliotecas incluidas + + + + Email + + + + + English + + + + + French + + + + + German + + + + + Hungarian + + + + + + + + Github + GitHub + + + + Norwegian + + + + + Spanish + Español + + + + Tamil + + + + + Translations included + Traducciones incluidas + + + + Improve + Mejorar + + + + bodemagnitude + + + Bode Magnitude + Magnitud de Bode + + + + Bode Magnitudes + Magnitudes de Bode + + + + + low-pass + Filtro paso bajo + + + + + high-pass + Filtro paso alto + + + + bodemagnitudesum + + + + Bode Magnitudes Sum + Suma de las Magnitudes de Bode + + + + bodephase + + + Bode Phase + Fase de Bode + + + + Bode Phases + Fases de Bode + + + + bodephasesum + + + + Bode Phases Sum + Suma de las fases de Bode + + + + changelog + + + Could not fetch changelog: Server error {}. + No se ha podido recuperar el registro de cambios: Error del servidor {}. + + + + + Could not fetch update: {}. + No se pudo obtener el registro de cambios: {}. + + + + color + + + + %1 %2's color changed from %3 to %4. + El color de %1 %2 cambió de %3 a %4. + + + + comment + + + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + Ej: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ-*), ]0;1[, {3;4;5} + + + + The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) + Los siguientes parámetros se utilizan cuando el dominio es un conjunto no continuo. (Ej: ℕ, ℤ, conjuntos como {0;3}...) + + + + Note: Specify the probability for each value. + Nota: Especifique la probabilidad para cada valor. + + + + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... + Nota: Utilice %1[n] para referirse a %1ₙ, %1[n+1] para %1ₙ₊₁… + + + + If you have latex enabled, you can use use latex markup in between $$ to create equations. + Si tiene habilitado el latex, puede utilizar el marcado de latex entre $$ para crear ecuaciones. + + + + control + + + + + + + %1: + + + + + create + + + + New %1 %2 created. + Se ha creado un nuevo %1 %2. + + + + delete + + + + %1 %2 deleted. + %1 %2 borrados. + + + + distribution + + + Repartition + Distribución + + + + Repartition functions + Funciones de distribución + + + + editproperty + + + %1 of %2 %3 changed from "%4" to "%5". + %1 de %2 %3 cambió de "%4" a "%5". + + + + %1 of %2 changed from %3 to %4. + %1 de %2 ha cambiado de %3 a %4. + + + + error + + + + Cannot find property %1 of object %2. + No se puede encontrar la propiedad %1 del objeto %2. + + + + Undefined variable %1. + Variable %1 no definida. + + + + In order to be executed, object %1 must have at least one argument. + Para ser ejecutado, el objeto %1 debe tener al menos un argumento. + + + + %1 cannot be executed. + %1 no es una función. + + + + + + Invalid expression. + Expresión incorrecta. + + + + Invalid expression (parity). + Expresión no válida (paridad). + + + + Unknown character "%1". + Carácter "%1" desconocido. + + + + + Illegal escape sequence: %1. + Secuencia de salida no válida: %1 . + + + Parse error [%1:%2]: %3 + Error de análisis [%1:%2]: %3 + + + + + Parse error [position %1]: %2 + Error en el análisis [posición%1 ]:%2 + + + + Expected %1 + Previsto %1 + + + + Unexpected %1 + Inesperado %1 + + + + Unexpected ".": member access is not permitted + "." Inesperado: el acceso de miembros no está permitido + + + + Unexpected "[]": arrays are disabled. + "[]" inesperado: las matrices están desactivadas. + + + + Unexpected symbol: %1. + Símbolo inesperado: %1. + + + + + Function %1 must have at least one argument. + La función %1 debe tener al menos un argumento. + + + First argument to map is not a function. + El primer argumento de map no es una función. + + + Second argument to map is not an array. + El segundo argumento de map no es una matriz. + + + First argument to fold is not a function. + El primer argumento de fold no es una función. + + + Second argument to fold is not an array. + El segundo argumento de fold no es una matriz. + + + First argument to filter is not a function. + El primer argumento del filtro no es una función. + + + Second argument to filter is not an array. + El segundo argumento del filtro no es una matriz. + + + Second argument to indexOf is not a string or array. + El segundo argumento de indexOf no es una cadena ni una matriz. + + + Second argument to join is not an array. + El segundo argumento para unirse no es una matriz. + + + + No object found with names %1. + No se ha encontrado ningún objeto con el nombre %1. + + + + No object found with name %1. + Ningún objeto con el nombre %1 encontrado. + + + + Object cannot be dependent on itself. + El objeto no puede depender de sí mismo. + + + + Circular dependency detected. Object %1 depends on %2. + Dependencia circular detectada. El objeto %1 depende de %2. + + + + Circular dependency detected. Objects %1 depend on %2. + Dependencia circular detectada. Los objetos %1 dependen de %2. + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Error al analizar la expresión de la propiedad %1: +%2 + +Expresión evaluada: %3 + + + + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + Error al intentar dibujar %1 %2: +%3 + +Deshaciendo el último cambio. + + + Function definition is not permitted. + No se permite la definición de las funciones. + + + Expected variable for assignment. + Variable de asignación esperada. + + + + EOF + Fin de la expresión + + + + expression + + + + LogarithmPlotter - Parsing error + LogarithmPlotter - Error de procesamiento + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Error al analizar la expresión de la propiedad %1: +%2 + +Expresión evaluada: %3 + + + + LogarithmPlotter - Drawing error + + + + + Automatically close parenthesises and brackets + Cerrar automáticamente paréntesis y corchetes + + + + Enable syntax highlighting + Activar el resaltado sintáctico + + + + Enable autocompletion + Activar autocompletar + + + + Color Scheme + Esquema de colores + + + + function + + + Function + Función + + + + Functions + Funciones + + + + gainbode + + high-pass + Filtro paso alto + + + low-pass + Filtro paso bajo + + + Bode Magnitude + Magnitud de Bode + + + Bode Magnitudes + Magnitudes de Bode + + + + general + + + Check for updates on startup + Comprobación de las actualizaciones al arrancar + + + + Reset redo stack automaticly + Restablecer la pila de rehacer automáticamente + + + + Enable LaTeX rendering + Activar el renderizado de LaTeX + + + + Enable threaded LaTeX renderer (experimental) + + + + + historylib + + %1 %2 deleted. + %1 %2 borrados. + + + %1 %2 shown. + Se muestra %1 %2. + + + %1 %2 hidden. + Se oculta %1 %2. + + + New %1 %2 created. + Se ha creado un nuevo %1 %2. + + + Name of %1 %2 changed to %3. + El nombre de %1 %2 se ha cambiado por %3. + + + %1 of %2 %3 changed from "%4" to "%5". + %1 de %2 %3 cambió de "%4" a "%5". + + + + io + + History + Historial + + + Copied plot screenshot to clipboard! + ¡Captura de pantalla del gráfico copiada al portapapeles! + + + &Update + &Actualizar + + + &Update LogarithmPlotter + &Actualizar LogarithmPlotter + + + Settings + Ajustes + + + Objects + Objetos + + + Saved plot to '%1'. + Gráfico guardado en '%1'. + + + Loading file '%1'. + Cargando el archivo '%1'. + + + Unknown object type: %1. + Tipo de objeto desconocido: %1 . + + + Invalid file provided. + Se ha proporcionado un archivo no válido. + + + Could not load file: + No se pudo cargar el archivo: + + + Could not save file: + No se ha podido guardar el archivo: + + + Loaded file '%1'. + Archivo cargado '%1'. + + + + latex + + + No Latex installation found. +If you already have a latex distribution installed, make sure it's installed on your path. +Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. + No se ha encontrado ninguna instalación de LaTeX. +Si ya tiene instalada una distribución de LaTeX, asegúrese de que está instalada en su ruta. +De lo contrario, puede descargar una distribución de LaTeX como TeX Live en https://tug.org/texlive/. + + + + DVIPNG was not found. Make sure you include it from your Latex distribution. + No se ha encontrado DVIPNG. Asegúrese de incluirlo en tu distribución LaTeX. + + + + An exception occured within the creation of the latex formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your latex installation is correct and report a bug if so. + Se ha producido una excepción durante la creación de la fórmula LaTeX. +El proceso '{}' terminó con un código de retorno distinto de cero {}: + +{} +Por favor, asegúrate de que tu instalación de LaTeX es correcta e informe de un error si es así. + + + + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + Tu instalación de LaTeX no incluye algunos paquetes necesarios: + +- {} (https://ctan.org/pkg/{}) + +Asegúrate de que dicho paquete está instalado, o desactive el renderizado LaTeX en LogarithmPlotter. + + + + An exception occured within the creation of the latex formula. +Process '{}' took too long to finish: +{} +Please make sure your latex installation is correct and report a bug if so. + Se ha producido una excepción durante la creación de la fórmula LaTeX. +El proceso '{}' tardó demasiado en finalizar: +{} +Por favor, asegúrese de que su instalación de LaTeX es correcta e informe de un error si es así. + + + + main + + + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + + + + + Could not open file "{}": +{} + + + + + Could not open file: "{}" +File does not exist. + + + + + Built with PySide6 (Qt) v{} and python v{} + + + + + name + + + + %1 %2 renamed to %3. + %1 %2 ha sido renombrado a %3. + + + + parameters + + + below + ↓ Abajo + + + + + left + ← Izquierda + + + + above-left + ↖ Arriba a la izquierda + + + + below-left + ↙ Abajo a la izquierda + + + + below-right + ↘ Arriba a la derecha + + + + center + >|< Centro + + + + top + ↑ Arriba + + + + above + ↑ Arriba + + + + + right + → Derecha + + + + above-right + ↗ Arriba a la derecha + + + + bottom + ↓ Bajar + + + + top-right + ↗ Arriba a la derecha + + + + application + Aplicación + + + + Next to target + Próximo al objetivo + + + + top-left + ↖ Arriba a la izquierda + + + + bottom-left + ↙ Abajo a la izquierda + + + + bottom-right + ↘ Abajo a la derecha + + + + function + Función + + + + high + Alto + + + + low + Bajo + + + + With label + Con etiqueta + + + + Hidden + Oculto + + + + phasebode + + Bode Phase + Fase de Bode + + + Bode Phases + Fases de Bode + + + + point + + + Point + Punto + + + + Points + Puntos + + + + position + + + Position of %1 %2 set from "%3" to "%4". + %1 %2 movido de "%3" a "%4". + + + + Position of %1 set from %2 to %3. + %1 movido de %2 a %3. + + + + prop + + + expression + Expresión + + + + definitionDomain + Dominio + + + + + om_0 + ω₀ + + + + disableLatex + Desactivar el renderizado LaTeX para este texto + + + + rounding + Redondeo + + + + + + + + + + labelX + Posición de la etiqueta en X + + + + + drawPoints + Mostrar puntos + + + + + drawDashedLines + Mostrar líneas discontinuas + + + + destinationDomain + Rango + + + + + + + + + + + + + labelPosition + Posición de la etiqueta + + + + displayMode + Modo de visualización + + + + pass + Pasar + + + + gain + Incremento de magnitud + + + + unit + Unidad a usar + + + + + y + Y + + + + omGraduation + Mostrar la graduación en ω₀ + + + + phase + Fase + + + + + + x + X + + + + pointStyle + Estilo de puntos + + + + text + Contenido + + + + probabilities + Lista de probabilidades + + + + targetElement + Objeto de destino + + + + approximate + Mostrar el resultado redondeado + + + + displayStyle + Estilo de visualización + + + + targetValuePosition + Posición del valor del objetivo + + + + defaultExpression + Expresión predeterminada + + + color + Color + + + + baseValues + Valores de inicialización + + + + labelContent + Contenido de la etiqueta + + + + repartition + + Repartition + Distribución + + + Repartition functions + Funciones de distribución + + + + sequence + + + Sequences + Secuencias + + + + Sequence + Secuencia + + + + settingCategory + + + general + General + + + + editor + Editor de expresiones + + + + default + Ajustes por defecto + + + + sommegainsbode + + Bode Magnitudes Sum + Suma de las Magnitudes de Bode + + + + sommephasesbode + + Bode Phases Sum + Suma de las fases de Bode + + + + text + + + Texts + Textos + + + + Text + Texto + + + + update + + + An update for LogarithmPlotter (v{}) is available. + Una actualización para LogarithmPlotter (v{}) está disponible. + + + + No update available. + No hay ninguna actualización disponible. + + + + Could not fetch update information: Server error {}. + No se ha podido obtener la información de la actualización: Error del servidor {}. + + + + Could not fetch update information: {}. + No se pudo obtener información de la actualización: {}. + + + + usage + + + integral(<from: number>, <to: number>, <f: ExecutableObject>) + integral(<desde: número>, <hasta: número>, <f: Objeto similar a una función>) + + + + + Usage: +%1 + Uso: +%1 + + + + + + Usage: +%1 +%2 + Uso: +%1 +%2 + + + + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + integral(<desde: número>, <hasta: número>, <f: cadena>, <variable: cadena>) + + + + derivative(<f: ExecutableObject>, <x: number>) + derivative(<f: objeto similar a una función>, <x: número>) + + + + derivative(<f: string>, <variable: string>, <x: number>) + derivative(<f: cadena>, <variable: cadena>, <x: número>) + + + + visibility + + + + %1 %2 shown. + Se muestra %1 %2. + + + + + %1 %2 hidden. + Se oculta %1 %2. + + + + xcursor + + + X Cursor + Cursor X + + + + X Cursors + Cursores X + + + diff --git a/assets/i18n/lp_fr.ts b/assets/i18n/lp_fr.ts new file mode 100644 index 0000000..d2e6380 --- /dev/null +++ b/assets/i18n/lp_fr.ts @@ -0,0 +1,1999 @@ + + + + + About + + + About LogarithmPlotter + À propos de LogarithmPlotter + + + + LogarithmPlotter v%1 + LogarithmPlotter v%1 + + + + 2D plotter software to make BODE plots, sequences and repartition functions. + Logiciel de traçage 2D pour les diagrammes de Bode, les suites et les fonctions de répartition. + + + + Report a bug + Rapport de bug + + + + Official website + Site officiel + + + + AppMenuBar + + + &File + &Fichier + + + + &Load... + &Ouvrir… + + + + &Save + &Sauvegarder + + + + Save &As... + Sauvegarde &Sous… + + + + &Quit + &Quitter + + + + &Edit + &Édition + + + + &Undo + &Annuler + + + + &Redo + &Rétablir + + + + &Copy plot + &Copier le graphe + + + + &Preferences + &Préférences + + + + &Create + &Créer + + + &Settings + &Paramètres + + + Check for updates on startup + Vérifier la présence de mise à jour au démarrage + + + Reset redo stack automaticly + Légèrement long, et pas forcément très compréhensible. + Réinitialiser la pile d'action "Rétablir" automatiquement + + + Enable LaTeX rendering + Activer le rendu LaTeX + + + Expression editor + Éditeur de formule + + + Automatically close parenthesises and brackets + Fermer automatiquement les parenthèses et les crochets + + + Enable syntax highlighting + Activer la coloration syntaxique + + + Enable autocompletion + Activer l'autocomplétion + + + Color Scheme + Coloration Syntaxique + + + + &Help + &Aide + + + + &Source code + &Code source + + + + &Report a bug + &Rapport de bug + + + + &User manual + Manuel d'&utilisation + + + + &Changelog + &Notes de version + + + + &Help translating! + &Aider à la traduction ! + + + + &Thanks + &Remerciements + + + + &About + &À propos + + + + Save unsaved changes? + Sauvegarder les modifications ? + + + + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + Ce graphe contient des modifications non sauvegardées. En faisant cela, toutes les données non sauvegardées seront perdues. Continuer ? + + + + BaseDialog + + + Close + Fermer + + + + BoolSetting + + Check for updates on startup + Vérifier la présence de mise à jour au démarrage + + + + Browser + + + Filter... + Filtrer… + + + + Redo > + Rétablir > + + + + > Now + > État actuel + + + + < Undo + < Annuler + + + + Changelog + + + Fetching changelog... + Récupération des notes de version… + + + + Close + Fermer + + + + CustomPropertyList + + + + + Create new %1 + + Créer un nouvel objet %1 + + + + Pick on graph + Prendre la position sur le graphe + + + + Dialog + + + Edit properties of %1 %2 + Changer les propriétés de %1 %2 + + + + LogarithmPlotter - Invalid object name + LogarithmPlotter - Nom d'objet invalide + + + + An object with the name '%1' already exists. + Un objet portant le nom '%1' existe déjà. + + + + Name + Nom + + + Label content + Étiquette + + + + null + vide + + + + name + nom + + + + name + value + nom + valeur + + + + EditorDialog + + Edit properties of %1 %2 + Changer les propriétés de %1 %2 + + + Name + Nom + + + Label content + Étiquette + + + null + vide + + + name + nom + + + name + value + nom + valeur + + + + Create new %1 + Traduction non litéralle pour éviter les problèmes de genre. + + Créer un nouvel objet %1 + + + + ExpressionEditor + + + Object Properties + Propriétés de l'objet + + + + Variables + Variables + + + + Constants + Constantes + + + + Functions + Fonctions + + + + Executable Objects + Objets fonction + + + + Objects + Objets + + + + FileDialog + + + Export Logarithm Plot file + Exporter le graphe Logarithmique + + + + Import Logarithm Plot file + Importer un graphe Logarithmique + + + + GreetScreen + + + Welcome to LogarithmPlotter + Bienvenu·e sur LogarithmPlotter + + + + Version %1 + Version %1 + + + Take a few seconds to configure LogarithmPlotter. +These settings can be changed at any time from the "Settings" menu. + Prenez quelques secondes pour configurer LogarithmPlotter. +Ces paramètres peuvent être modifiés à tout moment à partir du menu "Paramètres". + + + Enable LaTeX rendering + Activer le rendu LaTeX + + + Automatically close parenthesises and brackets in expressions + Fermer automatiquement les parenthèses et les crochets dans les formules + + + Enable syntax highlighting for expressions + Activer la coloration syntaxique des formules + + + Enable autocompletion interface in expression editor + Activer l'interface d'autocomplétion dans l'éditeur de formules + + + Color scheme: + Thème de coloration syntaxique : + + + + User manual + Manuel d'utilisation + + + + Changelog + Notes de version + + + + Preferences + Préférences + + + + Close + Fermer + + + Check for updates on startup (requires online connectivity) + Vérifier les mises à jour au démarrage (nécessite d'être connecté à internet) + + + Reset redo stack when a new action is added to history + Réinitialiser la pile d'action "Rétablir" lorsqu'une nouvelle action est ajoutée à l'historique + + + + HistoryBrowser + + Filter... + Filtrer… + + + Redo > + Rétablir > + + + > Now + > État actuel + + + < Undo + < Annuler + + + + ListSetting + + + + Add Entry + + Nouvelle entrée + + + + Loading + + + Loading... + Chargement… + + + + Finished rendering of %1 + Rendu de %1 terminé + + + + LogarithmPlotter + + + untitled + sans titre + + + + Objects + Objets + + + + Settings + Paramètres + + + + History + Historique + + + Saved plot to '%1'. + Graphe sauvegardé dans '%1'. + + + Loading file '%1'. + Chargement du fichier '%1'. + + + Unknown object type: %1. + Type d'objet inconnu : %1. + + + Invalid file provided. + Fichier fourni invalide. + + + Could not save file: + Impossible de sauvegarder le fichier : + + + Loaded file '%1'. + Fichier '%1' chargé. + + + + Copied plot screenshot to clipboard! + Image du graphe copiée dans le presse-papiers ! + + + + &Update + &Mise à jour + + + + &Update LogarithmPlotter + &Mettre à jour LogarithmPlotter + + + + ObjectCreationGrid + + + + Create new: + + Créer : + + + + ObjectLists + + + Hide all %1 + Cacher tous les %1 + + + + Show all %1 + Montrer tous les %1 + + + Hide %1 %2 + Cacher l'objet %1 %2 + + + Show %1 %2 + Montrer l'objet %1 %2 + + + Set %1 %2 position + Définir la position de l'objet %1 %2 + + + Delete %1 %2 + Supprimer l'objet %1 %2 + + + Pick new color for %1 %2 + Choisissez une nouvelle couleur pour %1 %2 + + + + ObjectRow + + + Hide %1 %2 + Cacher l'objet %1 %2 + + + + Show %1 %2 + Montrer l'objet %1 %2 + + + + Set %1 %2 position + Définir la position de l'objet %1 %2 + + + + Delete %1 %2 + Supprimer l'objet %1 %2 + + + + Pick new color for %1 %2 + Choisissez une nouvelle couleur pour %1 %2 + + + + PickLocation + + + Pointer precision: + Précision du pointeur : + + + + Snap to grid: + Placer sur la grille : + + + + Pick X + Prendre la position X + + + + Pick Y + Prendre la position Y + + + + Open picker settings + Ouvrir les paramètres du pointeur + + + + Hide picker settings + Cacher les paramètres du pointeur + + + + (no pick selected) + (aucun axe sélectionné) + + + + PickLocationOverlay + + Pointer precision: + Précision du pointeur : + + + Snap to grid + Placement sur la grille + + + Snap to grid: + Placer sur la grille : + + + Pick X + Prendre la position X + + + Pick Y + Prendre la position Y + + + Open picker settings + Ouvrir les paramètres du pointeur + + + Hide picker settings + Cacher les paramètres du pointeur + + + (no pick selected) + (aucun axe sélectionné) + + + + Preferences + + + Close + Fermer + + + + Settings + + + + X Zoom + Zoom en X + + + + + Y Zoom + Zoom en Y + + + + + Min X + Min X + + + + + Max Y + Max Y + + + + Max X + Max X + + + + Min Y + Min Y + + + + + X Axis Step + Pas de l'axe X + + + + + Y Axis Step + Pas de l'axe Y + + + + + Line width + Taille des lignes + + + + + Text size (px) + Taille du texte (px) + + + + + X Label + Label de l'axe X + + + + + Y Label + Label de l'axe Y + + + + + X Log scale + Échelle logarithmique en X + + + + + Show X graduation + Montrer la graduation de l'axe X + + + + + Show Y graduation + Montrer la graduation de l'axe Y + + + + Copy to clipboard + Copier vers le presse-papiers + + + + Save plot + Sauvegarder le graphe… + + + + Save plot as + Sauvegarder le graphe sous… + + + + Load plot + Ouvrir un graphe… + + + Close + Fermer + + + + ThanksTo + + + Thanks and Contributions - LogarithmPlotter + Remerciements et contributions - LogarithmPlotter + + + + Source code + Code source + + + + Original library by Raphael Graf + Bibliothèque originale de Raphael Graf + + + + Source + Source + + + + Ported to Javascript by Matthew Crumley + Porté en JavaScript par Matthew Crumley + + + + + + Website + Site web + + + + Ported to QMLJS by Ad5001 + Porté à QMLJS par Ad5001 + + + + Libraries included + Bibliothèques incluses + + + + Email + Email + + + + English + Anglais + + + + French + Français + + + + German + Allemand + + + + Hungarian + Hongrois + + + + + + + Github + GitHub + + + + Norwegian + Norvégien + + + + Spanish + Espagnol + + + + Tamil + Tamoul + + + + Translations included + Traductions incluses + + + + Improve + Améliorer + + + + bodemagnitude + + + Bode Magnitude + Gain de Bode + + + + Bode Magnitudes + Gains de Bode + + + + + low-pass + passe-bas + + + + + high-pass + passe-haut + + + + bodemagnitudesum + + + + Bode Magnitudes Sum + Sommes des gains de Bode + + + + bodephase + + + Bode Phase + Phase de Bode + + + + Bode Phases + Phases de Bode + + + + bodephasesum + + + + Bode Phases Sum + Somme des phases de Bode + + + + changelog + + + Could not fetch changelog: Server error {}. + Impossible de récupérer les notes de version : Erreur de serveur {}. + + + + + Could not fetch update: {}. + Impossible de récupérer les notes de version : {}. + + + + color + + + + %1 %2's color changed from %3 to %4. + La couleur du %1 %2 a été changée du %3 au %4. + + + + comment + + + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + Par exemple : R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + + + + The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) + Les paramètres suivants sont utilisés lorsque le domaine de définition est un ensemble non-continu. (Ex : ℕ, ℤ, des ensembles comme {0;3}…) + + + + Note: Specify the probability for each value. + Note : Spécifiez la probabilité pour chaque valeur. + + + + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... + Note : Utilisez %1[n] pour faire référence à %1ₙ, %1[n+1] pour %1ₙ₊₁... + Note : Utilisez %1[n] pour faire référence à %1ₙ, %1[n+1] pour %1ₙ₊₁… + + + + If you have latex enabled, you can use use latex markup in between $$ to create equations. + Si vous avez activé le rendu latex, vous pouvez utiliser les balises latex entre $$ pour créer des équations. + + + + control + + + + + + + %1: + %1 : + + + + create + + + + New %1 %2 created. + Nouvel objet %1 %2 créé. + + + + delete + + + + %1 %2 deleted. + %1 %2 supprimé(e). + + + + distribution + + + Repartition + Répartition + + + + Repartition functions + Fonctions de répartition + + + + editproperty + + + %1 of %2 %3 changed from "%4" to "%5". + %1 de %2 %3 modifiée de "%4" à "%5". + + + + %1 of %2 changed from %3 to %4. + %1 de %2 modifiée de %3 à %4. + + + + error + + + + Cannot find property %1 of object %2. + Impossible de trouver la propriété %1 de l'objet %2. + + + + Undefined variable %1. + La variable %1 n'est pas définie. + + + + In order to be executed, object %1 must have at least one argument. + Pour être utilisé comme fonction, l'objet %1 nécessite au moins un argument. + + + + %1 cannot be executed. + %1 n'est pas une fonction. + + + + + + Invalid expression. + Formule invalide. + + + + Invalid expression (parity). + Formule invalide (parité). + + + + Unknown character "%1". + Le caractère "%1" est inconnu. + + + + + Illegal escape sequence: %1. + Séquence d'échappement illégale : %1. + + + Parse error [%1:%2]: %3 + Erreur de syntaxe [%1:%2] : %3 + + + + Expected %1 + %1 attendu + + + + Unexpected %1 + %1 inattendu + + + Function definition is not permitted. + La définition de fonctions n'est pas autorisée. + + + Expected variable for assignment. + Une variable est attendue pour l'affectation. + + + + + Parse error [position %1]: %2 + Erreur de syntaxe [position %1] : %2 + + + + Unexpected ".": member access is not permitted + "." inattendu : l'accès aux propriétés n'est pas autorisé + + + + Unexpected "[]": arrays are disabled. + "[]" inattendu : les tableaux sont désactivés. + + + + Unexpected symbol: %1. + Symbole inconnu : %1. + + + + + Function %1 must have at least one argument. + La fonction %1 nécessite au moins un argument. + + + First argument to map is not a function. + Le premier argument de map n'est pas une fonction. + + + Second argument to map is not an array. + Le deuxième argument de map n'est pas un tableau. + + + First argument to fold is not a function. + Le premier argument de fold n'est pas une fonction. + + + Second argument to fold is not an array. + Le deuxième argument de fold n'est pas un tableau. + + + First argument to filter is not a function. + Le premier argument de filter n'est pas une fonction. + + + Second argument to filter is not an array. + Le deuxième argument de filter n'est pas un tableau. + + + Second argument to indexOf is not a string or array. + Le deuxième argument de indexOf n'est ni chaîne de caractères ni un tableau. + + + Second argument to join is not an array. + Le deuxième argument de join n'est pas un tableau. + + + + EOF + Fin de la formule + + + + No object found with names %1. + Aucun objet trouvé ayant pour noms %1. + + + + No object found with name %1. + Aucun objet avec le nom %1 n'a été trouvé. + + + + Object cannot be dependent on itself. + Un objet ne peut pas dépendre de lui-même. + + + + Circular dependency detected. Object %1 depends on %2. + Dépendance circulaire détectée. L'objet %1 dépend de %2. + + + + Circular dependency detected. Objects %1 depend on %2. + Dépendance circulaire détectée. Les objets %1 dépendent de %2. + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Erreur lors de l'analyse de la formule pour la propriété %1 : +%2 + +Formule analysée : %3 + + + + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + Erreur lors de la tentative de dessin du %1 %2 : +%3 + +La dernière modification a été annulée. + + + + expression + + + + LogarithmPlotter - Parsing error + LogarithmPlotter - Erreur de syntaxe + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Erreur lors de l'analyse de la formule pour la propriété %1 : +%2 + +Formule analysée : %3 + + + + LogarithmPlotter - Drawing error + LogarithmPlotter - Erreur + + + + Automatically close parenthesises and brackets + Fermer automatiquement les parenthèses et les crochets + + + + Enable syntax highlighting + Activer la coloration syntaxique + + + + Enable autocompletion + Activer l'autocomplétion + + + + Color Scheme + Coloration Syntaxique + + + + function + + + Function + Fonction + + + + Functions + Fonctions + + + + gainbode + + Bode Magnitude + Gain de Bode + + + Bode Magnitudes + Gains de Bode + + + low-pass + passe-bas + + + high-pass + passe-haut + + + + general + + + Check for updates on startup + Vérifier la présence de mise à jour au démarrage + + + + Reset redo stack automaticly + Réinitialiser la pile d'action "Rétablir" automatiquement + + + + Enable LaTeX rendering + Activer le rendu LaTeX + + + + Enable threaded LaTeX renderer (experimental) + Activer le moteur de rendu LaTeX asynchrone (expérimental) + + + + historylib + + New %1 %2 created. + Nouvel objet %1 %2 créé. + + + %1 %2 deleted. + %1 %2 supprimé(e). + + + %1 of %2 %3 changed from "%4" to "%5". + %1 de %2 %3 modifiée de "%4" à "%5". + + + %1 %2 shown. + %1 %2 affiché(e). + + + %1 %2 hidden. + %1 %2 cachée(e). + + + Name of %1 %2 changed to %3. + Le nom de %1 %2 a été changé en %3. + + + + io + + Objects + Objets + + + Settings + Paramètres + + + History + Historique + + + Copied plot screenshot to clipboard! + Image du graphe copiée dans le presse-papiers ! + + + &Update + &Mise à jour + + + &Update LogarithmPlotter + &Mettre à jour LogarithmPlotter + + + Saved plot to '%1'. + Graphe sauvegardé dans '%1'. + + + Loading file '%1'. + Chargement du fichier '%1'. + + + Unknown object type: %1. + Type d'objet inconnu : %1. + + + Invalid file provided. + Fichier fourni invalide. + + + Could not load file: + Impossible de charger le fichier : + + + Could not save file: + Impossible de sauvegarder le fichier : + + + Loaded file '%1'. + Fichier '%1' chargé. + + + + latex + + + No Latex installation found. +If you already have a latex distribution installed, make sure it's installed on your path. +Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. + Aucune installation de LaTeX trouvée. +Si vous avez déjà installé une distribution LaTeX, assurez-vous qu'elle est installée sur votre PATH. +Sinon, vous pouvez télécharger une distribution LaTeX comme TeX Live à l'adresse https://tug.org/texlive/. + + + + DVIPNG was not found. Make sure you include it from your Latex distribution. + DVIPNG n'a pas été trouvé. Assurez-vous de l'inclure dans votre distribution LaTeX. + + + + An exception occured within the creation of the latex formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your latex installation is correct and report a bug if so. + Une exception s'est produite lors de la création de la formule LaTeX. +Le processus '{}' s'est terminé par un code de retour non nul {} : + +{} +Vérifiez que votre installation de LaTeX est correcte et signalez un bogue si c'est le cas. + + + + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + Votre installation de LaTeX n'inclut pas certains paquets nécessaires : + +- {} (https://ctan.org/pkg/{}) + +Assurez-vous que ce paquetage est installé, ou désactivez le rendu LaTeX dans LogarithmPlotter. + + + + An exception occured within the creation of the latex formula. +Process '{}' took too long to finish: +{} +Please make sure your latex installation is correct and report a bug if so. + Une exception s'est produite lors de la création de la formule LaTeX. +Le processus '{}' a mis trop de temps à se terminer : +{} +Vérifiez que votre installation de LaTeX est correcte et signalez un bogue si c'est le cas. + + + + main + + + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + Ce fichier a été créé par une version plus récente de LogarithmPlotter et ne peut pas être rechargé dans LogarithmPlotter v{}. +Veuillez mettre à jour LogarithmPlotter pour ouvrir ce fichier. + + + + Could not open file "{}": +{} + Impossible d'ouvrir le fichier "{}": +{} + + + + Could not open file: "{}" +File does not exist. + Impossible d'ouvrir le fichier "{}": +Le fichier n'existe pas. + + + + Built with PySide6 (Qt) v{} and python v{} + Compilé avec PySide6 (Qt) v{} et python v{} + + + + name + + + + %1 %2 renamed to %3. + %1 %2 renommé(e) en %3. + + + + parameters + + + above + ↑ Au dessus + + + + below + ↓ En dessous + + + + + left + ← À gauche + + + + + right + → À droite + + + + above-left + ↖ Au dessus à gauche + + + + above-right + ↗ Au dessus à droite + + + + below-left + ↙ En dessous à gauche + + + + below-right + ↘ En dessous à droite + + + + center + >|< Centré + + + + top + ↑ Au dessus + + + + bottom + ↓ En dessous + + + + top-left + ↖ Au dessus à gauche + + + + top-right + ↗ Au dessus à droite + + + + bottom-left + ↙ En dessous à gauche + + + + bottom-right + ↘ En dessous à droite + + + + application + Application + + + + function + Fonction + + + + high + Haut + + + + low + Bas + + + + Next to target + A côté de la cible + + + + With label + Avec l'étiquette + + + + Hidden + Caché + + + + phasebode + + Bode Phase + Phase de Bode + + + Bode Phases + Phases de Bode + + + + point + + + Point + Point + + + + Points + Points + + + + position + + + Position of %1 %2 set from "%3" to "%4". + %1 %2 a été déplacé depuis "%3" vers "%4". + + + + Position of %1 set from %2 to %3. + %1 a été déplacé depuis %2 vers %3. + + + + prop + + + expression + Formule + + + + definitionDomain + Domaine de définition + + + + destinationDomain + Portée + + + + + + + + + + + + + labelPosition + Position de l'étiquette + + + + displayMode + Mode d'affichage + + + + + + + + + + labelX + Position en X de l'étiquette + + + + + drawPoints + Afficher les points + + + + + drawDashedLines + Afficher les pointillés + + + + + om_0 + ω₀ + + + + pass + Passe + + + + gain + Gain + + + + omGraduation + Afficher la graduation sur ω₀ + + + + phase + Phase + + + + unit + Unité de la phase + + + + + + x + X + + + + + y + Y + + + + pointStyle + Style du point + + + + probabilities + Liste de probabilités + + + + text + Contenu + + + + disableLatex + Désactiver le rendu LaTeX pour ce texte + + + + targetElement + Objet à cibler + + + + approximate + Afficher la valeur arrondie calculée + + + + rounding + Arrondi + + + + displayStyle + Style d'affichage + + + + targetValuePosition + Position de la valeur de la cible + + + + defaultExpression + Formule par défaut + + + + baseValues + Valeurs d'initialisation + + + color + Couleur + + + + labelContent + Étiquette + + + + repartition + + Repartition + Répartition + + + Repartition functions + Fonctions de répartition + + + + sequence + + + Sequence + Suite + + + + Sequences + Suites + + + + settingCategory + + + general + Général + + + + editor + Éditeur de formule + + + + default + Paramètres par défaut + + + + sommegainsbode + + Bode Magnitudes Sum + Sommes des gains de Bode + + + + sommephasesbode + + Bode Phases Sum + Somme des phases de Bode + + + + text + + + Text + Texte + + + + Texts + Textes + + + + update + + + An update for LogarithmPlotter (v{}) is available. + Une mise à jour de LogarithmPlotter (v{}) est disponible. + + + + No update available. + À jour. + + + + Could not fetch update information: Server error {}. + Impossible de récupérer les informations de mise à jour. Erreur du serveur {}. + + + + Could not fetch update information: {}. + Impossible de récupérer les informations de mise à jour. {}. + + + + usage + + + integral(<from: number>, <to: number>, <f: ExecutableObject>) + integral(<de : nombre>, <à : nombre>, <f : Objet fonction>) + + + + + Usage: +%1 + Emploi : +%1 + + + + + + Usage: +%1 +%2 + Emploi : +%1 +%2 + + + + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + integral(<de : nombre>, <à : nombre>, <f : fonction chaîne>, <variable>) + + + + derivative(<f: ExecutableObject>, <x: number>) + derivative(<f : Objet fonction>, <x : nombre>) + + + + derivative(<f: string>, <variable: string>, <x: number>) + derivative(<f : fonction chaîne>, <variable>, <x : nombre>) + + + + visibility + + + + %1 %2 shown. + %1 %2 affiché(e). + + + + + %1 %2 hidden. + %1 %2 cachée(e). + + + + xcursor + + + X Cursor + Curseur X + + + + X Cursors + Curseurs X + + + diff --git a/assets/i18n/lp_hu.ts b/assets/i18n/lp_hu.ts new file mode 100644 index 0000000..7e2856d --- /dev/null +++ b/assets/i18n/lp_hu.ts @@ -0,0 +1,1996 @@ + + + + + About + + + About LogarithmPlotter + LogarithmPlotter névjegye + + + + LogarithmPlotter v%1 + LogarithmPlotter %1 verzió + + + + 2D plotter software to make BODE plots, sequences and repartition functions. + Síkbeli ábrázolásszoftver Bode-ábrák, sorozatok és eloszlási funkciók készítéséhez. + + + + Report a bug + Hiba bejelentése + + + + Official website + Hivatalos honlap + + + + AppMenuBar + + + &File + &Fájl + + + + &Load... + &Megnyitás… + + + + &Save + &Mentés + + + + Save &As... + Me&ntés másként… + + + + &Quit + &Kilépés + + + + &Edit + S&zerkesztés + + + + &Undo + &Visszavonás + + + + &Redo + &Ismétlés + + + + &Copy plot + Ábra má&solása + + + + &Preferences + &Beállítások + + + + &Create + &Létrehozás + + + &Settings + &Beállítások + + + Check for updates on startup + Frissítések keresése indításkor + + + Reset redo stack automaticly + Ismétlési verem önműködő visszaállítása + + + Enable LaTeX rendering + LaTeX-megjelenítés engedélyezése + + + Expression editor + Kifejezésszerkesztő + + + Automatically close parenthesises and brackets + Zárójelek automatikus bezárása + + + Enable syntax highlighting + Mondattani kiemelés engedélyezése + + + Enable autocompletion + Automatikus befejezés engedélyezése + + + Color Scheme + Színséma + + + + &Help + &Súgó + + + + &Source code + &Forráskód + + + + &Report a bug + &Hiba bejelentése + + + + &User manual + &Használati utasítás + + + + &Changelog + &Változásnapló + + + + &Help translating! + &Segítség a fordításban! + + + + &Thanks + &Köszönjük + + + + &About + &Névjegy + + + + Save unsaved changes? + Menti a változtatásokat? + + + + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + Ez az ábra nem mentett változtatásokat tartalmaz. Ezzel az összes nem mentett adat elveszik. Folytatja? + + + + BaseDialog + + + Close + Bezárás + + + + BoolSetting + + Check for updates on startup + Frissítések keresése indításkor + + + + Browser + + + Filter... + Szűrő… + + + + Redo > + Ismétlés > + + + + > Now + > Most + + + + < Undo + < Visszavonás + + + + Changelog + + + Fetching changelog... + Változásnapló lekérése… + + + + Close + Kész + + + + CustomPropertyList + + + + + Create new %1 + + Új %1 létrehozása + + + + Pick on graph + Ábra kijelölése + + + + Dialog + + + Edit properties of %1 %2 + %1 %2 tulajdonságainak szerkesztése + + + + LogarithmPlotter - Invalid object name + LogarithmPlotter - Érvénytelen objektumnév + + + + An object with the name '%1' already exists. + A(z) „%1” nevű objektum már létezik. + + + + Name + Név + + + Label content + Címketartalom + + + + null + üres + + + + name + név + + + + name + value + név + érték + + + + EditorDialog + + Edit properties of %1 %2 + %1 %2 tulajdonságainak szerkesztése + + + Name + Név + + + Label content + Címke tartalom + + + null + üres + + + name + név + + + name + value + név + érték + + + + Create new %1 + + Új %1 létrehozása + + + + ExpressionEditor + + + Object Properties + Objektumtulajdonságok + + + + Variables + Változók + + + + Constants + Állandók + + + + Functions + Függvények + + + + Executable Objects + Függvényobjektumok + + + + Objects + Objektumok + + + + FileDialog + + + Export Logarithm Plot file + Logaritmus-ábra-fájl exportálása + + + + Import Logarithm Plot file + Logaritmus-ábra-fájl importálása + + + + GreetScreen + + + Welcome to LogarithmPlotter + Isten hozott a LogarithmPlotter! + + + + Version %1 + %1 verzió + + + Take a few seconds to configure LogarithmPlotter. +These settings can be changed at any time from the "Settings" menu. + Szánjon néhány másodpercet a LogarithmPlotter beállításához. +Ezek a beállítások bármikor módosíthatók a „Beállítások” menüben. + + + Check for updates on startup (requires online connectivity) + Frissítések keresése indításkor (online kapcsolat szükséges) + + + Reset redo stack when a new action is added to history + Ismétlési verem alaphelyzet visszaállítása, ha új műveletet adnak az előzményekhez + + + Enable LaTeX rendering + LaTeX-megjelenítés engedélyezése + + + Automatically close parenthesises and brackets in expressions + Zárójelek automatikus bezárása a kifejezésekben + + + Enable syntax highlighting for expressions + Mondattani kiemelés engedélyezése a kifejezésekhez + + + Enable autocompletion interface in expression editor + Automatikus befejezési felület engedélyezése a kifejezésszerkesztőben + + + Color scheme: + Színséma: + + + + User manual + Használati utasítás + + + + Changelog + Változásnapló + + + + Preferences + Beállítások + + + + Close + Kész + + + + HistoryBrowser + + Filter... + Szűrő… + + + Redo > + Ismétlés > + + + > Now + > Most + + + < Undo + < Visszavonás + + + + ListSetting + + + + Add Entry + + Bejegyzés hozzáadása + + + + Loading + + + Loading... + Betöltés… + + + + Finished rendering of %1 + %1 renderelése befejeződött + + + + LogarithmPlotter + + + untitled + névtelen + + + + Objects + Tárgyak + + + + Settings + Beállítások + + + + History + Előzmények + + + Saved plot to '%1'. + Ábra mentve ide: „%1”. + + + Loading file '%1'. + A(z) „%1” fájl betöltése folyamatban van. + + + Unknown object type: %1. + Ismeretlen objektumtípus: %1. + + + Invalid file provided. + A megadott fájl érvénytelen. + + + Could not save file: + A fájl mentése nem sikerült: + + + Loaded file '%1'. + A(z) „%1” fájl betöltve. + + + + Copied plot screenshot to clipboard! + Ábra képernyőkép vágólapra másolva! + + + + &Update + &Frissítés + + + + &Update LogarithmPlotter + A LogarithmPlotter &frissítése + + + + ObjectCreationGrid + + + + Create new: + + Új létrehozása: + + + + ObjectLists + + + Hide all %1 + Összes %1 elrejtése + + + + Show all %1 + Összes %1 megjelenítése + + + Hide %1 %2 + %1 %2 elrejtése + + + Show %1 %2 + %1 %2 megjelenítése + + + Set %1 %2 position + %1 %2 helye beállítása + + + Delete %1 %2 + %1 %2 törlése + + + Pick new color for %1 %2 + Válasszon új színt a következőhöz: %1 %2 + + + + ObjectRow + + + Hide %1 %2 + %1 %2 elrejtése + + + + Show %1 %2 + %1 %2 megjelenítése + + + + Set %1 %2 position + %1 %2 helye beállítása + + + + Delete %1 %2 + %1 %2 törlése + + + + Pick new color for %1 %2 + Válasszon új színt a következőhöz: %1 %2 + + + + PickLocation + + + Pointer precision: + Mutató pontossága: + + + + Snap to grid: + Rácshoz igazítás: + + + + Pick X + X kijelölése + + + + Pick Y + Y kijelölése + + + + Open picker settings + Kijelölési beállítások megnyitása + + + + Hide picker settings + Kijelölési beállítások elrejtése + + + + (no pick selected) + (nincs kijelölés kiválasztva) + + + + PickLocationOverlay + + Pointer precision: + Mutató pontossága: + + + Snap to grid + Rácshoz illesztés + + + Snap to grid: + Rácshoz igazítás: + + + Pick X + X kijelölése + + + Pick Y + Y kijelölése + + + Open picker settings + Kijelölési beállítások megnyitása + + + Hide picker settings + Kijelölési beállítások elrejtése + + + (no pick selected) + (nincs kijelölés kiválasztva) + + + + Preferences + + + Close + Bezárás + + + + Settings + + + + X Zoom + X-nagyítás + + + + + Y Zoom + Y-nagyítás + + + + + Min X + Legkisebb X + + + + + Max Y + Legnagyobb Y + + + + Max X + Legnagyobb X + + + + Min Y + Legkisebb Y + + + + + X Axis Step + X tengely lépésköze + + + + + Y Axis Step + Y tengely lépésköze + + + + + Line width + Vonalvastagság + + + + + Text size (px) + Szövegméret (képpont) + + + + + X Label + X címke + + + + + Y Label + Y címke + + + + + X Log scale + X tengely logaritmikus skálával + + + + + Show X graduation + X érettségi megjelenítése + + + + + Show Y graduation + Y érettségi megjelenítése + + + + Copy to clipboard + Másolás a vágólapra + + + + Save plot + Ábra mentése… + + + + Save plot as + Ábra mentése másként… + + + + Load plot + Ábra megnyitása… + + + Close + Kész + + + + ThanksTo + + + Thanks and Contributions - LogarithmPlotter + Köszönet és hozzájárulás - LogarithmPlotter + + + + Source code + Forráskód + + + + Original library by Raphael Graf + Eredeti könyvtár: Graf Raphael + + + + Source + Forrás + + + + Ported to Javascript by Matthew Crumley + JavaScript-átalakítás: Crumley Máté + + + + + + Website + Honlap + + + + Ported to QMLJS by Ad5001 + QMLJS-átalakítás: Ad5001 + + + + Libraries included + Tartalmazott könyvtárak + + + + Email + E-mail + + + + English + angol + + + + French + francia + + + + German + német + + + + Hungarian + magyar + + + + + + + Github + GitHub + + + + Norwegian + norvég + + + + Spanish + spanyol + + + + Tamil + Tamil + + + + Translations included + A felhasználói felület nyelvei + + + + Improve + Fejlesztés + + + + bodemagnitude + + + Bode Magnitude + Bode-nagyságrend + + + + Bode Magnitudes + Bode-nagyságrendek + + + + + low-pass + aluláteresztő + + + + + high-pass + felüláteresztő + + + + bodemagnitudesum + + + + Bode Magnitudes Sum + Bode-nagyságrendek összege + + + + bodephase + + + Bode Phase + Bode-fázis + + + + Bode Phases + Bode-fázisok + + + + bodephasesum + + + + Bode Phases Sum + Bode-fázisok összege + + + + changelog + + + Could not fetch changelog: Server error {}. + Nem sikerült lekérni a változásnaplót: Kiszolgálóhiba: {}. + + + + + Could not fetch update: {}. + Nem sikerült lekérni a változásnaplót: {}. + + + + color + + + + %1 %2's color changed from %3 to %4. + %1 %2 színe %3-ról %4-re változott. + + + + comment + + + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + Példák: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + + + + The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) + A következő paraméterek használatosak, ha a tartomány nem folytonos halmaz. (Példák: ℕ, ℤ, olyan halmazok, mint a {0;3}…) + + + + Note: Specify the probability for each value. + Megjegyzés: Adja meg az egyes értékek valószínűségét. + + + + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... + Megjegyzés: A(z) %1[n] használatával hivatkozhat erre: %1ₙ, a(z) %1[n+1] használatával hivatkozhat erre: %1ₙ₊₁, … + + + + If you have latex enabled, you can use use latex markup in between $$ to create equations. + Ha a LaTeX engedélyezve van, a LaTeX-jelölés használható egyenletek létrehozására $$ között. + + + + control + + + + + + + %1: + %1: + + + + create + + + + New %1 %2 created. + Új %1 %2 létrehozva. + + + + delete + + + + %1 %2 deleted. + %1 %2 törölve. + + + + distribution + + + Repartition + Elosztás + + + + Repartition functions + Elosztási függvények + + + + editproperty + + + %1 of %2 %3 changed from "%4" to "%5". + %1/%2 %3 megváltozott. Régi érték: %4, új érték: %5. + + + + %1 of %2 changed from %3 to %4. + %1/%2 megváltozott. Régi érték: %3, új érték: %4. + + + + error + + + + Cannot find property %1 of object %2. + A(z) %2 objektum %1 tulajdonsága nem található. + + + + Undefined variable %1. + A(z) %1 változó nincs meghatározva. + + + + In order to be executed, object %1 must have at least one argument. + A végrehajtáshoz a(z) %1 objektumnak legalább egy argumentummal kell rendelkeznie. + + + + %1 cannot be executed. + A(z) %1 nem függvény. + + + + + + Invalid expression. + Érvénytelen kifejezés. + + + + Invalid expression (parity). + Érvénytelen kifejezés (paritás). + + + + Unknown character "%1". + Ismeretlen karakter „%1”. + + + + + Illegal escape sequence: %1. + Érvénytelen kilépési sorozat: %1. + + + Parse error [%1:%2]: %3 + Elemzési hiba [%1:%2]: %3 + + + + Expected %1 + Várható %1 + + + + Unexpected %1 + Váratlan %1 + + + Function definition is not permitted. + A függvény meghatározása nem engedélyezett. + + + Expected variable for assignment. + A hozzárendeléshez várt változó. + + + + + Parse error [position %1]: %2 + Elemzési hiba [hely %1]: %2 + + + + Unexpected ".": member access is not permitted + Váratlan „.”: a tagok hozzáférése nem engedélyezett + + + + Unexpected "[]": arrays are disabled. + Váratlan „[]”: a tömbök le vannak tiltva. + + + + Unexpected symbol: %1. + Váratlan szimbólum: %1. + + + + + Function %1 must have at least one argument. + A(z) %1 függvénynek legalább egy argumentumnak kell lennie. + + + First argument to map is not a function. + Az első leképezési argumentum nem függvény. + + + Second argument to map is not an array. + A második leképezési argumentum nem tömb. + + + First argument to fold is not a function. + Az első behajtási argumentum nem függvény. + + + Second argument to fold is not an array. + A második behajtási argumentum nem tömb. + + + First argument to filter is not a function. + Az első szűrési argumentum nem függvény. + + + Second argument to filter is not an array. + A második szűrési argumentum nem tömb. + + + Second argument to indexOf is not a string or array. + Az indexOf második argumentuma nem karakterlánc vagy tömb. + + + Second argument to join is not an array. + A második csatlakozási argumentum nem tömb. + + + + EOF + Kifejezés vége + + + + No object found with names %1. + A(z) %1 nevű objektum nem található. + + + + No object found with name %1. + A(z) %1 nevű objektum nem található. + + + + Object cannot be dependent on itself. + Az objektum nem függhet önmagától. + + + + Circular dependency detected. Object %1 depends on %2. + Körkörös függőség észlelve. A(z) %1-objektum a(z) %2-objektumtól függ. + + + + Circular dependency detected. Objects %1 depend on %2. + Körkörös függőség észlelve. A(z) %1-objektumok a(z) %2-objektumtól függenek. + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Hiba a(z) %1 tulajdonság kifejezésének elemzésekor: +%2 + +Kiértékelt kifejezés: %3 + + + + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + Hiba történt a(z) %1 %2 rajzolása közben: +%3 + +Az utolsó módosítás visszavonása. + + + + expression + + + + LogarithmPlotter - Parsing error + LogarithmPlotter - Elemzési hiba + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + Hiba a(z) %1 tulajdonság kifejezésének elemzésekor: +%2 + +Kiértékelt kifejezés: %3 + + + + LogarithmPlotter - Drawing error + LogarithmPlotter - Rajzolási hiba + + + + Automatically close parenthesises and brackets + Zárójelek automatikus bezárása + + + + Enable syntax highlighting + Mondattani kiemelés engedélyezése + + + + Enable autocompletion + Automatikus befejezés engedélyezése + + + + Color Scheme + Színséma + + + + function + + + Function + Függvény + + + + Functions + Függvények + + + + gainbode + + Bode Magnitude + Bode-nagyságrend + + + Bode Magnitudes + Bode-nagyságrendek + + + low-pass + aluláteresztő + + + high-pass + felüláteresztő + + + + general + + + Check for updates on startup + Frissítések keresése indításkor + + + + Reset redo stack automaticly + Ismétlési verem önműködő visszaállítása + + + + Enable LaTeX rendering + LaTeX-megjelenítés engedélyezése + + + + Enable threaded LaTeX renderer (experimental) + A szálas LaTeX renderer engedélyezése (kísérleti) + + + + historylib + + New %1 %2 created. + Új %1 %2 létrehozva. + + + %1 %2 deleted. + %1 %2 törölve. + + + %1 of %2 %3 changed from "%4" to "%5". + %1/%2 %3 megváltozott. Régi érték: %4, új érték: %5. + + + %1 %2 shown. + %1 %2 megjelenítve. + + + %1 %2 hidden. + %1 %2 rejtve. + + + Name of %1 %2 changed to %3. + %1 %2 neve a következőre módosult: %3. + + + + io + + Settings + Beállítások + + + History + Előzmények + + + Saved plot to '%1'. + Ábra mentve ide: „%1”. + + + Loading file '%1'. + A(z) „%1” fájl betöltése folyamatban van. + + + Unknown object type: %1. + Ismeretlen objektumtípus: %1. + + + Invalid file provided. + A megadott fájl érvénytelen. + + + Could not load file: + Nem sikerült betölteni a fájlt: + + + Could not save file: + A fájl mentése nem sikerült: + + + Loaded file '%1'. + A(z) „%1” fájl betöltve. + + + Copied plot screenshot to clipboard! + Ábra képernyőkép vágólapra másolva! + + + &Update + &Frissítés + + + &Update LogarithmPlotter + A LogarithmPlotter &frissítése + + + Objects + Objektumok + + + + latex + + + No Latex installation found. +If you already have a latex distribution installed, make sure it's installed on your path. +Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. + Nem található LaTeX telepítés. +Ha már telepítve van egy LaTeX disztribúció, győződjön meg arról, hogy az telepítve van az elérési útján. +Egyébként letölthet egy LaTeX disztribúciót, például a TeX Live-t a https://tug.org/texlive/ címről. + + + + DVIPNG was not found. Make sure you include it from your Latex distribution. + DVIPNG nem található. Ügyeljen arra, hogy a LaTeX disztribúciójából tartalmazza. + + + + An exception occured within the creation of the latex formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your latex installation is correct and report a bug if so. + Kivétel történt a LaTeX-képlet létrehozása során. +A(z) „{}” folyamat nullától eltérő visszatérési kóddal ({}) végződött: + +{} +Kérjük, ellenőrizze, hogy a LaTeX telepítése helyes-e, és ha igen, jelentse a hibát. + + + + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + A LaTeX telepítése nem tartalmaz néhány szükséges csomagot: + +- {} (https://ctan.org/pkg/{}) + +Győződjön meg arról, hogy az említett csomag telepítve van, vagy tiltsa le a LaTeX megjelenítést a LogarithmPlotterben. + + + + An exception occured within the creation of the latex formula. +Process '{}' took too long to finish: +{} +Please make sure your latex installation is correct and report a bug if so. + Kivétel történt a LaTeX-képlet létrehozása során. +A(z) „{}” folyamat túl sokáig tartott a befejezéshez: +{} +Kérjük, ellenőrizze, hogy a LaTeX telepítése helyes-e, és ha igen, jelentse a hibát. + + + + main + + + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + Ezt a fájlt a LogarithmPlotter egy újabb verziója hozta létre, és nem tölthető vissza a LogarithmPlotter v{} alkalmazásban. +Kérjük, frissítse a LogarithmPlottert a fájl megnyitásához. + + + + Could not open file "{}": +{} + Nem sikerült megnyitni a(z) „{}”-fájlt: +{} + + + + Could not open file: "{}" +File does not exist. + Nem sikerült megnyitni a(z) „{}”-fájlt: +A fájl nem létezik. + + + + Built with PySide6 (Qt) v{} and python v{} + PySide6 (Qt) v{} és python v{} segítségével épült + + + + name + + + + %1 %2 renamed to %3. + %1 %2 átnevezve erre: %3. + + + + parameters + + + above + ↑ Felett + + + + below + ↓ Alatt + + + + + left + ← Balra + + + + + right + → Jobbra + + + + above-left + ↖ Felett, balra + + + + above-right + ↗ Felett, jobbra + + + + below-left + ↙ Alatt, balra + + + + below-right + ↘ Alatt, jobbra + + + + center + >|< Középre + + + + top + ↑ Felső + + + + bottom + ↓ Alsó + + + + top-left + ↖ Bal felső + + + + top-right + ↗ Jobb felső + + + + bottom-left + ↙ Bal alsó + + + + bottom-right + ↘ Jobb alsó + + + + application + Alkalmazás + + + + function + Függvény + + + + high + Magas + + + + low + Alul + + + + Next to target + Cél mellé + + + + With label + Címkével + + + + Hidden + Rejtett + + + + phasebode + + Bode Phase + Bode-fázis + + + Bode Phases + Bode-fázisok + + + + point + + + Point + Pont + + + + Points + Pontok + + + + position + + + Position of %1 %2 set from "%3" to "%4". + %1 %2 áthelyezve innen: „%3” ide: „%4”. + + + + Position of %1 set from %2 to %3. + %1 áthelyezve innen: %2 ide: %3. + + + + prop + + + expression + Kifejezés + + + + definitionDomain + Abszcissza tartomány + + + + destinationDomain + Ordináta tartomány + + + + + + + + + + + + + labelPosition + Címke helyzete + + + + displayMode + Megjelenítési mód + + + + + + + + + + labelX + Címke X helyzete + + + + + drawPoints + Pontok megjelenítése + + + + + drawDashedLines + Szaggatott vonalak megjelenítése + + + + + om_0 + ω₀ + + + + pass + Áteresztő + + + + gain + Nagyságrend nyeresége + + + + omGraduation + ω₀ érettségi megjelenítése + + + + phase + Fázis + + + + unit + Egység használata + + + + + + x + X + + + + + y + Y + + + + pointStyle + Pontstílus + + + + probabilities + Valószínűségek listája + + + + text + Tartalom + + + + disableLatex + LaTeX-megjelenítés letiltása ennél a szövegnél + + + + targetElement + Tárgycél + + + + approximate + Kerekített számított érték megjelenítése + + + + rounding + Kerekítés + + + + displayStyle + Megjelenítési stílus + + + + targetValuePosition + Cél értékpozíciója + + + + defaultExpression + Alapértelmezett kifejezés + + + + baseValues + Kezdeményezési értékek + + + color + Szín + + + + labelContent + Címketartalom + + + + repartition + + Repartition + Elosztás + + + Repartition functions + Elosztási függvények + + + + sequence + + + Sequence + Sorozat + + + + Sequences + Sorozatok + + + + settingCategory + + + general + Általános + + + + editor + Kifejezésszerkesztő + + + + default + Alapértelmezett ábra + + + + sommegainsbode + + Bode Magnitudes Sum + Bode-nagyságrendek összege + + + + sommephasesbode + + Bode Phases Sum + Bode-fázisok összege + + + + text + + + Text + Szöveg + + + + Texts + Szövegek + + + + update + + + An update for LogarithmPlotter (v{}) is available. + Elérhető a Logaritmus-ábrázoló ({} verzió) frissítése. + + + + No update available. + Nincs telepíthető frissítés. + + + + Could not fetch update information: Server error {}. + Nem sikerült lekérni a frissítési adatokat: Kiszolgálóhiba: {}. + + + + Could not fetch update information: {}. + Nem sikerült lekérni a frissítési adatokat: {}. + + + + usage + + + integral(<from: number>, <to: number>, <f: ExecutableObject>) + integral(<alsó korlát: szám>, <felső korlát: szám>, <f: függvényszerű objektum>) + + + + + Usage: +%1 + Használat: +%1 + + + + + + Usage: +%1 +%2 + Használat: +%1 +%2 + + + + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + integral(<alsó korlát: szám>, <felső korlát: szám>, <függvény: karakterlánc>, <változó: karakterlánc>) + + + + derivative(<f: ExecutableObject>, <x: number>) + derivative(<f: függvényszerű objektum>, <x: szám>) + + + + derivative(<f: string>, <variable: string>, <x: number>) + derivált(<függvény: karakterlánc>, <változó: karakterlánc>, <x: szám>) + + + + visibility + + + + %1 %2 shown. + %1 %2 megjelenítve. + + + + + %1 %2 hidden. + %1 %2 rejtve. + + + + xcursor + + + X Cursor + X kurzor + + + + X Cursors + X kurzorok + + + diff --git a/assets/i18n/lp_nb_NO.ts b/assets/i18n/lp_nb_NO.ts new file mode 100644 index 0000000..97799c6 --- /dev/null +++ b/assets/i18n/lp_nb_NO.ts @@ -0,0 +1,1836 @@ + + + + + About + + + About LogarithmPlotter + Om + + + + LogarithmPlotter v%1 + LogarithmPlotter v%1 + + + + 2D plotter software to make BODE plots, sequences and repartition functions. + 2D-plotterprogramvare laget for opprettelse av Bode-diagram, sekvenser, og distribusjonsfunksjoner. + + + + Report a bug + Rapporter en feil + + + + Official website + + + + + AppMenuBar + + + &File + &Fil + + + + &Load... + &Last inn … + + + + &Save + &Lagre + + + + Save &As... + Lagre &som … + + + + &Quit + &Avslutt + + + + &Edit + &Rediger + + + + &Undo + &Angre + + + + &Redo + &Gjenta + + + + &Copy plot + &Kopier plott + + + + &Preferences + + + + + &Create + &Opprett + + + &Settings + &Innstillinger + + + Check for updates on startup + Se etter nye versjoner ved programstart + + + Reset redo stack automaticly + Tilbakestill angrehistorikk automatisk + + + + &Help + &Hjelp + + + + &Source code + + + + + &Report a bug + &Rapporter en feil + + + + &User manual + + + + + &Changelog + &Endringslogg + + + + &Help translating! + &Hjelp til å oversette! + + + + &Thanks + &Erkjennelser + + + + &About + &Om + + + + Save unsaved changes? + Lagre ikke-lagrede endringer? + + + + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + Dette plottet inneholder ikke-lagrede endringer. Hvis du gjør dette, vil alle ikke-lagrede data gå tapt. Fortsette? + + + + BaseDialog + + + Close + Lukk + + + + BoolSetting + + Check for updates on startup + Se etter nye versjoner ved programstart + + + + Browser + + + Filter... + + + + + Redo > + Angre > + + + + > Now + > Nå + + + + < Undo + < Angre + + + + Changelog + + + Fetching changelog... + Henter endringslogg… + + + + Close + Lukk + + + + CustomPropertyList + + + + + Create new %1 + + Opprett nytt %1 + + + + Pick on graph + + + + + Dialog + + + Edit properties of %1 %2 + Rediger egenskaper for %1 %2 + + + + LogarithmPlotter - Invalid object name + LogarithmPlotter - Ugyldig objektnavn + + + + An object with the name '%1' already exists. + Et objekt med navnet '%1' finnes allerede. + + + + Name + Navn + + + Label content + Etikett-innhold + + + + null + NULL + + + + name + navn + + + + name + value + navn + veri + + + + EditorDialog + + Edit properties of %1 %2 + Rediger egenskaper for %1 %2 + + + Name + Navn + + + Label content + Etikett-innhold + + + null + NULL + + + name + navn + + + name + value + navn + veri + + + + Create new %1 + + Opprett nytt %1 + + + + ExpressionEditor + + + Object Properties + + + + + Variables + + + + + Constants + + + + + Functions + Funksjoner + + + + Executable Objects + + + + + Objects + Objekter + + + + FileDialog + + + Export Logarithm Plot file + Eksporter logaritmeplott-fil + + + + Import Logarithm Plot file + Importer logaritmeplott-fil + + + + GreetScreen + + + Welcome to LogarithmPlotter + Velkommen til LogarithmPlotter + + + + Version %1 + Versjon %1 + + + Take a few seconds to configure LogarithmPlotter. +These settings can be changed at any time from the "Settings" menu. + Sett opp LogarithmPlotter. +Disse innstillingene kan endres når som helst fra «Innstillinger»-menyen. + + + Check for updates on startup (requires online connectivity) + Se etter nye versjoner ved programstart. (Krever tilkobling til Internett.) + + + Reset redo stack when a new action is added to history + Tilbakesitll angrehistorikk når en ny handling legges til + + + + User manual + + + + + Changelog + + + + + Preferences + + + + + Close + Lukk + + + + HistoryBrowser + + Redo > + Angre > + + + > Now + > Nå + + + < Undo + < Angre + + + + ListSetting + + + + Add Entry + + + + + Loading + + + Loading... + + + + + Finished rendering of %1 + + + + + LogarithmPlotter + + + untitled + + + + + Objects + Objekter + + + + Settings + Innstillinger + + + + History + Historikk + + + Saved plot to '%1'. + Lagret plott i «%1». + + + Loading file '%1'. + Laster inn «%1»-fil. + + + Unknown object type: %1. + Ukjent objekttype: %1. + + + Invalid file provided. + Ugyldig fil angitt. + + + Could not save file: + Kunne ikke lagre fil: + + + Loaded file '%1'. + Lastet inn filen «%1». + + + + Copied plot screenshot to clipboard! + Kopierte plott-skjermavbildning til utklippstavlen! + + + + &Update + &Oppdater + + + + &Update LogarithmPlotter + &Installer ny versjon av LogartimePlotter + + + + ObjectCreationGrid + + + + Create new: + + Opprett ny: + + + + ObjectLists + + + Hide all %1 + Skjul alle %1 + + + + Show all %1 + Vis alle %1 + + + Hide %1 %2 + Skjul %1 %2 + + + Show %1 %2 + Vis %1 %2 + + + Set %1 %2 position + Sett %1 %2 posisjon + + + Delete %1 %2 + Slett %1 %2 + + + Pick new color for %1 %2 + Velg ny farge for %1 %2 + + + + ObjectRow + + + Hide %1 %2 + Skjul %1 %2 + + + + Show %1 %2 + Vis %1 %2 + + + + Set %1 %2 position + Sett %1 %2 posisjon + + + + Delete %1 %2 + Slett %1 %2 + + + + Pick new color for %1 %2 + Velg ny farge for %1 %2 + + + + PickLocation + + + Pointer precision: + Peker-presisjon: + + + + Snap to grid: + + + + + Pick X + + + + + Pick Y + + + + + Open picker settings + + + + + Hide picker settings + + + + + (no pick selected) + + + + + PickLocationOverlay + + Pointer precision: + Peker-presisjon: + + + Snap to grid + Fest til rutenett + + + + Preferences + + + Close + Lukk + + + + Settings + + + + X Zoom + X-forstørrelse + + + + + Y Zoom + Y-forstørrelse + + + + + Min X + Min. X + + + + + Max Y + Maks. Y + + + + Max X + Maks. X + + + + Min Y + Min. Y + + + + + X Axis Step + X-aksesteg + + + + + Y Axis Step + Y-aksesteg + + + + + Line width + Linjebredde + + + + + Text size (px) + Tekststørrelse (piksler) + + + + + X Label + Navn på X-akse + + + + + Y Label + Navn på Y-akse + + + + + X Log scale + Logaritmisk skala i x + + + + + Show X graduation + Vis X-inndeling + + + + + Show Y graduation + Vis Y-inndeling + + + + Copy to clipboard + Kopier til utklippstavle + + + + Save plot + Lagre plott + + + + Save plot as + Lagre plott som + + + + Load plot + Last inn plott + + + + ThanksTo + + + Thanks and Contributions - LogarithmPlotter + + + + + Source code + + + + + Original library by Raphael Graf + + + + + Source + + + + + Ported to Javascript by Matthew Crumley + Overført til JavaScript av Matthew Crumley + + + + + + Website + + + + + Ported to QMLJS by Ad5001 + + + + + Libraries included + + + + + Email + + + + + English + + + + + French + + + + + German + + + + + Hungarian + + + + + + + + Github + GitHub + + + + Norwegian + + + + + Spanish + + + + + Tamil + + + + + Translations included + + + + + Improve + + + + + bodemagnitude + + + Bode Magnitude + Bode-magnitude + + + + Bode Magnitudes + Bode-magnituder + + + + + low-pass + lavpass + + + + + high-pass + høypass + + + + bodemagnitudesum + + + + Bode Magnitudes Sum + Bode-magnitudesum + + + + bodephase + + + Bode Phase + Bode-fase + + + + Bode Phases + Bode-faser + + + + bodephasesum + + + + Bode Phases Sum + Bode-fasesum + + + + changelog + + + Could not fetch changelog: Server error {}. + + + + + + Could not fetch update: {}. + + + + + color + + + + %1 %2's color changed from %3 to %4. + + + + + comment + + + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + + + + + The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) + + + + + Note: Specify the probability for each value. + + + + + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... + + + + + If you have latex enabled, you can use use latex markup in between $$ to create equations. + + + + + control + + + + + + + %1: + + + + + create + + + + New %1 %2 created. + Ny %1 %2 opprettet. + + + + delete + + + + %1 %2 deleted. + %1 %2 slettet. + + + + distribution + + + Repartition + Distribusjon + + + + Repartition functions + Distribusjonsfunksjoner + + + + editproperty + + + %1 of %2 %3 changed from "%4" to "%5". + %1 av %2 %3 endret fra «%4» til «%5». + + + + %1 of %2 changed from %3 to %4. + + + + + error + + + No object found with names %1. + + + + + No object found with name %1. + + + + + Object cannot be dependent on itself. + + + + + Circular dependency detected. Object %1 depends on %2. + + + + + Circular dependency detected. Objects %1 depend on %2. + + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + + + + + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + + + + + + Cannot find property %1 of object %2. + + + + + Undefined variable %1. + + + + + In order to be executed, object %1 must have at least one argument. + + + + + %1 cannot be executed. + + + + + + + Invalid expression. + + + + + Invalid expression (parity). + + + + + EOF + + + + + + Parse error [position %1]: %2 + + + + + Expected %1 + + + + + Unexpected %1 + + + + + Unexpected ".": member access is not permitted + + + + + Unexpected "[]": arrays are disabled. + + + + + Unexpected symbol: %1. + + + + + + Function %1 must have at least one argument. + + + + + Unknown character "%1". + + + + + + Illegal escape sequence: %1. + + + + + expression + + + + LogarithmPlotter - Parsing error + + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + + + + + LogarithmPlotter - Drawing error + + + + + Automatically close parenthesises and brackets + + + + + Enable syntax highlighting + + + + + Enable autocompletion + + + + + Color Scheme + + + + + function + + + Function + Funksjon + + + + Functions + Funksjoner + + + + gainbode + + Bode Magnitude + Bode-magnitude + + + Bode Magnitudes + Bode-magnituder + + + low-pass + lavpass + + + high-pass + høypass + + + + general + + + Check for updates on startup + Se etter nye versjoner ved programstart + + + + Reset redo stack automaticly + Tilbakestill angrehistorikk automatisk + + + + Enable LaTeX rendering + + + + + Enable threaded LaTeX renderer (experimental) + + + + + historylib + + New %1 %2 created. + Ny %1 %2 opprettet. + + + %1 %2 deleted. + %1 %2 slettet. + + + %1 of %2 %3 changed from "%4" to "%5". + %1 av %2 %3 endret fra «%4» til «%5». + + + %1 %2 shown. + %1 %2 vist. + + + %1 %2 hidden. + %1 %2 skjult. + + + + io + + Objects + Objekter + + + Settings + Innstillinger + + + History + Historikk + + + Saved plot to '%1'. + Lagret plott i «%1». + + + Loading file '%1'. + Laster inn «%1»-fil. + + + Unknown object type: %1. + Ukjent objekttype: %1. + + + Invalid file provided. + Ugyldig fil angitt. + + + Could not save file: + Kunne ikke lagre fil: + + + Loaded file '%1'. + Lastet inn filen «%1». + + + Copied plot screenshot to clipboard! + Kopierte plott-skjermavbildning til utklippstavlen! + + + &Update + &Oppdater + + + &Update LogarithmPlotter + &Installer ny versjon av LogartimePlotter + + + + latex + + + No Latex installation found. +If you already have a latex distribution installed, make sure it's installed on your path. +Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. + + + + + DVIPNG was not found. Make sure you include it from your Latex distribution. + + + + + An exception occured within the creation of the latex formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your latex installation is correct and report a bug if so. + + + + + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + + + + + An exception occured within the creation of the latex formula. +Process '{}' took too long to finish: +{} +Please make sure your latex installation is correct and report a bug if so. + + + + + main + + + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + + + + + Could not open file "{}": +{} + + + + + Could not open file: "{}" +File does not exist. + + + + + Built with PySide6 (Qt) v{} and python v{} + + + + + name + + + + %1 %2 renamed to %3. + + + + + parameters + + + above + + + + + below + + + + + + left + + + + + + right + + + + + above-left + + + + + above-right + + + + + below-left + + + + + below-right + + + + + center + + + + + top + + + + + bottom + + + + + top-left + + + + + top-right + + + + + bottom-left + + + + + bottom-right + + + + + application + + + + + function + + + + + high + + + + + low + + + + + Next to target + + + + + With label + + + + + Hidden + + + + + phasebode + + Bode Phase + Bode-fase + + + Bode Phases + Bode-faser + + + + point + + + Point + Punkt + + + + Points + Punkter + + + + position + + + Position of %1 %2 set from "%3" to "%4". + + + + + Position of %1 set from %2 to %3. + + + + + prop + + + expression + + + + + definitionDomain + + + + + destinationDomain + + + + + displayMode + + + + + + + + + + + + + + labelPosition + + + + + + + + + + + labelX + + + + + + drawPoints + + + + + + drawDashedLines + + + + + + om_0 + + + + + pass + + + + + gain + + + + + omGraduation + + + + + phase + + + + + unit + + + + + + + x + + + + + + y + + + + + pointStyle + + + + + probabilities + + + + + defaultExpression + + + + + baseValues + + + + + text + + + + + disableLatex + + + + + targetElement + + + + + approximate + + + + + rounding + + + + + displayStyle + + + + + targetValuePosition + + + + + labelContent + Etikett-innhold + + + + repartition + + Repartition + Distribusjon + + + Repartition functions + Distribusjonsfunksjoner + + + + sequence + + + Sequence + Følge + + + + Sequences + Følger + + + + settingCategory + + + general + + + + + editor + + + + + default + + + + + sommegainsbode + + Bode Magnitudes Sum + Bode-magnitudesum + + + + sommephasesbode + + Bode Phases Sum + Bode-fasesum + + + + text + + + Text + Tekst + + + + Texts + Tekster + + + + update + + + An update for LogarithmPlotter (v{}) is available. + En ny versjon av LogartimePlotter (v{}) er tilgjengelig. + + + + No update available. + Ingen nye versjoner. + + + + Could not fetch update information: Server error {}. + Fant ikke ut om det er noen nye versjoner. Tjenerfeil {}. + + + + Could not fetch update information: {}. + Kunne ikke hente info om hvorvidt det er nye versjoner: {}. + + + + usage + + + integral(<from: number>, <to: number>, <f: ExecutableObject>) + + + + + + Usage: +%1 + + + + + + + Usage: +%1 +%2 + + + + + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + + + + + derivative(<f: ExecutableObject>, <x: number>) + + + + + derivative(<f: string>, <variable: string>, <x: number>) + + + + + visibility + + + + %1 %2 shown. + %1 %2 vist. + + + + + %1 %2 hidden. + %1 %2 skjult. + + + + xcursor + + + X Cursor + X-peker + + + + X Cursors + X-pekere + + + diff --git a/assets/i18n/lp_ta.ts b/assets/i18n/lp_ta.ts new file mode 100644 index 0000000..2f3048c --- /dev/null +++ b/assets/i18n/lp_ta.ts @@ -0,0 +1,1597 @@ + + + + + About + + + About LogarithmPlotter + மடக்கை பற்றி + + + + LogarithmPlotter v%1 + மடக்கை சதித்திட்டம் வி 1 + + + + 2D plotter software to make BODE plots, sequences and repartition functions. + போட் அடுக்கு, காட்சிகள் மற்றும் விநியோக செயல்பாடுகளை உருவாக்க 2 டி ப்ளாட்டர் மென்பொருள். + + + + Report a bug + ஒரு பிழையைப் புகாரளிக்கவும் + + + + Official website + அதிகாரப்பூர்வ வலைத்தளம் + + + + AppMenuBar + + + &File + கோப்பு (&f) + + + + &Load... + & திறந்த… + + + + &Save + சேமி (&s) + + + + Save &As... + சேமிக்கவும்… + + + + &Quit + &வெளியேறு + + + + &Edit + திருத்து (&e) + + + + &Undo + செயல்தவிர் (&u) + + + + &Redo + மீண்டும்செய் (&r) + + + + &Copy plot + & சூழ்ச்சி நகலெடுக்கவும் + + + + &Preferences + &விருப்பத்தேர்வுகள் + + + + &Create + & உருவாக்கு + + + + &Help + உதவி (&h) + + + + &Source code + & மூலக் குறியீடு + + + + &Report a bug + ஒரு பிழையைப் புகாரளிக்கவும் + + + + &User manual + & பயனர் கையேடு + + + + &Changelog + & சேஞ்ச்லாக் + + + + &Help translating! + & மொழிபெயர்க்க உதவுங்கள்! + + + + &Thanks + & நன்றி + + + + &About + &பற்றி + + + + Save unsaved changes? + சேமிக்கப்படாத மாற்றங்களைச் சேமிக்கவா? + + + + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + இந்த சதித்திட்டத்தில் சேமிக்கப்படாத மாற்றங்கள் உள்ளன. இதைச் செய்வதன் மூலம், சேமிக்கப்படாத அனைத்து தரவும் இழக்கப்படும். தொடரவா? + + + + BaseDialog + + + Close + மூடு + + + + Browser + + + Filter... + வடிகட்டி… + + + + Redo > + மீண்டும்> + + + + > Now + > இப்போது + + + + < Undo + <செயல்தவிர்க்கவும் + + + + Changelog + + + Fetching changelog... + சேஞ்ச்லாக் பெறுதல்… + + + + Close + மூடு + + + + CustomPropertyList + + + + + Create new %1 + + புதிய %1 ஐ உருவாக்கவும் + + + + Pick on graph + வரைபடத்தைத் தேர்ந்தெடுங்கள் + + + + Dialog + + + Edit properties of %1 %2 + %1 %2 இன் பண்புகளைத் திருத்தவும் + + + + LogarithmPlotter - Invalid object name + மடக்கை - தவறான பொருள் பெயர் + + + + An object with the name '%1' already exists. + '%1' என்ற பெயரைக் கொண்ட ஒரு பொருள் ஏற்கனவே உள்ளது. + + + + Name + பெயர் + + + + null + சுழியம் + + + + name + பெயர் + + + + name + value + பெயர் + மதிப்பு + + + + ExpressionEditor + + + Object Properties + பொருள் பண்புகள் + + + + Variables + மாறிகள் + + + + Constants + மாறிலிகள் + + + + Functions + செயல்பாடுகள் + + + + Executable Objects + செயல்பாடு பொருள்கள் + + + + Objects + பொருள்கள் + + + + FileDialog + + + Export Logarithm Plot file + மடக்கை சூழ்ச்சி கோப்பை ஏற்றுமதி செய்யுங்கள் + + + + Import Logarithm Plot file + மடக்கை சூழ்ச்சி கோப்பை இறக்குமதி செய்க + + + + GreetScreen + + + Welcome to LogarithmPlotter + மடக்கை பிளாட்டருக்கு வருக + + + + Version %1 + பதிப்பு %1 + + + + User manual + பயனர் கையேடு + + + + Changelog + மாற்றபதிவு + + + + Preferences + விருப்பத்தேர்வுகள் + + + + Close + மூடு + + + + ListSetting + + + + Add Entry + + உள்ளீட்டைச் சேர்க்கவும் + + + + Loading + + + Loading... + ஏற்றுகிறது… + + + + Finished rendering of %1 + %1 இன் வழங்குதல் முடிந்தது + + + + LogarithmPlotter + + + untitled + தலைப்பிடப்படாத + + + + Objects + பொருள்கள் + + + + Settings + அமைப்புகள் + + + + History + வரலாறு + + + + Copied plot screenshot to clipboard! + இடைநிலைப்பலகைக்கு சூழ்ச்சி திரை சாட்டை நகலெடுத்தது! + + + + &Update + & புதுப்பிக்கவும் + + + + &Update LogarithmPlotter + & மடக்கை புதுப்பிக்கவும் + + + + ObjectCreationGrid + + + + Create new: + + புதியதை உருவாக்கு: + + + + ObjectLists + + + Hide all %1 + அனைத்து %1 ஐ மறைக்கவும் + + + + Show all %1 + அனைத்து %1 ஐக் காட்டு + + + + ObjectRow + + + Hide %1 %2 + %1 %2 ஐ மறைக்கவும் + + + + Show %1 %2 + %1 %2 ஐக் காட்டு + + + + Set %1 %2 position + %1 %2 நிலையை அமைக்கவும் + + + + Delete %1 %2 + %1 %2 ஐ நீக்கு + + + + Pick new color for %1 %2 + %1 %2 க்கு புதிய வண்ணத்தைத் தேர்ந்தெடுங்கள் + + + + PickLocation + + + Pointer precision: + சுட்டிக்காட்டி துல்லியம்: + + + + Snap to grid: + கட்டம் வரை: + + + + Pick X + ஃச் பிக் + + + + Pick Y + ஒய் ஐ தேர்வு செய்யுங்கள் + + + + Open picker settings + திறந்த பிக்கர் அமைப்புகள் + + + + Hide picker settings + பிக்கர் அமைப்புகளை மறைக்கவும் + + + + (no pick selected) + (தேர்ந்தெடுக்கப்பட்ட தேர்வு இல்லை) + + + + Preferences + + + Close + மூடு + + + + Settings + + + + X Zoom + ஃச் சூம் + + + + + Y Zoom + மற்றும் பெரிதாக்கு + + + + + Min X + என் ஃச் + + + + + Max Y + அதிகபட்சம் மற்றும் + + + + Max X + அதிகபட்ச ஃச் + + + + Min Y + Min ஒய் + + + + + X Axis Step + ஃச் அச்சு படி + + + + + Y Axis Step + ஒய் அச்சு படி + + + + + Line width + வரி அகலம் + + + + + Text size (px) + உரை அளவு (பிஎக்ச்) + + + + + X Label + ஃச் சிட்டை + + + + + Y Label + ஒய் சிட்டை + + + + + X Log scale + ஃச் பதிவு அளவுகோல் + + + + + Show X graduation + ஃச் பட்டப்படிப்பைக் காட்டு + + + + + Show Y graduation + ஒய் பட்டப்படிப்பைக் காட்டு + + + + Copy to clipboard + இடைநிலைப்பலகைக்கு நகலெடுக்கவும் + + + + Save plot + சதித்திட்டத்தை சேமிக்கவும்… + + + + Save plot as + சூழ்ச்சி சேமிக்கவும்… + + + + Load plot + திறந்த சதி… + + + + ThanksTo + + + Thanks and Contributions - LogarithmPlotter + நன்றி மற்றும் பங்களிப்புகள் - மடக்கை + + + + Source code + மூலக் குறியீடு + + + + Original library by Raphael Graf + ரபேல் கிராஃப் எழுதிய அசல் நூலகம் + + + + Source + மூலம் + + + + Ported to Javascript by Matthew Crumley + மத்தேயு க்ரம்லி எழுதிய சாவாச்கிரிப்டுக்கு அனுப்பப்பட்டது + + + + + + Website + வலைத்தளம் + + + + Ported to QMLJS by Ad5001 + AD5001 ஆல் QMLJS க்கு அனுப்பப்பட்டது + + + + Libraries included + நூலகங்கள் சேர்க்கப்பட்டுள்ளன + + + + Email + மின்னஞ்சல் + + + + English + ஆங்கிலம் + + + + French + பிரஞ்சு + + + + German + செர்மன் + + + + Hungarian + அங்கேரியன் + + + + + + + Github + கிரப் + + + + Norwegian + நோர்வே + + + + Spanish + ச்பானிச் + + + + Tamil + தமிழ் + + + + Translations included + மொழிபெயர்ப்புகள் சேர்க்கப்பட்டுள்ளன + + + + Improve + மேம்படுத்தவும் + + + + bodemagnitude + + + Bode Magnitude + போட் அளவு + + + + Bode Magnitudes + போட் அளவுகள் + + + + + low-pass + குறைந்த பாச் + + + + + high-pass + உயர்-பாச் + + + + bodemagnitudesum + + + + Bode Magnitudes Sum + போட் அளவு தொகை + + + + bodephase + + + Bode Phase + போட் கட்டம் + + + + Bode Phases + போட் கட்டங்கள் + + + + bodephasesum + + + + Bode Phases Sum + போட் கட்டங்கள் தொகை + + + + changelog + + + Could not fetch changelog: Server error {}. + சேஞ்ச்லாக் பெற முடியவில்லை: சேவையக பிழை {}. + + + + + Could not fetch update: {}. + சேஞ்ச்லாக் பெற முடியவில்லை: {}. + + + + color + + + + %1 %2's color changed from %3 to %4. + %1 %2 இன் நிறம் %3 முதல் %4 வரை மாற்றப்பட்டது. + + + + comment + + + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + Ex: r+* (ℝ⁺*), n (ℕ), z-* (ℤ⁻*),] 0; 1 [, {3; 4; 5} + + + + The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) + டொமைன் ஒரு தொடர்ச்சியான தொகுப்பாக இருக்கும்போது பின்வரும் அளவுருக்கள் பயன்படுத்தப்படுகின்றன. (எ.கா: ℕ, ℤ, {0; 3}…) + + + + Note: Specify the probability for each value. + குறிப்பு: ஒவ்வொரு மதிப்புக்கும் நிகழ்தகவைக் குறிப்பிடவும். + + + + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... + குறிப்பு: %1ₙ, %1 [n+1] ஐ %1ₙ₊₁ க்கு குறிக்க %1 [n] ஐப் பயன்படுத்தவும்… + + + + If you have latex enabled, you can use use latex markup in between $$ to create equations. + நீங்கள் லேடெக்ச் இயக்கப்பட்டிருந்தால், சமன்பாடுகளை உருவாக்க லேடெக்ச் மார்க்அப்பைப் பயன்படுத்தலாம். + + + + control + + + + + + + %1: + %1: + + + + create + + + + New %1 %2 created. + புதிய %1 %2 உருவாக்கப்பட்டது. + + + + delete + + + + %1 %2 deleted. + %1 %2 நீக்கப்பட்டது. + + + + distribution + + + Repartition + பரவல் + + + + Repartition functions + விநியோக செயல்பாடுகள் + + + + editproperty + + + %1 of %2 %3 changed from "%4" to "%5". + %2 %3 இல் %1 " %4" இலிருந்து " %5" ஆக மாற்றப்பட்டது. + + + + %1 of %2 changed from %3 to %4. + %2 இல் %1 %3 முதல் %4 வரை மாற்றப்பட்டது. + + + + error + + + No object found with names %1. + %1 பெயர்களைக் கொண்ட எந்த பொருளும் காணப்படவில்லை. + + + + No object found with name %1. + %1 என்ற பெயருடன் எந்த பொருளும் காணப்படவில்லை. + + + + Object cannot be dependent on itself. + பொருள் தன்னைச் சார்ந்து இருக்க முடியாது. + + + + Circular dependency detected. Object %1 depends on %2. + வட்ட சார்பு கண்டறியப்பட்டது. பொருள் %1 %2 ஐப் பொறுத்தது. + + + + Circular dependency detected. Objects %1 depend on %2. + வட்ட சார்பு கண்டறியப்பட்டது. பொருள்கள் %1 %2 ஐ சார்ந்துள்ளது. + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + சொத்து %1 க்கான வெளிப்பாட்டை பாகுபடுத்தும்போது பிழை: + %2 + + மதிப்பீடு செய்யப்பட்ட வெளிப்பாடு: %3 + + + + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + %1 %2 ஐ வரைய முயற்சிக்கும் போது பிழை: + %3 + + கடைசி மாற்றத்தை செயல்தவிர்க்கவும். + + + + + Cannot find property %1 of object %2. + பொருள் %2 இன் சொத்து %1 ஐக் கண்டுபிடிக்க முடியாது. + + + + Undefined variable %1. + வரையறுக்கப்படாத மாறி %1. + + + + In order to be executed, object %1 must have at least one argument. + செயல்படுத்தப்படுவதற்கு, பொருள் %1 க்கு குறைந்தது ஒரு உரையாடல் இருக்க வேண்டும். + + + + %1 cannot be executed. + %1 ஒரு செயல்பாடு அல்ல. + + + + + + Invalid expression. + தவறான வெளிப்பாடு. + + + + Invalid expression (parity). + தவறான வெளிப்பாடு (சமநிலை). + + + + EOF + வெளிப்பாட்டின் முடிவு + + + + + Parse error [position %1]: %2 + பாகுபடுத்தும் பிழை [நிலை %1]: %2 + + + + Expected %1 + எதிர்பார்க்கப்படும் %1 + + + + Unexpected %1 + எதிர்பாராத %1 + + + + Unexpected ".": member access is not permitted + எதிர்பாராதது ".": உறுப்பினர் அணுகல் அனுமதிக்கப்படவில்லை + + + + Unexpected "[]": arrays are disabled. + எதிர்பாராத "[]": வரிசைகள் முடக்கப்பட்டுள்ளன. + + + + Unexpected symbol: %1. + எதிர்பாராத சின்னம்: %1. + + + + + Function %1 must have at least one argument. + செயல்பாடு %1 க்கு குறைந்தது ஒரு உரையாடல் இருக்க வேண்டும். + + + + Unknown character "%1". + அறியப்படாத எழுத்து "%1". + + + + + Illegal escape sequence: %1. + சட்டவிரோத தப்பிக்கும் வரிசை: %1. + + + + expression + + + + LogarithmPlotter - Parsing error + மடக்கை பிளாட்டர் - பாகுபடுத்தும் பிழை + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + சொத்து %1 க்கான வெளிப்பாட்டை பாகுபடுத்தும்போது பிழை: + %2 + + மதிப்பீடு செய்யப்பட்ட வெளிப்பாடு: %3 + + + + LogarithmPlotter - Drawing error + மடக்கை - வரைதல் பிழை + + + + Automatically close parenthesises and brackets + தானாகவே அடைப்புக்குறிப்புகள் மற்றும் அடைப்புக்குறிகளை மூடு + + + + Enable syntax highlighting + தொடரியல் சிறப்பம்சத்தை இயக்கவும் + + + + Enable autocompletion + தன்னியக்கவியல் இயக்கவும் + + + + Color Scheme + வண்ணத் திட்டம் + + + + function + + + Function + சார்பு + + + + Functions + செயல்பாடுகள் + + + + general + + + Check for updates on startup + தொடக்கத்தின் புதுப்பிப்புகளைச் சரிபார்க்கவும் + + + + Reset redo stack automaticly + மீண்டும் அடுக்கை மீட்டமைக்கவும் + + + + Enable LaTeX rendering + லேடெக்ச் வழங்குதல் இயக்கவும் + + + + Enable threaded LaTeX renderer (experimental) + திரிக்கப்பட்ட லேடெக்ச் ரெண்டரரை இயக்கவும் (சோதனை) + + + + latex + + + No Latex installation found. +If you already have a latex distribution installed, make sure it's installed on your path. +Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. + லேடெக்ச் நிறுவல் எதுவும் கிடைக்கவில்லை. + உங்களிடம் ஏற்கனவே லேடெக்ச் வழங்கல் நிறுவப்பட்டிருந்தால், அது உங்கள் பாதையில் நிறுவப்பட்டுள்ளதா என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள். + இல்லையெனில், டெக்ச் லைவ் போன்ற லேடெக்ச் விநியோகத்தை https://tug.org/texlive/ இல் பதிவிறக்கம் செய்யலாம். + + + + DVIPNG was not found. Make sure you include it from your Latex distribution. + Dvipng கண்டுபிடிக்கப்படவில்லை. உங்கள் லேடெக்ச் விநியோகத்திலிருந்து இதைச் சேர்த்துள்ளீர்கள் என்பதை உறுதிப்படுத்திக் கொள்ளுங்கள். + + + + An exception occured within the creation of the latex formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your latex installation is correct and report a bug if so. + லேடெக்ச் சூத்திரத்தை உருவாக்குவதற்குள் விதிவிலக்கு ஏற்பட்டது. + '{}' ஐ பூச்சியமற்ற வருவாய் குறியீட்டோடு முடிந்தது {}: + + {} + உங்கள் லேடெக்ச் நிறுவல் சரியானது என்பதை உறுதிப்படுத்தவும், அப்படியானால் ஒரு பிழையைப் புகாரளிக்கவும். + + + + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + உங்கள் லேடெக்ச் நிறுவலில் தேவையான சில தொகுப்புகள் இல்லை: + + - {} (https://ctan.org/pkg/ {}) + + தொகுப்பு நிறுவப்பட்டிருப்பதை உறுதிசெய்து கொள்ளுங்கள், அல்லது லோகரிதம்லட்டரில் லேடெக்ச் வழங்குதல் முடக்கவும். + + + + An exception occured within the creation of the latex formula. +Process '{}' took too long to finish: +{} +Please make sure your latex installation is correct and report a bug if so. + லேடெக்ச் சூத்திரத்தை உருவாக்குவதற்குள் விதிவிலக்கு ஏற்பட்டது. + '{}' செயல்முறை முடிக்க அதிக நேரம் எடுத்தது: + {} + உங்கள் லேடெக்ச் நிறுவல் சரியானது என்பதை உறுதிப்படுத்தவும், அப்படியானால் ஒரு பிழையைப் புகாரளிக்கவும். + + + + main + + + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + இந்த கோப்பு மடக்கை படத்தின் மிக அண்மைக் கால பதிப்பால் உருவாக்கப்பட்டது, மேலும் லோகரிதம் பிளாட்டர் v {fol இல் பின்வாங்க முடியாது. + இந்த கோப்பைத் திறக்க லோகரிதில் பிளாட்டரைப் புதுப்பிக்கவும். + + + + Could not open file "{}": +{} + "{}" கோப்பைத் திறக்க முடியவில்லை: + {} + + + + Could not open file: "{}" +File does not exist. + கோப்பைத் திறக்க முடியவில்லை: "{}" + கோப்பு இல்லை. + + + + Built with PySide6 (Qt) v{} and python v{} + Pyside6 (qt) V {} மற்றும் பைதான் V {with உடன் கட்டப்பட்டுள்ளது + + + + name + + + + %1 %2 renamed to %3. + %1 %2 %3 என மறுபெயரிடப்பட்டது. + + + + parameters + + + above + மேலே + + + + below + கீழே + + + + + left + . இடது + + + + + right + சரி + + + + above-left + இடதுபுறம் மேலே + + + + above-right + ↗ மேலே வலதுபுறம் + + + + below-left + இடதுபுறமாக கீழே + + + + below-right + ↘ கீழே வலது கீழே + + + + center + > | <மையம் + + + + top + . மேல் + + + + bottom + கீழே + + + + top-left + ↖ மேல் இடது + + + + top-right + ↗ மேல் வலது + + + + bottom-left + ↙ கீழ் இடது + + + + bottom-right + வலது வலது + + + + application + பயன்பாடு + + + + function + சார்பு + + + + high + உயர்ந்த + + + + low + குறைந்த + + + + Next to target + இலக்கு அடுத்து + + + + With label + லேபிளுடன் + + + + Hidden + மறைக்கப்பட்ட + + + + point + + + Point + புள்ளியம் + + + + Points + பிரிவகம் + + + + position + + + Position of %1 %2 set from "%3" to "%4". + %1%2 "%3" இலிருந்து "%4" க்கு நகர்ந்தது. + + + + Position of %1 set from %2 to %3. + %1 %2 முதல் %3 வரை நகர்ந்தது. + + + + prop + + + expression + கோவை + + + + definitionDomain + டொமைன் + + + + destinationDomain + வீச்சு + + + + displayMode + காட்சி முறை + + + + + + + + + + + + + labelPosition + சிட்டை நிலை + + + + + + + + + + labelX + லேபிளின் ஃச் நிலை + + + + + drawPoints + புள்ளிகளைக் காட்டு + + + + + drawDashedLines + கோடு கோடுகளைக் காட்டு + + + + + om_0 + + + + + pass + கணவாய் + + + + gain + அளவு ஆதாயம் + + + + omGraduation + Ω₀ இல் பட்டப்படிப்பைக் காட்டு + + + + phase + கட்டம் + + + + unit + பயன்படுத்த அலகு + + + + + + x + ஃச் + + + + + y + ஒய் + + + + pointStyle + புள்ளி நடை + + + + probabilities + நிகழ்தகவுகள் பட்டியல் + + + + defaultExpression + இயல்புநிலை வெளிப்பாடு + + + + baseValues + துவக்க மதிப்புகள் + + + + text + உள்ளடக்கம் + + + + disableLatex + இந்த உரைக்கு லேடெக்ச் வழங்குதல் முடக்கு + + + + targetElement + இலக்கை எதிர்க்கவும் + + + + approximate + வட்டமான கணக்கிடப்பட்ட மதிப்பைக் காட்டு + + + + rounding + சுற்று + + + + displayStyle + காட்சி நடை + + + + targetValuePosition + இலக்கின் மதிப்பு நிலை + + + + labelContent + சிட்டை உள்ளடக்கம் + + + + sequence + + + Sequence + வரிசை + + + + Sequences + வரிசைகள் + + + + settingCategory + + + general + பொது + + + + editor + வெளிப்பாடு ஆசிரியர் + + + + default + இயல்புநிலை அமைப்புகள் + + + + text + + + Text + உரை + + + + Texts + நூல்கள் + + + + update + + + An update for LogarithmPlotter (v{}) is available. + மடக்கை (v {}) க்கான புதுப்பிப்பு கிடைக்கிறது. + + + + No update available. + புதுப்பிப்பு எதுவும் கிடைக்கவில்லை. + + + + Could not fetch update information: Server error {}. + புதுப்பிப்பு தகவலைப் பெற முடியவில்லை: சேவையக பிழை {}. + + + + Could not fetch update information: {}. + புதுப்பிப்பு தகவல்களைப் பெற முடியவில்லை: {}. + + + + usage + + + integral(<from: number>, <to: number>, <f: ExecutableObject>) + ஒருங்கிணைந்த (<இலிருந்து: எண்>, <க்கு: எண்>, <f: செயல்பாடு போன்ற பொருள்>) + + + + + Usage: +%1 + பயன்பாடு: + %1 + + + + + + Usage: +%1 +%2 + பயன்பாடு: + %1 + %2 + + + + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + ஒருங்கிணைந்த (<இலிருந்து: எண்>, <க்கு: எண்>, <f: சரம்>, <மாறி: சரம்>) + + + + derivative(<f: ExecutableObject>, <x: number>) + வழித்தோன்றல் (<f: செயல்பாடு போன்ற பொருள்>, <x: எண்>) + + + + derivative(<f: string>, <variable: string>, <x: number>) + வழித்தோன்றல் (<f: சரம்>, <மாறி: சரம்>, <x: எண்>) + + + + visibility + + + + %1 %2 shown. + %1 %2 காட்டப்பட்டுள்ளது. + + + + + %1 %2 hidden. + %1 %2 மறைக்கப்பட்டுள்ளது. + + + + xcursor + + + X Cursor + ஃச் கர்சர் + + + + X Cursors + ஃச் கர்சர்கள் + + + diff --git a/assets/i18n/lp_template.ts b/assets/i18n/lp_template.ts new file mode 100644 index 0000000..7f1bc28 --- /dev/null +++ b/assets/i18n/lp_template.ts @@ -0,0 +1,1569 @@ + + + + + About + + + About LogarithmPlotter + + + + + LogarithmPlotter v%1 + + + + + 2D plotter software to make BODE plots, sequences and repartition functions. + + + + + Report a bug + + + + + Official website + + + + + AppMenuBar + + + &File + + + + + &Load... + + + + + &Save + + + + + Save &As... + + + + + &Quit + + + + + &Edit + + + + + &Undo + + + + + &Redo + + + + + &Copy plot + + + + + &Preferences + + + + + &Create + + + + + &Help + + + + + &Source code + + + + + &Report a bug + + + + + &User manual + + + + + &Changelog + + + + + &Help translating! + + + + + &Thanks + + + + + &About + + + + + Save unsaved changes? + + + + + This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue? + + + + + BaseDialog + + + Close + + + + + Browser + + + Filter... + + + + + Redo > + + + + + > Now + + + + + < Undo + + + + + Changelog + + + Fetching changelog... + + + + + Close + + + + + CustomPropertyList + + + + + Create new %1 + + + + + Pick on graph + + + + + Dialog + + + Edit properties of %1 %2 + + + + + LogarithmPlotter - Invalid object name + + + + + An object with the name '%1' already exists. + + + + + Name + + + + + null + + + + + name + + + + + name + value + + + + + ExpressionEditor + + + Object Properties + + + + + Variables + + + + + Constants + + + + + Functions + + + + + Executable Objects + + + + + Objects + + + + + FileDialog + + + Export Logarithm Plot file + + + + + Import Logarithm Plot file + + + + + GreetScreen + + + Welcome to LogarithmPlotter + + + + + Version %1 + + + + + User manual + + + + + Changelog + + + + + Preferences + + + + + Close + + + + + ListSetting + + + + Add Entry + + + + + Loading + + + Loading... + + + + + Finished rendering of %1 + + + + + LogarithmPlotter + + + untitled + + + + + Objects + + + + + Settings + + + + + History + + + + + Copied plot screenshot to clipboard! + + + + + &Update + + + + + &Update LogarithmPlotter + + + + + ObjectCreationGrid + + + + Create new: + + + + + ObjectLists + + + Hide all %1 + + + + + Show all %1 + + + + + ObjectRow + + + Hide %1 %2 + + + + + Show %1 %2 + + + + + Set %1 %2 position + + + + + Delete %1 %2 + + + + + Pick new color for %1 %2 + + + + + PickLocation + + + Pointer precision: + + + + + Snap to grid: + + + + + Pick X + + + + + Pick Y + + + + + Open picker settings + + + + + Hide picker settings + + + + + (no pick selected) + + + + + Preferences + + + Close + + + + + Settings + + + + X Zoom + + + + + + Y Zoom + + + + + + Min X + + + + + + Max Y + + + + + Max X + + + + + Min Y + + + + + + X Axis Step + + + + + + Y Axis Step + + + + + + Line width + + + + + + Text size (px) + + + + + + X Label + + + + + + Y Label + + + + + + X Log scale + + + + + + Show X graduation + + + + + + Show Y graduation + + + + + Copy to clipboard + + + + + Save plot + + + + + Save plot as + + + + + Load plot + + + + + ThanksTo + + + Thanks and Contributions - LogarithmPlotter + + + + + Source code + + + + + Original library by Raphael Graf + + + + + Source + + + + + Ported to Javascript by Matthew Crumley + + + + + + + Website + + + + + Ported to QMLJS by Ad5001 + + + + + Libraries included + + + + + Email + + + + + English + + + + + French + + + + + German + + + + + Hungarian + + + + + + + + Github + + + + + Norwegian + + + + + Spanish + + + + + Tamil + + + + + Translations included + + + + + Improve + + + + + bodemagnitude + + + Bode Magnitude + + + + + Bode Magnitudes + + + + + + low-pass + + + + + + high-pass + + + + + bodemagnitudesum + + + + Bode Magnitudes Sum + + + + + bodephase + + + Bode Phase + + + + + Bode Phases + + + + + bodephasesum + + + + Bode Phases Sum + + + + + changelog + + + Could not fetch changelog: Server error {}. + + + + + + Could not fetch update: {}. + + + + + color + + + + %1 %2's color changed from %3 to %4. + + + + + comment + + + Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5} + + + + + The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...) + + + + + Note: Specify the probability for each value. + + + + + Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁... + + + + + If you have latex enabled, you can use use latex markup in between $$ to create equations. + + + + + control + + + + + + + %1: + + + + + create + + + + New %1 %2 created. + + + + + delete + + + + %1 %2 deleted. + + + + + distribution + + + Repartition + + + + + Repartition functions + + + + + editproperty + + + %1 of %2 %3 changed from "%4" to "%5". + + + + + %1 of %2 changed from %3 to %4. + + + + + error + + + No object found with names %1. + + + + + No object found with name %1. + + + + + Object cannot be dependent on itself. + + + + + Circular dependency detected. Object %1 depends on %2. + + + + + Circular dependency detected. Objects %1 depend on %2. + + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + + + + + Error while attempting to draw %1 %2: +%3 + +Undoing last change. + + + + + + Cannot find property %1 of object %2. + + + + + Undefined variable %1. + + + + + In order to be executed, object %1 must have at least one argument. + + + + + %1 cannot be executed. + + + + + + + Invalid expression. + + + + + Invalid expression (parity). + + + + + EOF + + + + + + Parse error [position %1]: %2 + + + + + Expected %1 + + + + + Unexpected %1 + + + + + Unexpected ".": member access is not permitted + + + + + Unexpected "[]": arrays are disabled. + + + + + Unexpected symbol: %1. + + + + + + Function %1 must have at least one argument. + + + + + Unknown character "%1". + + + + + + Illegal escape sequence: %1. + + + + + expression + + + + LogarithmPlotter - Parsing error + + + + + Error while parsing expression for property %1: +%2 + +Evaluated expression: %3 + + + + + LogarithmPlotter - Drawing error + + + + + Automatically close parenthesises and brackets + + + + + Enable syntax highlighting + + + + + Enable autocompletion + + + + + Color Scheme + + + + + function + + + Function + + + + + Functions + + + + + general + + + Check for updates on startup + + + + + Reset redo stack automaticly + + + + + Enable LaTeX rendering + + + + + Enable threaded LaTeX renderer (experimental) + + + + + latex + + + No Latex installation found. +If you already have a latex distribution installed, make sure it's installed on your path. +Otherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/. + + + + + DVIPNG was not found. Make sure you include it from your Latex distribution. + + + + + An exception occured within the creation of the latex formula. +Process '{}' ended with a non-zero return code {}: + +{} +Please make sure your latex installation is correct and report a bug if so. + + + + + Your LaTeX installation does not include some required packages: + +- {} (https://ctan.org/pkg/{}) + +Make sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter. + + + + + An exception occured within the creation of the latex formula. +Process '{}' took too long to finish: +{} +Please make sure your latex installation is correct and report a bug if so. + + + + + main + + + This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}. +Please update LogarithmPlotter to open this file. + + + + + Could not open file "{}": +{} + + + + + Could not open file: "{}" +File does not exist. + + + + + Built with PySide6 (Qt) v{} and python v{} + + + + + name + + + + %1 %2 renamed to %3. + + + + + parameters + + + above + + + + + below + + + + + + left + + + + + + right + + + + + above-left + + + + + above-right + + + + + below-left + + + + + below-right + + + + + center + + + + + top + + + + + bottom + + + + + top-left + + + + + top-right + + + + + bottom-left + + + + + bottom-right + + + + + application + + + + + function + + + + + high + + + + + low + + + + + Next to target + + + + + With label + + + + + Hidden + + + + + point + + + Point + + + + + Points + + + + + position + + + Position of %1 %2 set from "%3" to "%4". + + + + + Position of %1 set from %2 to %3. + + + + + prop + + + expression + + + + + definitionDomain + + + + + destinationDomain + + + + + displayMode + + + + + + + + + + + + + + labelPosition + + + + + + + + + + + labelX + + + + + + drawPoints + + + + + + drawDashedLines + + + + + + om_0 + + + + + pass + + + + + gain + + + + + omGraduation + + + + + phase + + + + + unit + + + + + + + x + + + + + + y + + + + + pointStyle + + + + + probabilities + + + + + defaultExpression + + + + + baseValues + + + + + text + + + + + disableLatex + + + + + targetElement + + + + + approximate + + + + + rounding + + + + + displayStyle + + + + + targetValuePosition + + + + + labelContent + + + + + sequence + + + Sequence + + + + + Sequences + + + + + settingCategory + + + general + + + + + editor + + + + + default + + + + + text + + + Text + + + + + Texts + + + + + update + + + An update for LogarithmPlotter (v{}) is available. + + + + + No update available. + + + + + Could not fetch update information: Server error {}. + + + + + Could not fetch update information: {}. + + + + + usage + + + integral(<from: number>, <to: number>, <f: ExecutableObject>) + + + + + + Usage: +%1 + + + + + + + Usage: +%1 +%2 + + + + + integral(<from: number>, <to: number>, <f: string>, <variable: string>) + + + + + derivative(<f: ExecutableObject>, <x: number>) + + + + + derivative(<f: string>, <variable: string>, <x: number>) + + + + + visibility + + + + %1 %2 shown. + + + + + + %1 %2 hidden. + + + + + xcursor + + + X Cursor + + + + + X Cursors + + + + diff --git a/assets/i18n/release.sh b/assets/i18n/release.sh new file mode 100755 index 0000000..58477c3 --- /dev/null +++ b/assets/i18n/release.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pyside6-lrelease *.ts diff --git a/assets/i18n/update.sh b/assets/i18n/update.sh new file mode 100755 index 0000000..98f4201 --- /dev/null +++ b/assets/i18n/update.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# +# This file automatically renames .mjs files to js, and (tries) to fix most common ECMAScript +# specificities so that lupdate doesn't cry out in pain. +# See also: https://bugreports.qt.io/browse/QTBUG-123819 +# + +escape() { + str="$1" + str="${str//\//\\/}" # Escape slashes + str="${str//\*/\\*}" # Escape asterixes + echo "$str" +} + +replace() { + file="$1" + from="$(escape "$2")" + to="$(escape "$3")" + sed -i "s/${from}/${to}/g" "$file" +} + +rm ../qml/eu/ad5001/LogarithmPlotter/js/index.mjs # Remove index which should not be scanned + +files=$(find ../../common/src -name '*.mjs') +for file in $files; do + echo "Moving '$file' to '${file%.*}.js'..." + mv "$file" "${file%.*}.js" + # Replacements to make it valid js + replace "${file%.*}.js" "^import" "/*import" + replace "${file%.*}.js" "^export *" "/*export *" + replace "${file%.*}.js" '.mjs"$' '.mjs"*/' + replace "${file%.*}.js" "^export default" "/*export default*/" + replace "${file%.*}.js" "^export" "/*export*/" + replace "${file%.*}.js" "async " "/*async */" + replace "${file%.*}.js" "await" "/*await */" + replace "${file%.*}.js" " #" "// #" + replace "${file%.*}.js" "this.#" "/*this.#*/" +done + +echo "----------------------------" +echo "| Updating translations... |" +echo "----------------------------" +pyside6-lupdate -extensions js,qs,qml,py -recursive ../../common/src -recursive ../../runtime-pyside6/LogarithmPlotter -ts lp_*.ts +# Updating locations in files +for lp in *.ts; do + echo "Replacing locations in $lp..." + for file in $files; do + replace "$lp" "${file%.*}.js" "$file" + done +done + +for file in $files; do + echo "Moving '${file%.*}.js' to '$file'..." + mv "${file%.*}.js" "$file" + # Resetting changes + replace "$file" "/*await */" "await" + replace "$file" "/*async */" "async " + replace "$file" "^/*export*/" "export" + replace "$file" "^/*export default*/" "export default" + replace "$file" '.mjs"*/' '.mjs"' + replace "$file" "^/*import" "import" + replace "$file" "^/*export" "export" + replace "$file" "// #" " #" + replace "$file" "/*this.#*/" "this.#" +done diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/angle.svg b/assets/icons/common/angle.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/angle.svg rename to assets/icons/common/angle.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/appearance.svg b/assets/icons/common/appearance.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/appearance.svg rename to assets/icons/common/appearance.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/arrow.svg b/assets/icons/common/arrow.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/arrow.svg rename to assets/icons/common/arrow.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/delete.svg b/assets/icons/common/close.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/delete.svg rename to assets/icons/common/close.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/delete.svg b/assets/icons/common/delete.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/delete.svg rename to assets/icons/common/delete.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/label.svg b/assets/icons/common/label.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/label.svg rename to assets/icons/common/label.svg diff --git a/assets/icons/common/manual.svg b/assets/icons/common/manual.svg new file mode 100644 index 0000000..9ca9b8e --- /dev/null +++ b/assets/icons/common/manual.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/common/new.svg b/assets/icons/common/new.svg new file mode 100644 index 0000000..7c7f6fd --- /dev/null +++ b/assets/icons/common/new.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/position.svg b/assets/icons/common/position.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/position.svg rename to assets/icons/common/position.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/settings.svg b/assets/icons/common/settings.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/settings.svg rename to assets/icons/common/settings.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/target.svg b/assets/icons/common/target.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/target.svg rename to assets/icons/common/target.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/Text.svg b/assets/icons/common/text.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/common/Text.svg rename to assets/icons/common/text.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/appearance.svg b/assets/icons/history/appearance.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/appearance.svg rename to assets/icons/history/appearance.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/create.svg b/assets/icons/history/create.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/create.svg rename to assets/icons/history/create.svg diff --git a/assets/icons/history/delete.svg b/assets/icons/history/delete.svg new file mode 120000 index 0000000..dca0794 --- /dev/null +++ b/assets/icons/history/delete.svg @@ -0,0 +1 @@ +../common/close.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/modify.svg b/assets/icons/history/modify.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/modify.svg rename to assets/icons/history/modify.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/name.svg b/assets/icons/history/name.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/name.svg rename to assets/icons/history/name.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/position.svg b/assets/icons/history/position.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/position.svg rename to assets/icons/history/position.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/visibility.svg b/assets/icons/history/visibility.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/history/visibility.svg rename to assets/icons/history/visibility.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/logarithmplotter.svg b/assets/icons/logarithmplotter.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/logarithmplotter.svg rename to assets/icons/logarithmplotter.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Function.svg b/assets/icons/objects/Function.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Function.svg rename to assets/icons/objects/Function.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Gain Bode.svg b/assets/icons/objects/Gain Bode.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Gain Bode.svg rename to assets/icons/objects/Gain Bode.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Phase Bode.svg b/assets/icons/objects/Phase Bode.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Phase Bode.svg rename to assets/icons/objects/Phase Bode.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Point.svg b/assets/icons/objects/Point.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Point.svg rename to assets/icons/objects/Point.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Repartition.svg b/assets/icons/objects/Repartition.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Repartition.svg rename to assets/icons/objects/Repartition.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Sequence.svg b/assets/icons/objects/Sequence.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Sequence.svg rename to assets/icons/objects/Sequence.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Text.svg b/assets/icons/objects/Text.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/Text.svg rename to assets/icons/objects/Text.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/X Cursor.svg b/assets/icons/objects/X Cursor.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/objects/X Cursor.svg rename to assets/icons/objects/X Cursor.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Definition Domain.svg b/assets/icons/properties/definitionDomain.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Definition Domain.svg rename to assets/icons/properties/definitionDomain.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Destination Domain.svg b/assets/icons/properties/destinationDomain.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Destination Domain.svg rename to assets/icons/properties/destinationDomain.svg diff --git a/assets/icons/properties/displayMode.svg b/assets/icons/properties/displayMode.svg new file mode 120000 index 0000000..41b711b --- /dev/null +++ b/assets/icons/properties/displayMode.svg @@ -0,0 +1 @@ +../common/appearance.svg \ No newline at end of file diff --git a/assets/icons/properties/displayStyle.svg b/assets/icons/properties/displayStyle.svg new file mode 120000 index 0000000..41b711b --- /dev/null +++ b/assets/icons/properties/displayStyle.svg @@ -0,0 +1 @@ +../common/appearance.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Expression.svg b/assets/icons/properties/expression.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Expression.svg rename to assets/icons/properties/expression.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Gain.svg b/assets/icons/properties/gain.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Gain.svg rename to assets/icons/properties/gain.svg diff --git a/assets/icons/properties/labelPosition.svg b/assets/icons/properties/labelPosition.svg new file mode 120000 index 0000000..3e4c849 --- /dev/null +++ b/assets/icons/properties/labelPosition.svg @@ -0,0 +1 @@ +../common/arrow.svg \ No newline at end of file diff --git a/assets/icons/properties/labelX.svg b/assets/icons/properties/labelX.svg new file mode 120000 index 0000000..4eecad3 --- /dev/null +++ b/assets/icons/properties/labelX.svg @@ -0,0 +1 @@ +../common/position.svg \ No newline at end of file diff --git a/assets/icons/properties/om_0.svg b/assets/icons/properties/om_0.svg new file mode 120000 index 0000000..e4130be --- /dev/null +++ b/assets/icons/properties/om_0.svg @@ -0,0 +1 @@ +../common/angle.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Pass.svg b/assets/icons/properties/pass.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Pass.svg rename to assets/icons/properties/pass.svg diff --git a/assets/icons/properties/phase.svg b/assets/icons/properties/phase.svg new file mode 120000 index 0000000..e4130be --- /dev/null +++ b/assets/icons/properties/phase.svg @@ -0,0 +1 @@ +../common/angle.svg \ No newline at end of file diff --git a/assets/icons/properties/pointStyle.svg b/assets/icons/properties/pointStyle.svg new file mode 120000 index 0000000..41b711b --- /dev/null +++ b/assets/icons/properties/pointStyle.svg @@ -0,0 +1 @@ +../common/appearance.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Rounding.svg b/assets/icons/properties/rounding.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/custom/Rounding.svg rename to assets/icons/properties/rounding.svg diff --git a/assets/icons/properties/targetElement.svg b/assets/icons/properties/targetElement.svg new file mode 120000 index 0000000..659962b --- /dev/null +++ b/assets/icons/properties/targetElement.svg @@ -0,0 +1 @@ +../common/target.svg \ No newline at end of file diff --git a/assets/icons/properties/targetValuePosition.svg b/assets/icons/properties/targetValuePosition.svg new file mode 120000 index 0000000..4eecad3 --- /dev/null +++ b/assets/icons/properties/targetValuePosition.svg @@ -0,0 +1 @@ +../common/position.svg \ No newline at end of file diff --git a/assets/icons/properties/text.svg b/assets/icons/properties/text.svg new file mode 120000 index 0000000..280f9ad --- /dev/null +++ b/assets/icons/properties/text.svg @@ -0,0 +1 @@ +../common/label.svg \ No newline at end of file diff --git a/assets/icons/properties/unit.svg b/assets/icons/properties/unit.svg new file mode 120000 index 0000000..e4130be --- /dev/null +++ b/assets/icons/properties/unit.svg @@ -0,0 +1 @@ +../common/angle.svg \ No newline at end of file diff --git a/assets/icons/properties/x.svg b/assets/icons/properties/x.svg new file mode 120000 index 0000000..4eecad3 --- /dev/null +++ b/assets/icons/properties/x.svg @@ -0,0 +1 @@ +../common/position.svg \ No newline at end of file diff --git a/assets/icons/properties/y.svg b/assets/icons/properties/y.svg new file mode 120000 index 0000000..4eecad3 --- /dev/null +++ b/assets/icons/properties/y.svg @@ -0,0 +1 @@ +../common/position.svg \ No newline at end of file diff --git a/assets/icons/settings/color.svg b/assets/icons/settings/color.svg new file mode 120000 index 0000000..41b711b --- /dev/null +++ b/assets/icons/settings/color.svg @@ -0,0 +1 @@ +../common/appearance.svg \ No newline at end of file diff --git a/assets/icons/settings/label.svg b/assets/icons/settings/label.svg new file mode 120000 index 0000000..280f9ad --- /dev/null +++ b/assets/icons/settings/label.svg @@ -0,0 +1 @@ +../common/label.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/linewidth.svg b/assets/icons/settings/linewidth.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/linewidth.svg rename to assets/icons/settings/linewidth.svg diff --git a/assets/icons/settings/text.svg b/assets/icons/settings/text.svg new file mode 120000 index 0000000..a705389 --- /dev/null +++ b/assets/icons/settings/text.svg @@ -0,0 +1 @@ +../common/text.svg \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/textsize.svg b/assets/icons/settings/textsize.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/textsize.svg rename to assets/icons/settings/textsize.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/timeline.svg b/assets/icons/settings/timeline.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/timeline.svg rename to assets/icons/settings/timeline.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/update.svg b/assets/icons/settings/update.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/update.svg rename to assets/icons/settings/update.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xaxisstep.svg b/assets/icons/settings/xaxisstep.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xaxisstep.svg rename to assets/icons/settings/xaxisstep.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xlabel.svg b/assets/icons/settings/xlabel.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xlabel.svg rename to assets/icons/settings/xlabel.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xmax.svg b/assets/icons/settings/xmax.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xmax.svg rename to assets/icons/settings/xmax.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xmin.svg b/assets/icons/settings/xmin.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xmin.svg rename to assets/icons/settings/xmin.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xzoom.svg b/assets/icons/settings/xzoom.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/xzoom.svg rename to assets/icons/settings/xzoom.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/yaxisstep.svg b/assets/icons/settings/yaxisstep.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/yaxisstep.svg rename to assets/icons/settings/yaxisstep.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/ylabel.svg b/assets/icons/settings/ylabel.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/ylabel.svg rename to assets/icons/settings/ylabel.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/ymax.svg b/assets/icons/settings/ymax.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/ymax.svg rename to assets/icons/settings/ymax.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/ymin.svg b/assets/icons/settings/ymin.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/ymin.svg rename to assets/icons/settings/ymin.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/yzoom.svg b/assets/icons/settings/yzoom.svg similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/icons/settings/yzoom.svg rename to assets/icons/settings/yzoom.svg diff --git a/assets/logarithmplotter.svg b/assets/logarithmplotter.svg new file mode 100644 index 0000000..1f3a5e0 --- /dev/null +++ b/assets/logarithmplotter.svg @@ -0,0 +1,171 @@ + + + + + LogarithmPlotter Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LogarithmPlotter Icon + 2024-10-06 + + + Adsooi <mail@ad5001.eu> + + + + + (c) Adsooi 2021-2024 + + + + + + + + + + + + + + + + diff --git a/logplotterfile.svg b/assets/logplotterfile.svg similarity index 100% rename from logplotterfile.svg rename to assets/logplotterfile.svg diff --git a/assets/native/linux/application-x-logarithm-plot.svg b/assets/native/linux/application-x-logarithm-plot.svg new file mode 120000 index 0000000..4e7d218 --- /dev/null +++ b/assets/native/linux/application-x-logarithm-plot.svg @@ -0,0 +1 @@ +../../logplotterfile.svg \ No newline at end of file diff --git a/linux/debian/changelog b/assets/native/linux/debian/changelog similarity index 100% rename from linux/debian/changelog rename to assets/native/linux/debian/changelog diff --git a/linux/debian/control b/assets/native/linux/debian/control.bkp similarity index 64% rename from linux/debian/control rename to assets/native/linux/debian/control.bkp index 15e0846..1df7fd8 100644 --- a/linux/debian/control +++ b/assets/native/linux/debian/control.bkp @@ -1,11 +1,11 @@ Package: logarithmplotter Source: logarithmplotter -Version: 0.5.0 +Version: 0.6.0 Architecture: all Maintainer: Ad5001 -Depends: python3, python3-pip, python3-pyside6-essentials (>= 6.4.0), texlive-latex-base, dvipng +Depends: python3 (>= 3.9), python3-pip, python3-pyside6-essentials (>= 6.7.0), python3-pyside6-addons (>= 6.7), texlive-latex-base, dvipng -Build-Depends: debhelper (>=11~), dh-python, dpkg-dev (>= 1.16.1~), python-setuptools, python3-all-dev (>=3.6) +Build-Depends: debhelper (>=11~), dh-python, dpkg-dev (>= 1.16.1~), python-setuptools Section: science Priority: optional Homepage: https://apps.ad5001.eu/logarithmplotter/ diff --git a/linux/debian/copyright b/assets/native/linux/debian/copyright similarity index 81% rename from linux/debian/copyright rename to assets/native/linux/debian/copyright index 6d05e77..138dae9 100644 --- a/linux/debian/copyright +++ b/assets/native/linux/debian/copyright @@ -3,6 +3,6 @@ Upstream-Name: logarithmplotter Upstream-Contact: Ad5001 Files: * -Copyright: 2023, Ad5001 +Copyright: 2024, Ad5001 License: GPL-3.0+ diff --git a/assets/native/linux/debian/depends.packaged b/assets/native/linux/debian/depends.packaged new file mode 100644 index 0000000..0bace27 --- /dev/null +++ b/assets/native/linux/debian/depends.packaged @@ -0,0 +1 @@ +python3 (>= 3.9), python3-pip, python3-pyside6.qtcore (>= 6), python3-pyside6.qtgui (>= 6), python3-pyside6.qtqml (>= 6), python3-pyside6.qtwidgets (>= 6), python3-pyside6.qtquick (>= 6), python3-pyside6.qtquickcontrols2 (>= 6), qml6-module-qt-labs-platform (>= 6), qml6-module-qtquick-dialogs (>= 6), texlive-latex-base, dvipng diff --git a/assets/native/linux/debian/depends.wheels b/assets/native/linux/debian/depends.wheels new file mode 100644 index 0000000..5b7d902 --- /dev/null +++ b/assets/native/linux/debian/depends.wheels @@ -0,0 +1 @@ +python3 (>= 3.9), python3-pip, python3-pyside6-essentials (>= 6.7.0), texlive-latex-base, dvipng diff --git a/linux/debian/recommends b/assets/native/linux/debian/recommends similarity index 100% rename from linux/debian/recommends rename to assets/native/linux/debian/recommends diff --git a/linux/debian/rules b/assets/native/linux/debian/rules similarity index 100% rename from linux/debian/rules rename to assets/native/linux/debian/rules diff --git a/linux/eu.ad5001.LogarithmPlotter.metainfo.xml b/assets/native/linux/eu.ad5001.LogarithmPlotter.metainfo.xml similarity index 70% rename from linux/eu.ad5001.LogarithmPlotter.metainfo.xml rename to assets/native/linux/eu.ad5001.LogarithmPlotter.metainfo.xml index 259edb1..f7e4c9d 100644 --- a/linux/eu.ad5001.LogarithmPlotter.metainfo.xml +++ b/assets/native/linux/eu.ad5001.LogarithmPlotter.metainfo.xml @@ -1,83 +1,71 @@ - - + + eu.ad5001.LogarithmPlotter - eu.ad5001.LogarithmPlotter.desktop logarithmplotter.desktop CC0-1.0 GPL-3.0+ LogarithmPlotter - http://apps.ad5001.eu/icons/apps/svg/logarithmplotter.svg + https://apps.ad5001.eu/icons/apps/svg/logarithmplotter.svg Create and edit Bode plots Erstellen und Bearbeiten von Bode-Diagrammen Créez et éditez des diagrammes de Bode Bode-diagramok létrehozása és szerkesztése Opprette og redigere Bode-diagrammer -

- LogarithmPlotter is, as it's name suggests, a plotter made with logarithm scales in mind. With an object system similar to Geogebra's, it allows dynamic creation of both logarithmic-scaled and non logarithmic-scaled plots with very few limitations. + LogarithmPlotter is, as its name suggests, a plotter made with logarithm scales in mind. With an object system similar to Geogebra's, it allows dynamic creation of both logarithmic-scaled and non logarithmic-scaled plots with very few limitations. +

+

+ LogarithmPlotter est, comme son nom l'indique, un créateur de graphes et diagrammes 2D réalisé avec l'échelle logarithmique en tête. Avec un système d'objets similaire à Geogebra, ce qui lui permet de créer des graphes à échelle logarithmique et non logarithmique avec peu de limitations. +

+

+ A LogarithmPlotter egy logaritmus-ábrázoló, amely logaritmikus léptékek figyelembevételével készült. A Geogebrához hasonló objektumrendszerrel dinamikus parcellák létrehozását teszi lehetővé, nagyon kevés korlátozással.

- It's primary use is to quickly create asymptotic Bode plots, but it's extensible nature and ability to switch to non-logarithmic scales allow it to create other things with it, like sequences or statistical repartition functions. + Its primary use is to quickly create asymptotic Bode plots, but its extensible nature and ability to switch to non-logarithmic scales allow it to create other things with it, like sequences or statistical repartition functions. +

+

+ Son intérêt principal est de permettre de créer des diagrammes asymptotiques de Bode, mais sa nature extensible et sa capacité à passer à une échelle non-logarithmique lui permet de créer d'autres choses. +

+

+ Elsődleges felhasználása az aszimptotikus Bode-ábrák gyors létrehozása, de bővíthető jellege és a nem logaritmikus skálákra váltás lehetősége lehetővé teszi, hogy más dolgokat is létrehozzon vele, például sorozatokat vagy statisztikai újraosztási függvényeket.

Features:

+

Fonctionnalités:

  • Graphical objects (points, fonctions, Bode magnitudes...) management system
  • Complete object edition
  • Advanced history system
  • Diagram looks customisation
  • +
  • Système de gestion des objets graphiques (points, fonctions, gains de Bode...)
  • +
  • Modification complète des objets
  • +
  • Système d'historique avancé
  • +
  • Personnalisation de l'apparence des diagrammes

LogarithmPlotter is available in:

+

LogarithmPlotter est disponible en:

  • 🇬🇧 English
  • 🇫🇷 French
  • 🇩🇪 German
  • 🇭🇺 Hungarian
  • 🇳🇴 Norwergian
  • +
  • 🇪🇸 Spanish
  • +
  • 🇬🇧 Anglais
  • +
  • 🇫🇷 Français
  • +
  • 🇩🇪 Allemand
  • +
  • 🇭🇺 Hongrois
  • +
  • 🇳🇴 Norvégien
  • +
  • 🇪🇸 Espagnol
-

Learn more: https://apps.ad5001.eu/logarithmplotter/

-

- LogarithmPlotter est, comme son nom l'indique, un créateur de graphes et diagrammes 2D réalisé avec l'échelle logarithmique en tête. Avec un système d'objets similaire à Geogebra, ce qui lui permet de créer des graphes à échelle logarithmique et non logarithmique avec peu de limitations. -

-

- Son intérêt principal est de permettre de créer des diagrammes asymptotiques de Bode, mais sa nature extensible et sa capacité à passer à une échelle non-logarithmique lui permet de créer d'autres choses. -

-

Fonctionnalités:

-
    -
  • Système de gestion des objets graphiques (points, fonctions, gains de Bode...)
  • -
  • Modification complète des objets
  • -
  • Système d'historique avancé
  • -
  • Personnalisation de l'apparence des diagrammes
  • -
-

LogarithmPlotter est disponible en:

-
    -
  • 🇬🇧 Anglais
  • -
  • 🇫🇷 Français
  • -
  • 🇩🇪 Allemand
  • -
  • 🇭🇺 Hongrois
  • -
  • 🇳🇴 Norvégien
  • -
-

En savoir plus: https://apps.ad5001.eu/fr/logarithmplotter/

-

- A LogarithmPlotter egy logaritmus-ábrázoló, amely logaritmikus léptékek figyelembevételével készült. A Geogebrához hasonló objektumrendszerrel dinamikus parcellák létrehozását teszi lehetővé, nagyon kevés korlátozással. -

-

- Elsődleges felhasználása az aszimptotikus Bode-ábrák gyors létrehozása, de bővíthető jellege és a nem logaritmikus skálákra váltás lehetősége lehetővé teszi, hogy más dolgokat is létrehozzon vele, például sorozatokat vagy statisztikai újraosztási függvényeket. -

Science Education - Qt https://apps.ad5001.eu/logarithmplotter/ @@ -85,39 +73,66 @@ https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/ https://hosted.weblate.org/engage/logarithmplotter/ - https://apps.ad5001.eu/img/en/gain.png?v=0.5 - https://apps.ad5001.eu/img/en/logarithmplotter/phase.png?v=0.5 - https://apps.ad5001.eu/img/en/logarithmplotter/welcome.png?v=0.5 - https://apps.ad5001.eu/img/de/gain.png?v=0.5 - https://apps.ad5001.eu/img/de/logarithmplotter/phase.png?v=0.5 - https://apps.ad5001.eu/img/de/logarithmplotter/welcome.png?v=0.5 - https://apps.ad5001.eu/img/fr/gain.png?v=0.5 - https://apps.ad5001.eu/img/fr/logarithmplotter/phase.png?v=0.5 - https://apps.ad5001.eu/img/fr/logarithmplotter/welcome.png?v=0.5 - https://apps.ad5001.eu/img/hu/gain.png?v=0.5 - https://apps.ad5001.eu/img/hu/logarithmplotter/phase.png?v=0.5 - https://apps.ad5001.eu/img/hu/logarithmplotter/welcome.png?v=0.5 - https://apps.ad5001.eu/img/no/gain.png?v=0.5 - https://apps.ad5001.eu/img/no/logarithmplotter/phase.png?v=0.5 - https://apps.ad5001.eu/img/no/logarithmplotter/welcome.png?v=0.5 + + https://apps.ad5001.eu/img/en/logarithmplotter/gain.png?v=0.6 + https://apps.ad5001.eu/img/de/logarithmplotter/gain.png?v=0.6 + https://apps.ad5001.eu/img/fr/logarithmplotter/gain.png?v=0.6 + https://apps.ad5001.eu/img/hu/logarithmplotter/gain.png?v=0.6 + https://apps.ad5001.eu/img/no/logarithmplotter/gain.png?v=0.6 + https://apps.ad5001.eu/img/es/logarithmplotter/gain.png?v=0.6 + Main view of LogarithmPlotter showing an asymptotic Bode magnitude plot. + Die Hauptansicht des LogarithmPlotters zeigt eine asymptotische Bode-Magnitude-Darstellung. + Vue principale de LogarithmPlotter montrant un tracé asymptotique d'une magnitude de Bode. + A LogarithmPlotter fő nézete, amely egy aszimptotikus Bode-magnitúdó ábrát mutat. + Hovedvisning av LogarithmPlotter som viser et asymptotisk Bode-størrelsesplott. + Vista principal de LogarithmPlotter mostrando un gráfico asintótico de una magnitud de Bode. + + + https://apps.ad5001.eu/img/en/logarithmplotter/phase.png?v=0.6 + https://apps.ad5001.eu/img/de/logarithmplotter/phase.png?v=0.6 + https://apps.ad5001.eu/img/fr/logarithmplotter/phase.png?v=0.6 + https://apps.ad5001.eu/img/hu/logarithmplotter/phase.png?v=0.6 + https://apps.ad5001.eu/img/no/logarithmplotter/phase.png?v=0.6 + https://apps.ad5001.eu/img/es/logarithmplotter/phase.png?v=0.6 + Main view of LogarithmPlotter showing an asymptotic Bode phase plot. + Hauptansicht des LogarithmPlotters mit einer asymptotischen Bode-Phasendarstellung. + Vue principale de LogarithmPlotter montrant un tracé asymptotique d'une phase de Bode. + A LogarithmPlotter fő nézete, amely egy aszimptotikus Bode-fázis ábrát mutat. + Hovedvisning av LogarithmPlotter som viser et asymptotisk Bode-fasediagram. + Vista principal de LogarithmPlotter mostrando un gráfico asintótico de una fase de Bode. + + + https://apps.ad5001.eu/img/en/logarithmplotter/welcome.png?v=0.6 + https://apps.ad5001.eu/img/de/logarithmplotter/welcome.png?v=0.6 + https://apps.ad5001.eu/img/fr/logarithmplotter/welcome.png?v=0.6 + https://apps.ad5001.eu/img/hu/logarithmplotter/welcome.png?v=0.6 + https://apps.ad5001.eu/img/no/logarithmplotter/welcome.png?v=0.6 + https://apps.ad5001.eu/img/es/logarithmplotter/welcome.png?v=0.6 + LogarithmPlotter's welcome page. + LogarithmPlotter's Willkommensseite. + Page d'accueil de LogarithmPlotter. + LogarithmPlotter üdvözlő oldala. + LogarithmPlotters velkomstside. + Página de bienvenida de LogarithmPlotter. + - + - medium + 768 - xlarge - xsmall + 3840 + 360 - + -

Changes for v0.5.0:

+

Changes for v0.5.0:

New

  • New, reworked application icon.
  • @@ -149,19 +164,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.5.0/logarithmplotter-v0.5.0-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.5.0/logarithmplotter-v0.5.0-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.5.0/LogarithmPlotter-v0.5.0-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.5.0/LogarithmPlotter-v0.5.0-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.5.0/logarithmplotter-0.5.0.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.5.0/logarithmplotter-0.5.0.tar.gz -

    Changes for v0.4.0:

    +

    Changes for v0.4.0:

    Changes

    • Fully ported to PySide6 (Qt6).
    • @@ -198,19 +213,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.4.0/logarithmplotter-v0.4.0-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.4.0/logarithmplotter-v0.4.0-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.4.0/LogarithmPlotter-v0.4.0-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.4.0/LogarithmPlotter-v0.4.0-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.4.0/logarithmplotter-0.4.0.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.4.0/logarithmplotter-0.4.0.tar.gz -

      Changes for v0.3.0:

      +

      Changes for v0.3.0:

      New

      • New completely revamped expression editor:
      • @@ -264,19 +279,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.3.0/logarithmplotter-v0.3.0-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.3.0/logarithmplotter-v0.3.0-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.3.0/LogarithmPlotter-v0.3.0-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.3.0/LogarithmPlotter-v0.3.0-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.3.0/logarithmplotter-0.3.0.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.3.0/logarithmplotter-0.3.0.tar.gz -

        Changes for v0.2.0:

        +

        Changes for v0.2.0:

        New

        • (EXPERIMENTAL) LogarithmPlotter now has an optional LaTeX integration.
        • @@ -317,19 +332,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.2.0/logarithmplotter-v0.2.0-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.2.0/logarithmplotter-v0.2.0-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.2.0/LogarithmPlotter-v0.2.0-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.2.0/LogarithmPlotter-v0.2.0-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.2.0/logarithmplotter-0.2.0.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.2.0/logarithmplotter-0.2.0.tar.gz -

          Changes for v0.1.8:

          +

          Changes for v0.1.8:

          New

          • There is now a user manual for LogarithmPlotter! Contributions apprecriated.
          • @@ -362,19 +377,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.8/logarithmplotter-v0.1.8-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.8/logarithmplotter-v0.1.8-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.8/LogarithmPlotter-v0.1.8-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.8/LogarithmPlotter-v0.1.8-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.8/logarithmplotter-0.1.8.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.8/logarithmplotter-0.1.8.tar.gz -

            Changes for v0.1.7:

            +

            Changes for v0.1.7:

            New

            • The history browser has been completly redesigned, improving UX.
            • @@ -418,19 +433,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.7/logarithmplotter-v0.1.7-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.7/logarithmplotter-v0.1.7-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.7/LogarithmPlotter-v0.1.7-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.7/LogarithmPlotter-v0.1.7-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.7/logarithmplotter-0.1.7.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.7/logarithmplotter-0.1.7.tar.gz -

              Changes for v0.1.6:

              +

              Changes for v0.1.6:

              New

              • A new changelog popup is available at startup and in the help menu.
              • @@ -459,19 +474,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.6/logarithmplotter-v0.1.6-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.6/logarithmplotter-v0.1.6-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.6/LogarithmPlotter-v0.1.6-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.6/LogarithmPlotter-v0.1.6-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.6/LogarithmPlotter-v0.1.6.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.6/LogarithmPlotter-v0.1.6.tar.gz -

                Changes for v0.1.5:

                +

                Changes for v0.1.5:

                New

                • LogarithmPlotter has now better handling of very high values in logarithmic scale.
                • @@ -489,19 +504,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.5/logarithmplotter-v0.1.5-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.5/logarithmplotter-v0.1.5-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.5/LogarithmPlotter-v0.1.5-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.5/LogarithmPlotter-v0.1.5-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.5/LogarithmPlotter-v0.1.5.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.5/LogarithmPlotter-v0.1.5.tar.gz -

                  Changes for v0.1.4:

                  +

                  Changes for v0.1.4:

                  New

                  • LogarithmPlotter detects unsaved changes.
                  • @@ -520,19 +535,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.4/logarithmplotter-v0.1.4-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.4/logarithmplotter-v0.1.4-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.4/LogarithmPlotter-v0.1.4-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.4/LogarithmPlotter-v0.1.4-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.4/LogarithmPlotter-v0.1.4.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.4/LogarithmPlotter-v0.1.4.tar.gz -

                    Changes for v0.1.3:

                    +

                    Changes for v0.1.3:

                    Fixed bugs

                    • Sandboxed packages (snapcraft and flatpak) won't show error messages related to update checks.
                    • @@ -541,19 +556,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.3/logarithmplotter-v0.1.3-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.3/logarithmplotter-v0.1.3-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.3/LogarithmPlotter-v0.1.3-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.3/LogarithmPlotter-v0.1.3-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.3/LogarithmPlotter-v0.1.3.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.3/LogarithmPlotter-v0.1.3.tar.gz -

                      Changes for v0.1.2:

                      +

                      Changes for v0.1.2:

                      Fixed bugs

                      • Unable to move Bode diagrams elements when having deleted the sum element.
                      • @@ -565,19 +580,19 @@ - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.2/logarithmplotter-v0.1.2-setup.exe + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.2/logarithmplotter-v0.1.2-setup.exe - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.2/LogarithmPlotter-v0.1.2-setup.dmg + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.2/LogarithmPlotter-v0.1.2-setup.dmg - https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.2/LogarithmPlotter-v0.1.2.tar.gz + https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v0.1.2/LogarithmPlotter-v0.1.2.tar.gz -

                        Changes for v0.1:

                        +

                        Changes for v0.1:

                        • Initial release.
                        @@ -586,8 +601,10 @@ - Ad5001 - mail@ad5001.eu + + Ad5001 + https://ad5001.eu + Plot @@ -602,7 +619,8 @@ Phase Sequence Distribution + Qt - + diff --git a/linux/logarithmplotter.desktop b/assets/native/linux/logarithmplotter.desktop similarity index 81% rename from linux/logarithmplotter.desktop rename to assets/native/linux/logarithmplotter.desktop index 56a0b14..70637c5 100644 --- a/linux/logarithmplotter.desktop +++ b/assets/native/linux/logarithmplotter.desktop @@ -7,14 +7,14 @@ GenericName[de]=2D-Grafiksoftware mit logarithmischer Skalierung GenericName[fr]=Logiciel de traçage à l'échelle logarithmique GenericName[hu]=Síkbeli ábrázolásszoftver GenericName[no]=2D-plotterprogramvare -Comment=Create BODE diagrams, sequences and distribution functions +Comment=Create Bode diagrams, sequences and distribution functions Comment[de]=Erstellung von Bode-Diagramms, Folgen und Verteilungsfunktionen -Comment[fr]=Créer des diagrammes de BODE, des suites et des fonctions de répartition +Comment[fr]=Créer des diagrammes de Bode, des suites et des fonctions de répartition Comment[hu]=Bode-ábrák, sorozatok és újraosztási függvények létrehozása TryExec=logarithmplotter Exec=logarithmplotter %f -Icon=logplotter +Icon=logarithmplotter MimeType=application/x-logarithm-plot; Terminal=false StartupNotify=false diff --git a/linux/snapcraft/launcher/launch-logarithmplotter b/assets/native/linux/snapcraft/launcher/launch-logarithmplotter similarity index 100% rename from linux/snapcraft/launcher/launch-logarithmplotter rename to assets/native/linux/snapcraft/launcher/launch-logarithmplotter diff --git a/linux/x-logarithm-plot.xml b/assets/native/linux/x-logarithm-plot.xml similarity index 90% rename from linux/x-logarithm-plot.xml rename to assets/native/linux/x-logarithm-plot.xml index 31b5c9d..888641e 100644 --- a/linux/x-logarithm-plot.xml +++ b/assets/native/linux/x-logarithm-plot.xml @@ -1,7 +1,7 @@ - Logarithm Plot File + Logarithmic Plot File Fichier Graphe Logarithmique diff --git a/mac/Info.plist b/assets/native/mac/Info.plist similarity index 98% rename from mac/Info.plist rename to assets/native/mac/Info.plist index d7c0c50..1ba304b 100644 --- a/mac/Info.plist +++ b/assets/native/mac/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.5.0 + 0.6.0 NSHighResolutionCapable UTExportedTypeDeclarations diff --git a/mac/install-bg.png b/assets/native/mac/install-bg.png similarity index 100% rename from mac/install-bg.png rename to assets/native/mac/install-bg.png diff --git a/mac/install-bg.xcf b/assets/native/mac/install-bg.xcf similarity index 100% rename from mac/install-bg.xcf rename to assets/native/mac/install-bg.xcf diff --git a/mac/logarithmplotter.icns b/assets/native/mac/logarithmplotter.icns similarity index 100% rename from mac/logarithmplotter.icns rename to assets/native/mac/logarithmplotter.icns diff --git a/mac/logarithmplotter.iconset/icon_128x128.png b/assets/native/mac/logarithmplotter.iconset/icon_128x128.png similarity index 100% rename from mac/logarithmplotter.iconset/icon_128x128.png rename to assets/native/mac/logarithmplotter.iconset/icon_128x128.png diff --git a/mac/logarithmplotter.iconset/icon_16x16.png b/assets/native/mac/logarithmplotter.iconset/icon_16x16.png similarity index 100% rename from mac/logarithmplotter.iconset/icon_16x16.png rename to assets/native/mac/logarithmplotter.iconset/icon_16x16.png diff --git a/mac/logarithmplotter.iconset/icon_256x256.png b/assets/native/mac/logarithmplotter.iconset/icon_256x256.png similarity index 100% rename from mac/logarithmplotter.iconset/icon_256x256.png rename to assets/native/mac/logarithmplotter.iconset/icon_256x256.png diff --git a/mac/logarithmplotter.iconset/icon_32x32.png b/assets/native/mac/logarithmplotter.iconset/icon_32x32.png similarity index 100% rename from mac/logarithmplotter.iconset/icon_32x32.png rename to assets/native/mac/logarithmplotter.iconset/icon_32x32.png diff --git a/mac/logarithmplotter.iconset/icon_512x512.png b/assets/native/mac/logarithmplotter.iconset/icon_512x512.png similarity index 100% rename from mac/logarithmplotter.iconset/icon_512x512.png rename to assets/native/mac/logarithmplotter.iconset/icon_512x512.png diff --git a/mac/logarithmplotterfile.icns b/assets/native/mac/logarithmplotterfile.icns similarity index 100% rename from mac/logarithmplotterfile.icns rename to assets/native/mac/logarithmplotterfile.icns diff --git a/mac/logarithmplotterfile.iconset/icon_128x128.png b/assets/native/mac/logarithmplotterfile.iconset/icon_128x128.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_128x128.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_128x128.png diff --git a/mac/logarithmplotterfile.iconset/icon_128x128@2x.png b/assets/native/mac/logarithmplotterfile.iconset/icon_128x128@2x.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_128x128@2x.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_128x128@2x.png diff --git a/mac/logarithmplotterfile.iconset/icon_16x16.png b/assets/native/mac/logarithmplotterfile.iconset/icon_16x16.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_16x16.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_16x16.png diff --git a/mac/logarithmplotterfile.iconset/icon_16x16@2x.png b/assets/native/mac/logarithmplotterfile.iconset/icon_16x16@2x.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_16x16@2x.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_16x16@2x.png diff --git a/mac/logarithmplotterfile.iconset/icon_256x256.png b/assets/native/mac/logarithmplotterfile.iconset/icon_256x256.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_256x256.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_256x256.png diff --git a/mac/logarithmplotterfile.iconset/icon_256x256@2x.png b/assets/native/mac/logarithmplotterfile.iconset/icon_256x256@2x.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_256x256@2x.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_256x256@2x.png diff --git a/mac/logarithmplotterfile.iconset/icon_32x32.png b/assets/native/mac/logarithmplotterfile.iconset/icon_32x32.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_32x32.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_32x32.png diff --git a/mac/logarithmplotterfile.iconset/icon_32x32@2x.png b/assets/native/mac/logarithmplotterfile.iconset/icon_32x32@2x.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_32x32@2x.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_32x32@2x.png diff --git a/mac/logarithmplotterfile.iconset/icon_512x512.png b/assets/native/mac/logarithmplotterfile.iconset/icon_512x512.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_512x512.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_512x512.png diff --git a/mac/logarithmplotterfile.iconset/icon_512x512@2x.png b/assets/native/mac/logarithmplotterfile.iconset/icon_512x512@2x.png similarity index 100% rename from mac/logarithmplotterfile.iconset/icon_512x512@2x.png rename to assets/native/mac/logarithmplotterfile.iconset/icon_512x512@2x.png diff --git a/win/inst_banner.bmp b/assets/native/win/inst_banner.bmp similarity index 100% rename from win/inst_banner.bmp rename to assets/native/win/inst_banner.bmp diff --git a/win/installer.nsi b/assets/native/win/installer.nsi similarity index 98% rename from win/installer.nsi rename to assets/native/win/installer.nsi index 7c5b248..bf38199 100644 --- a/win/installer.nsi +++ b/assets/native/win/installer.nsi @@ -11,10 +11,10 @@ Unicode True !define PROG_ID "LogarithmPlotter.File.1" !define DEV_NAME "Ad5001" !define WEBSITE "https://apps.ad5001.eu/logarithmplotter" -!define VERSION_SHORT "0.5.0" +!define VERSION_SHORT "0.6.0" !define APP_VERSION "${VERSION_SHORT}.0" -!define COPYRIGHT "Ad5001 (c) 2021-2024" -!define DESCRIPTION "Create graphs with logarithm scales." +!define COPYRIGHT "Ad5001 (c) 2021-2025" +!define DESCRIPTION "Create graphs with logarithmic scales." !define REG_UNINSTALL "Software\Microsoft\Windows\CurrentVersion\Uninstall\LogarithmPlotter" !define REG_APPPATHS "Software\Microsoft\Windows\CurrentVersion\App Paths\logarithmplotter.exe" diff --git a/win/logarithmplotter.ico b/assets/native/win/logarithmplotter.ico similarity index 100% rename from win/logarithmplotter.ico rename to assets/native/win/logarithmplotter.ico diff --git a/ci/all.lpf b/ci/all.lpf new file mode 100644 index 0000000..9eeb0b9 --- /dev/null +++ b/ci/all.lpf @@ -0,0 +1 @@ +LPFv1{"xzoom":100,"yzoom":10,"xmin":0.2696454905834007,"ymax":33.115625,"xaxisstep":"4","yaxisstep":"π","xaxislabel":"","yaxislabel":"","logscalex":true,"linewidth":1,"showxgrad":true,"showygrad":true,"textsize":18,"history":[[["CreateNewObject",["A","Point",["A",true,"#941A97","name + value","1","0","above","●"]]],["EditedPosition",["A","Point","1","175.36","0","9.9"]],["CreateNewObject",["f","Function",["f",true,"#6E590E","name + value","x","ℝ⁺*","ℝ","application","above",1,true,true]]],["EditedProperty",["f","Function","expression","x","((x / 2) - 1)",true]],["CreateNewObject",["t","Text",["t",true,"#118455","null","1","0","center","New text",false]]],["EditedPosition",["t","Text","1","36.48","0","(-13.7)"]],["EditedProperty",["t","Text","text","New text","AEZA",false]],["CreateNewObject",["ω","Point",["ω",true,"#5A3A52","name","1","0","above","●"]]],["CreateNewObject",["G₀","Gain Bode",["G₀",true,"#5A3A52","name + value","ω","high","20","below",1,false]]],["EditedPosition",["ω","Point","1","17.76","0","(-8.9)"]],["EditedProperty",["G₀","Gain Bode","gain","20","10",true]],["EditedProperty",["G₀","Gain Bode","labelPosition","below","below-left",false]],["EditedProperty",["G₀","Gain Bode","pass","high","low",false]],["EditedProperty",["G₀","Gain Bode","labelX",1,62.61,false]],["CreateNewObject",["X","X Cursor",["X",true,"#5909A9","name + value","1",null,"left",true,3,"— — — — — — —","Next to target"]]],["EditedProperty",["X","X Cursor","x","1","5.04",true]],["CreateNewObject",["u","Sequence",["u",true,"#78929E","name + value",true,true,{"1":"n"},{"0":0},"above",1]]],["EditedProperty",["u","Sequence","defaultExpression",{"1":"n"},{"1":"n+1"},false]],["EditedProperty",["u","Sequence","defaultExpression",{"1":"n+1"},{"1":"n+1"},false]],["EditedProperty",["u","Sequence","baseValues",{"0":0},{"0":"-1"},false]],["EditedProperty",["u","Sequence","baseValues",{"0":"-1"},{"0":"-1"},false]],["CreateNewObject",["F_X","Repartition",["F_X",true,"#231931","name + value",{"0":"0"},"above",1]]],["EditedProperty",["F_X","Repartition","labelX",1,12.64,false]],["EditedProperty",["f","Function","labelPosition","above","right",false]],["EditedProperty",["f","Function","labelX",1,30,false]],["EditedProperty",["u","Sequence","labelX",1,3,false]],["EditedProperty",["F_X","Repartition","labelX",12.64,40,false]],["EditedProperty",["ω","Point","labelPosition","above","below",false]],["CreateNewObject",["ω₀","Point",["ω₀","#7C2981","name","name + value","1","0","above","●"]]],["CreateNewObject",["φ₀","Phase Bode",["φ₀",true,"#7C2981","name + value","ω₀","90","°","below",1]]],["EditedPosition",["ω₀","Point","1","3","0","(-8)"]],["EditedPosition",["ω₀","Point","3","2","(-8)","8"]],["EditedProperty",["ω₀","Point","labelPosition","above","above-right",false]],["EditedProperty",["u","Sequence","labelPosition","above","above-left",false]],["EditedProperty",["u","Sequence","labelX",3,20,false]],["EditedProperty",["G","Somme gains Bode","labelX",1,2,false]]],[]],"width":1000,"height":500,"objects":{"Point":[["A",true,"#941A97","name + value","175.36","9.9","above","●"],["ω",true,"#5A3A52","name","17.76","(-8.9)","below","●"],["ω₀",false,"name","name","2","8","above-right","●"]],"Function":[["f",true,"#6E590E","name + value","((x / 2) - 1)","ℝ⁺*","ℝ","application","right",30,true,true]],"Text":[["t",true,"#118455","null","36.48","(-13.7)","center","AEZA",false]],"Gain Bode":[["G₀",true,"#5A3A52","name + value","ω","low","10","below-left",62.61,false]],"Somme gains Bode":[["G",true,"#A83C72","name + value","above",2]],"X Cursor":[["X",true,"#5909A9","name + value","5.04",null,"left",true,null,"— — — — — — —","Next to target"]],"Sequence":[["u",true,"#78929E","name + value",true,true,{"1":"n+1"},{"0":"-1"},"above-left",20]],"Repartition":[["F_X",true,"#231931","name + value",{"0":"0"},"above",40]],"Phase Bode":[["φ₀",true,"#7C2981","name + value","ω₀","90","°","below",1]],"Somme phases Bode":[["φ",true,"#A08B14","name + value","above",1]]},"type":"logplotv1"} \ No newline at end of file diff --git a/ci/drone.yml b/ci/drone.yml index 3bcf9f2..54ef658 100644 --- a/ci/drone.yml +++ b/ci/drone.yml @@ -6,43 +6,43 @@ platform: arch: amd64 steps: -- name: submodules - image: alpine/git - commands: - - git submodule update --init --recursive + - name: submodules + image: alpine/git + commands: + - git submodule update --init --recursive -- name: Linux test - image: ad5001/ubuntu-pyside6-xvfb:jammy-6.6.1 - commands: - - xvfb-run python3 run.py --test-build --no-check-for-updates - - xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test1.lpf - - xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test2.lpf - when: - event: [ push, tag ] + - name: Build + image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node + commands: + - cd common && npm install && cd .. + - bash scripts/build.sh -# - name: Windows test -# image: ad5001/ubuntu-pyside6-xvfb-wine:win7-6.5.0-rev1 -# commands: -# - # For some reason, launching GUI apps with wine, even with xvfb-run, fails. -# - xvfb-run python run.py --test-build --no-check-for-updates -# - xvfb-run python run.py --test-build --no-check-for-updates ./ci/test1.lpf -# - xvfb-run python run.py --test-build --no-check-for-updates ./ci/test2.lpf -# when: -# event: [ push, tag ] + - name: Unit Tests + image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node + commands: + - cd common && npm install -D && cd .. + - xvfb-run bash scripts/run-tests.sh --no-rebuild + when: + event: [ push, tag ] -# - name: Linux packaging -# image: ad5001/ubuntu-pyside6-xvfb:jammy-6.5.0 -# commands: -# - bash scripts/package-linux.sh -# when: -# event: [ push, tag ] + - name: File Tests + image: ad5001/ubuntu-pyside-xvfb:linux-6-latest-latex-node + commands: + - xvfb-run python3 run.py --test-build --no-check-for-updates + - xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test1.lpf + - xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/test2.lpf + - xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/all.lpf + - xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/magnitude.lpf + - xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/phase.lpf + - xvfb-run python3 run.py --test-build --no-check-for-updates ./ci/stress.lpf + when: + event: [ push, tag ] - -- name: Windows building - image: ad5001/ubuntu-pyside6-xvfb-wine:win10-6.6.1 - commands: - - bash scripts/build-wine.sh - - bash scripts/package-wine.sh - when: - event: [ push, tag ] + - name: Windows build + image: ad5001/ubuntu-pyside-xvfb:wine-6-latest + commands: + - bash scripts/build-wine.sh --no-rebuild + - bash scripts/package-wine.sh + when: + event: [ push, tag ] diff --git a/ci/magnitude.lpf b/ci/magnitude.lpf new file mode 100644 index 0000000..35ac7ae --- /dev/null +++ b/ci/magnitude.lpf @@ -0,0 +1 @@ +LPFv1{"xzoom":120,"yzoom":10,"xmin":0.5,"ymax":25,"xaxisstep":"4","yaxisstep":"4","xaxislabel":"ω (rad/s)","yaxislabel":"G (dB)","logscalex":true,"linewidth":2,"showxgrad":false,"showygrad":true,"textsize":17,"history":[[["CreateNewObject",["ω","Point",["ω",true,"#94AE66","name","1","0","top","●"]]],["CreateNewObject",["G₀","Gain Bode",["G₀",true,"#94AE66","name + value","ω","high","20","below",1,false]]],["EditedProperty",["G₀","Gain Bode","gain","20","0",true]],["EditedProperty",["ω","Point","y","0","10",true]],["EditedVisibility",["ω","Point","visible"]],["EditedProperty",["G","Somme gains Bode","labelX",1,10,false]],["CreateNewObject",["ω₀","Point",["ω₀",true,"#A38B4D","name","1","0","top","●"]]],["CreateNewObject",["G₁","Gain Bode",["G₁",true,"#A38B4D","name + value","ω₀","high","20","below",1,false]]],["EditedProperty",["G₁","Gain Bode","pass","high","low",false]],["EditedProperty",["G₁","Gain Bode","gain","20","(-20)",true]],["EditedVisibility",["ω₀","Point","visible"]],["EditedProperty",["ω₁","Point","x","1","10",true]],["CreateNewObject",["ω₀","Point",["ω₀",true,"#5F04A2","name","1","0","top","●"]]],["CreateNewObject",["G₂","Gain Bode",["G₂",true,"#5F04A2","name + value","ω₀","high","20","below",1,false]]],["EditedVisibility",["ω₀","Point","visible"]],["EditedProperty",["G₂","Gain Bode","labelX",1,5,false]],["EditedProperty",["ω₂","Point","x","1","5",true]],["EditedProperty",["G₂","Gain Bode","pass","high","low",false]],["EditedProperty",["G₂","Gain Bode","gain","20","(-20)",true]],["EditedProperty",["ω₁","Point","x","10","13",true]],["EditedProperty",["ω₁","Point","x","13","30",true]],["EditedProperty",["G₀","Gain Bode","labelPosition","below","below",false]],["EditedProperty",["G₀","Gain Bode","labelX",1,2,false]],["EditedProperty",["G₀","Gain Bode","labelPosition","below","above",false]],["EditedProperty",["G₂","Gain Bode","labelX",5,40,false]],["EditedProperty",["G₂","Gain Bode","labelX",40,20,false]],["EditedProperty",["G₂","Gain Bode","labelX",20,10,false]],["EditedProperty",["G₁","Gain Bode","labelX",1,40,false]],["EditedProperty",["G₁","Gain Bode","labelPosition","below","above-left",false]],["EditedProperty",["G₁","Gain Bode","labelPosition","above-left","above-right",false]],["EditedProperty",["G₁","Gain Bode","labelX",40,45,false]],["EditedProperty",["G","Somme gains Bode","labelX",10,4,false]],["EditedProperty",["G₀","Gain Bode","labelX",2,20,false]],["EditedProperty",["G₁","Gain Bode","omGraduation",false,true,false]],["EditedProperty",["G₂","Gain Bode","omGraduation",false,true,false]],["EditedProperty",["G","Somme gains Bode","labelX",4,2,false]],["EditedProperty",["G","Somme gains Bode","labelPosition","above","below",false]],["EditedProperty",["G₁","Gain Bode","omGraduation",true,false,false]],["EditedProperty",["ω₁","Point","x","30","0",true]],["EditedVisibility",["ω₁","Point","visible"]],["EditedProperty",["G₁","Gain Bode","gain","(-20)","20",true]],["EditedProperty",["ω₁","Point","x","1","0.05",true]],["EditedProperty",["ω₁","Point","y","0","(-20)",true]],["EditedProperty",["ω₁","Point","x","0.05","0.1",true]],["EditedProperty",["G₁","Gain Bode","labelX",45,2,false]],["EditedProperty",["ω","Point","y","10","(-10)",true]],["EditedProperty",["ω","Point","y","(-10)","(-5)",true]],["EditedProperty",["G","Somme gains Bode","labelX",2,15,false]],["CreateNewObject",["X","X Cursor",["X",true,"#61AD9E","name + value","1",null,"left",true,3,"— — — — — — —","Next to target"]]],["EditedProperty",["X","X Cursor","x","1","50",true]],["EditedProperty",["G₂","Gain Bode","color","#5F04A2","#5f04a2",false]],["EditedProperty",["X","X Cursor","color","#61AD9E","#5f04a2",false]],["CreateNewObject",["text","Text",["text",true,"#242159","null","1","0","center","New text"]]],["EditedProperty",["text","Text","x","1","20",true]],["EditedProperty",["text","Text","x","20","50",true]],["EditedProperty",["text","Text","labelPosition","center","right",false]],["EditedProperty",["text","Text","labelPosition","right","left",false]],["EditedProperty",["text","Text","y","0","20",true]],["EditedProperty",["text","Text","text","New text","10",false]],["EditedProperty",["text","Text","text","10","10ω",false]],["EditedProperty",["text","Text","text","10ω","10ω₂",false]],["EditedProperty",["X","X Cursor","color","#5f04a2","#5f04a2",false]],["EditedProperty",["text","Text","color","#242159","#5f04a2",false]],["EditedProperty",["text","Text","disableLatex",false,true,false]],["EditedProperty",["G₁","Gain Bode","labelPosition","above-right","above-left",false]],["ColorChanged",["G₀","Gain Bode","#94AE66","#00aa00"]],["ColorChanged",["G₀","Gain Bode","#00aa00","#0000ff"]],["ColorChanged",["ω","Point","#94AE66","#0000ff"]],["EditedProperty",["ω₂","Point","labelPosition","top","above-left",false]],["EditedProperty",["ω₂","Point","labelPosition","above-left","below-left",false]],["EditedProperty",["ω₂","Point","labelPosition","below-left","above-right",false]],["EditedProperty",["text","Text","disableLatex",false,true,false]]],[]],"width":961,"height":500,"objects":{"Point":[["ω",false,"#0000ff","name","1","(-5)","top","●"],["ω₁",false,"#A38B4D","name","0.1","(-20)","top","●"],["ω₂",true,"#5F04A2","name","5","0","above-right","●"]],"Gain Bode":[["G₀",true,"#0000ff","name","ω","high","0","above",20,false],["G₁",true,"#A38B4D","name","ω₁","low","20","above-left",2,false],["G₂",true,"#5f04a2","name","ω₂","low","(-20)","below",10,true]],"Somme gains Bode":[["G",true,"#3E6E2A","name + value","below",15]],"X Cursor":[["X",true,"#5f04a2","null","50",null,"left",true,3,"— — — — — — —","Next to target"]],"Text":[["text",true,"#5f04a2","null","50","20","left","10ω₂",false]]},"type":"logplotv1"} diff --git a/ci/phase.lpf b/ci/phase.lpf new file mode 100644 index 0000000..361d157 --- /dev/null +++ b/ci/phase.lpf @@ -0,0 +1 @@ +LPFv1{"xzoom":100,"yzoom":100,"xmin":0.5,"ymax":2,"xaxisstep":"4","yaxisstep":"pi/4","xaxislabel":"ω (rad/s)","yaxislabel":"φ (rad)","logscalex":true,"linewidth":2,"showxgrad":true,"showygrad":true,"textsize":20,"history":[[["CreateNewObject",["f","Function",["f",true,"#989E2D","name + value","x","ℝ⁺*","ℝ","application","above",1,true,true]]],["EditedProperty",["f","Function","expression","x","(x ^ 20)",true]],["EditedProperty",["f","Function","expression","(x ^ 20)","(20 * (log10 x))",true]],["DeleteObject",["f","Function",["f",true,"#989E2D","name + value","(20 * (log10 x))","ℝ⁺*","ℝ","application","above",1,true,true]]],["CreateNewObject",["ω","Point",["ω",true,"#995178","name","1","0","bottom","●"]]],["CreateNewObject",["φ₀","Phase Bode",["φ₀",true,"#995178","name + value","ω","90","°","below",1]]],["EditedProperty",["φ₀","Phase Bode","phase","90","0",true]],["EditedProperty",["φ₀","Phase Bode","unit","°","rad",false]],["EditedProperty",["ω","Point","y","0","((-pi) / 2)",true]],["EditedVisibility",["ω","Point","visible"]],["EditedProperty",["φ₀","Phase Bode","labelX",1,10,false]],["CreateNewObject",["ω₀","Point",["ω₀",true,"#037753","name","1","0","bottom","●"]]],["CreateNewObject",["φ₁","Phase Bode",["φ₁",true,"#037753","name + value","ω₀","90","°","below",1]]],["EditedProperty",["ω₀","Point","x","1","10",true]],["EditedProperty",["φ₁","Phase Bode","unit","°","rad",false]],["EditedProperty",["φ₁","Phase Bode","phase","90","(pi / 2)",true]],["EditedProperty",["ω₀","Point","x","10","5",true]],["EditedProperty",["ω₀","Point","labelPosition","bottom","top-left",false]],["EditedProperty",["φ₁","Phase Bode","labelX",1,2,false]],["EditedProperty",["φ","Somme phases Bode","labelX",1,2,false]],["ColorChanged",["φ","Somme phases Bode","#665B74","#550000"]],["EditedProperty",["ω₀","Point","labelPosition","top-left","above-left",false]],["EditedProperty",["ω₀","Point","labelPosition","above-left","above-right",false]]],[]],"width":1000,"height":500,"objects":{"Function":[],"Point":[["ω",false,"#995178","name","1","((-pi) / 2)","below","●"],["ω₀",true,"#037753","name","5","0","above-right","●"]],"Phase Bode":[["φ₀",true,"#995178","name","ω","0","rad","below",10],["φ₁",true,"#037753","name","ω₀","(pi / 2)","rad","below",2]],"Somme phases Bode":[["φ",true,"#550000","name + value","above",2]]},"type":"logplotv1"} \ No newline at end of file diff --git a/ci/stress.lpf b/ci/stress.lpf new file mode 100644 index 0000000..05e171c --- /dev/null +++ b/ci/stress.lpf @@ -0,0 +1 @@ +LPFv1{"xzoom":150,"yzoom":10,"xmin":0.49556664658184385,"ymax":25.120703,"xaxisstep":"4","yaxisstep":"4","xaxislabel":"","yaxislabel":"","logscalex":true,"linewidth":1,"showxgrad":true,"showygrad":true,"textsize":18,"history":[[["CreateNewObject",["A","Point",["A",true,"#A35D32","name + value","1","0","above","●"]]],["CreateNewObject",["B","Point",["B",true,"#AF370C","name + value","1","0","above","●"]]],["CreateNewObject",["C","Point",["C",true,"#AB9221","name + value","1","0","above","●"]]],["CreateNewObject",["D","Point",["D",true,"#43A98E","name + value","1","0","above","●"]]],["CreateNewObject",["E","Point",["E",true,"#1F1C6A","name + value","1","0","above","●"]]],["CreateNewObject",["F","Point",["F",true,"#407099","name + value","1","0","above","●"]]],["CreateNewObject",["J","Point",["J",true,"#9B4B7A","name + value","1","0","above","●"]]],["CreateNewObject",["K","Point",["K",true,"#3B5698","name + value","1","0","above","●"]]],["CreateNewObject",["L","Point",["L",true,"#0C6A4D","name + value","1","0","above","●"]]],["CreateNewObject",["M","Point",["M",true,"#350945","name + value","1","0","above","●"]]],["CreateNewObject",["N","Point",["N",true,"#46511E","name + value","1","0","above","●"]]],["CreateNewObject",["O","Point",["O",true,"#0B318B","name + value","1","0","above","●"]]],["CreateNewObject",["P","Point",["P",true,"#354125","name + value","1","0","above","●"]]],["CreateNewObject",["Q","Point",["Q",true,"#598608","name + value","1","0","above","●"]]],["CreateNewObject",["R","Point",["R",true,"#733D64","name + value","1","0","above","●"]]],["CreateNewObject",["S","Point",["S",true,"#7477A8","name + value","1","0","above","●"]]],["CreateNewObject",["T","Point",["T",true,"#5B6034","name + value","1","0","above","●"]]],["CreateNewObject",["U","Point",["U",true,"#951A57","name + value","1","0","above","●"]]],["CreateNewObject",["V","Point",["V",true,"#72804E","name + value","1","0","above","●"]]],["CreateNewObject",["W","Point",["W",true,"#7B1070","name + value","1","0","above","●"]]],["CreateNewObject",["A₀","Point",["A₀",true,"#AFA71F","name + value","1","0","above","●"]]],["CreateNewObject",["B₀","Point",["B₀",true,"#121733","name + value","1","0","above","●"]]],["CreateNewObject",["C₀","Point",["C₀",true,"#363740","name + value","1","0","above","●"]]],["CreateNewObject",["D₀","Point",["D₀",true,"#96896E","name + value","1","0","above","●"]]],["CreateNewObject",["E₀","Point",["E₀",true,"#051126","name + value","1","0","above","●"]]],["CreateNewObject",["F₀","Point",["F₀",true,"#2F004D","name + value","1","0","above","●"]]],["CreateNewObject",["J₀","Point",["J₀",true,"#4B064D","name + value","1","0","above","●"]]],["CreateNewObject",["K₀","Point",["K₀",true,"#7D6527","name + value","1","0","above","●"]]],["CreateNewObject",["L₀","Point",["L₀",true,"#55725F","name + value","1","0","above","●"]]],["CreateNewObject",["M₀","Point",["M₀",true,"#A09E28","name + value","1","0","above","●"]]],["CreateNewObject",["N₀","Point",["N₀",true,"#3A7E1A","name + value","1","0","above","●"]]],["CreateNewObject",["O₀","Point",["O₀",true,"#905361","name + value","1","0","above","●"]]],["CreateNewObject",["P₀","Point",["P₀",true,"#824C96","name + value","1","0","above","●"]]],["CreateNewObject",["t","Text",["t",true,"#7F6A2B","null","1","0","center","New text",false]]],["CreateNewObject",["Q₀","Point",["Q₀",true,"#0B919F","name + value","1","0","above","●"]]],["CreateNewObject",["R₀","Point",["R₀",true,"#308188","name + value","1","0","above","●"]]],["CreateNewObject",["S₀","Point",["S₀",true,"#434DA2","name + value","1","0","above","●"]]],["CreateNewObject",["T₀","Point",["T₀",true,"#5A4455","name + value","1","0","above","●"]]],["CreateNewObject",["U₀","Point",["U₀",true,"#289954","name + value","1","0","above","●"]]],["CreateNewObject",["V₀","Point",["V₀",true,"#5D2371","name + value","1","0","above","●"]]],["CreateNewObject",["W₀","Point",["W₀",true,"#9E9F13","name + value","1","0","above","●"]]],["CreateNewObject",["A₁","Point",["A₁",true,"#6B8E7A","name + value","1","0","above","●"]]],["CreateNewObject",["B₁","Point",["B₁",true,"#25186C","name + value","1","0","above","●"]]],["CreateNewObject",["C₁","Point",["C₁",true,"#8FA8AD","name + value","1","0","above","●"]]],["CreateNewObject",["D₁","Point",["D₁",true,"#5D4528","name + value","1","0","above","●"]]],["CreateNewObject",["E₁","Point",["E₁",true,"#26435D","name + value","1","0","above","●"]]],["CreateNewObject",["F₁","Point",["F₁",true,"#6B7C9C","name + value","1","0","above","●"]]],["CreateNewObject",["J₁","Point",["J₁",true,"#96275E","name + value","1","0","above","●"]]],["CreateNewObject",["K₁","Point",["K₁",true,"#3DAF12","name + value","1","0","above","●"]]],["CreateNewObject",["L₁","Point",["L₁",true,"#1EA902","name + value","1","0","above","●"]]],["CreateNewObject",["t₀","Text",["t₀",true,"#511C6A","null","1","0","center","New text",false]]],["CreateNewObject",["M₁","Point",["M₁",true,"#515D07","name + value","1","0","above","●"]]],["CreateNewObject",["N₁","Point",["N₁",true,"#711764","name + value","1","0","above","●"]]],["CreateNewObject",["O₁","Point",["O₁",true,"#732C94","name + value","1","0","above","●"]]],["CreateNewObject",["P₁","Point",["P₁",true,"#66174C","name + value","1","0","above","●"]]],["CreateNewObject",["Q₁","Point",["Q₁",true,"#75816A","name + value","1","0","above","●"]]],["CreateNewObject",["R₁","Point",["R₁",true,"#AF8C59","name + value","1","0","above","●"]]],["CreateNewObject",["S₁","Point",["S₁",true,"#1E446B","name + value","1","0","above","●"]]],["CreateNewObject",["T₁","Point",["T₁",true,"#A58DA6","name + value","1","0","above","●"]]],["CreateNewObject",["U₁","Point",["U₁",true,"#912D54","name + value","1","0","above","●"]]],["CreateNewObject",["V₁","Point",["V₁",true,"#8E8886","name + value","1","0","above","●"]]],["CreateNewObject",["W₁","Point",["W₁",true,"#26602E","name + value","1","0","above","●"]]],["CreateNewObject",["A₂","Point",["A₂",true,"#8C6064","name + value","1","0","above","●"]]],["CreateNewObject",["B₂","Point",["B₂",true,"#431889","name + value","1","0","above","●"]]],["CreateNewObject",["C₂","Point",["C₂",true,"#8D6959","name + value","1","0","above","●"]]],["CreateNewObject",["D₂","Point",["D₂",true,"#A57776","name + value","1","0","above","●"]]],["CreateNewObject",["E₂","Point",["E₂",true,"#312374","name + value","1","0","above","●"]]],["CreateNewObject",["F₂","Point",["F₂",true,"#3D3150","name + value","1","0","above","●"]]],["CreateNewObject",["J₂","Point",["J₂",true,"#6F9C66","name + value","1","0","above","●"]]],["CreateNewObject",["t₁","Text",["t₁",true,"#84223B","null","1","0","center","New text",false]]],["CreateNewObject",["K₂","Point",["K₂",true,"#3C2899","name + value","1","0","above","●"]]],["CreateNewObject",["L₂","Point",["L₂",true,"#753355","name + value","1","0","above","●"]]],["CreateNewObject",["M₂","Point",["M₂",true,"#3F046A","name + value","1","0","above","●"]]],["CreateNewObject",["N₂","Point",["N₂",true,"#9E4B51","name + value","1","0","above","●"]]],["CreateNewObject",["O₂","Point",["O₂",true,"#199B64","name + value","1","0","above","●"]]],["CreateNewObject",["P₂","Point",["P₂",true,"#59893D","name + value","1","0","above","●"]]],["CreateNewObject",["Q₂","Point",["Q₂",true,"#944D3E","name + value","1","0","above","●"]]],["CreateNewObject",["R₂","Point",["R₂",true,"#8D8836","name + value","1","0","above","●"]]],["CreateNewObject",["S₂","Point",["S₂",true,"#A52589","name + value","1","0","above","●"]]],["CreateNewObject",["t₂","Text",["t₂",true,"#A10A90","null","1","0","center","New text",false]]],["CreateNewObject",["T₂","Point",["T₂",true,"#7F723D","name + value","1","0","above","●"]]],["CreateNewObject",["U₂","Point",["U₂",true,"#78AB96","name + value","1","0","above","●"]]],["CreateNewObject",["V₂","Point",["V₂",true,"#626A23","name + value","1","0","above","●"]]],["CreateNewObject",["W₂","Point",["W₂",true,"#401362","name + value","1","0","above","●"]]],["CreateNewObject",["A₃","Point",["A₃",true,"#9F0E30","name + value","1","0","above","●"]]],["CreateNewObject",["B₃","Point",["B₃",true,"#100C0E","name + value","1","0","above","●"]]],["CreateNewObject",["C₃","Point",["C₃",true,"#858161","name + value","1","0","above","●"]]],["CreateNewObject",["D₃","Point",["D₃",true,"#7482A8","name + value","1","0","above","●"]]],["CreateNewObject",["E₃","Point",["E₃",true,"#0D03A3","name + value","1","0","above","●"]]],["CreateNewObject",["F₃","Point",["F₃",true,"#652922","name + value","1","0","above","●"]]],["CreateNewObject",["J₃","Point",["J₃",true,"#39A671","name + value","1","0","above","●"]]],["CreateNewObject",["K₃","Point",["K₃",true,"#4E0481","name + value","1","0","above","●"]]],["CreateNewObject",["L₃","Point",["L₃",true,"#1F9239","name + value","1","0","above","●"]]],["CreateNewObject",["M₃","Point",["M₃",true,"#986A07","name + value","1","0","above","●"]]],["CreateNewObject",["N₃","Point",["N₃",true,"#4AAE1F","name + value","1","0","above","●"]]],["CreateNewObject",["O₃","Point",["O₃",true,"#3C0911","name + value","1","0","above","●"]]],["CreateNewObject",["P₃","Point",["P₃",true,"#400A42","name + value","1","0","above","●"]]],["CreateNewObject",["Q₃","Point",["Q₃",true,"#9A9C5F","name + value","1","0","above","●"]]],["CreateNewObject",["R₃","Point",["R₃",true,"#AB7FA9","name + value","1","0","above","●"]]],["CreateNewObject",["S₃","Point",["S₃",true,"#4E9D33","name + value","1","0","above","●"]]],["CreateNewObject",["T₃","Point",["T₃",true,"#564E4C","name + value","1","0","above","●"]]],["CreateNewObject",["U₃","Point",["U₃",true,"#674739","name + value","1","0","above","●"]]],["CreateNewObject",["V₃","Point",["V₃",true,"#5D689B","name + value","1","0","above","●"]]],["CreateNewObject",["W₃","Point",["W₃",true,"#6B8E57","name + value","1","0","above","●"]]],["CreateNewObject",["A₄","Point",["A₄",true,"#169C3E","name + value","1","0","above","●"]]],["CreateNewObject",["B₄","Point",["B₄",true,"#95104E","name + value","1","0","above","●"]]],["CreateNewObject",["C₄","Point",["C₄",true,"#73379E","name + value","1","0","above","●"]]],["CreateNewObject",["D₄","Point",["D₄",true,"#98143F","name + value","1","0","above","●"]]],["CreateNewObject",["E₄","Point",["E₄",true,"#A8926F","name + value","1","0","above","●"]]],["CreateNewObject",["F₄","Point",["F₄",true,"#3172AB","name + value","1","0","above","●"]]],["CreateNewObject",["J₄","Point",["J₄",true,"#8A9C96","name + value","1","0","above","●"]]],["CreateNewObject",["K₄","Point",["K₄",true,"#AF001D","name + value","1","0","above","●"]]],["CreateNewObject",["L₄","Point",["L₄",true,"#711495","name + value","1","0","above","●"]]],["CreateNewObject",["M₄","Point",["M₄",true,"#082241","name + value","1","0","above","●"]]],["CreateNewObject",["N₄","Point",["N₄",true,"#631582","name + value","1","0","above","●"]]],["CreateNewObject",["O₄","Point",["O₄",true,"#895378","name + value","1","0","above","●"]]],["CreateNewObject",["P₄","Point",["P₄",true,"#812404","name + value","1","0","above","●"]]],["CreateNewObject",["Q₄","Point",["Q₄",true,"#3B2755","name + value","1","0","above","●"]]],["CreateNewObject",["R₄","Point",["R₄",true,"#5B6E03","name + value","1","0","above","●"]]],["CreateNewObject",["S₄","Point",["S₄",true,"#AF4237","name + value","1","0","above","●"]]],["CreateNewObject",["T₄","Point",["T₄",true,"#9C75AC","name + value","1","0","above","●"]]],["CreateNewObject",["U₄","Point",["U₄",true,"#926178","name + value","1","0","above","●"]]],["CreateNewObject",["V₄","Point",["V₄",true,"#8C3DA7","name + value","1","0","above","●"]]],["CreateNewObject",["W₄","Point",["W₄",true,"#95843C","name + value","1","0","above","●"]]],["CreateNewObject",["A₅","Point",["A₅",true,"#022359","name + value","1","0","above","●"]]],["CreateNewObject",["B₅","Point",["B₅",true,"#8B906E","name + value","1","0","above","●"]]],["CreateNewObject",["C₅","Point",["C₅",true,"#317B9A","name + value","1","0","above","●"]]],["CreateNewObject",["D₅","Point",["D₅",true,"#559232","name + value","1","0","above","●"]]],["CreateNewObject",["E₅","Point",["E₅",true,"#9324A5","name + value","1","0","above","●"]]],["CreateNewObject",["F₅","Point",["F₅",true,"#574940","name + value","1","0","above","●"]]],["CreateNewObject",["J₅","Point",["J₅",true,"#A93968","name + value","1","0","above","●"]]],["CreateNewObject",["K₅","Point",["K₅",true,"#1F6266","name + value","1","0","above","●"]]],["CreateNewObject",["L₅","Point",["L₅",true,"#181928","name + value","1","0","above","●"]]],["CreateNewObject",["M₅","Point",["M₅",true,"#760C13","name + value","1","0","above","●"]]],["CreateNewObject",["N₅","Point",["N₅",true,"#366B7E","name + value","1","0","above","●"]]],["CreateNewObject",["O₅","Point",["O₅",true,"#8E6060","name + value","1","0","above","●"]]],["CreateNewObject",["P₅","Point",["P₅",true,"#A8158D","name + value","1","0","above","●"]]],["CreateNewObject",["Q₅","Point",["Q₅",true,"#1D206C","name + value","1","0","above","●"]]],["CreateNewObject",["R₅","Point",["R₅",true,"#9C6169","name + value","1","0","above","●"]]],["CreateNewObject",["S₅","Point",["S₅",true,"#6F412F","name + value","1","0","above","●"]]],["CreateNewObject",["T₅","Point",["T₅",true,"#0B2453","name + value","1","0","above","●"]]],["CreateNewObject",["U₅","Point",["U₅",true,"#04839E","name + value","1","0","above","●"]]],["CreateNewObject",["V₅","Point",["V₅",true,"#8B29A5","name + value","1","0","above","●"]]],["CreateNewObject",["W₅","Point",["W₅",true,"#491438","name + value","1","0","above","●"]]],["CreateNewObject",["A₆","Point",["A₆",true,"#00087B","name + value","1","0","above","●"]]],["CreateNewObject",["B₆","Point",["B₆",true,"#0F431C","name + value","1","0","above","●"]]],["CreateNewObject",["C₆","Point",["C₆",true,"#1D3B07","name + value","1","0","above","●"]]],["CreateNewObject",["D₆","Point",["D₆",true,"#7F695A","name + value","1","0","above","●"]]],["CreateNewObject",["E₆","Point",["E₆",true,"#AB4383","name + value","1","0","above","●"]]],["CreateNewObject",["F₆","Point",["F₆",true,"#9C9E78","name + value","1","0","above","●"]]],["CreateNewObject",["J₆","Point",["J₆",true,"#243945","name + value","1","0","above","●"]]],["CreateNewObject",["K₆","Point",["K₆",true,"#A49EAA","name + value","1","0","above","●"]]],["CreateNewObject",["L₆","Point",["L₆",true,"#822C32","name + value","1","0","above","●"]]],["CreateNewObject",["M₆","Point",["M₆",true,"#095D68","name + value","1","0","above","●"]]],["CreateNewObject",["N₆","Point",["N₆",true,"#AA361D","name + value","1","0","above","●"]]],["CreateNewObject",["O₆","Point",["O₆",true,"#045B40","name + value","1","0","above","●"]]],["CreateNewObject",["P₆","Point",["P₆",true,"#2B10AD","name + value","1","0","above","●"]]],["CreateNewObject",["Q₆","Point",["Q₆",true,"#8F0607","name + value","1","0","above","●"]]],["CreateNewObject",["R₆","Point",["R₆",true,"#953472","name + value","1","0","above","●"]]],["CreateNewObject",["S₆","Point",["S₆",true,"#7F7663","name + value","1","0","above","●"]]],["CreateNewObject",["T₆","Point",["T₆",true,"#9A4294","name + value","1","0","above","●"]]],["CreateNewObject",["U₆","Point",["U₆",true,"#924762","name + value","1","0","above","●"]]],["CreateNewObject",["V₆","Point",["V₆",true,"#006385","name + value","1","0","above","●"]]],["CreateNewObject",["W₆","Point",["W₆",true,"#8C0504","name + value","1","0","above","●"]]],["CreateNewObject",["A₇","Point",["A₇",true,"#337BA0","name + value","1","0","above","●"]]],["CreateNewObject",["B₇","Point",["B₇",true,"#970A47","name + value","1","0","above","●"]]],["CreateNewObject",["C₇","Point",["C₇",true,"#8D071F","name + value","1","0","above","●"]]],["CreateNewObject",["D₇","Point",["D₇",true,"#241417","name + value","1","0","above","●"]]],["CreateNewObject",["E₇","Point",["E₇",true,"#8DAB44","name + value","1","0","above","●"]]],["CreateNewObject",["F₇","Point",["F₇",true,"#555D7B","name + value","1","0","above","●"]]],["CreateNewObject",["J₇","Point",["J₇",true,"#537A31","name + value","1","0","above","●"]]],["CreateNewObject",["K₇","Point",["K₇",true,"#298D88","name + value","1","0","above","●"]]],["CreateNewObject",["L₇","Point",["L₇",true,"#55249C","name + value","1","0","above","●"]]],["CreateNewObject",["M₇","Point",["M₇",true,"#AB5E71","name + value","1","0","above","●"]]],["CreateNewObject",["N₇","Point",["N₇",true,"#834D89","name + value","1","0","above","●"]]],["CreateNewObject",["O₇","Point",["O₇",true,"#7B4EAC","name + value","1","0","above","●"]]],["CreateNewObject",["P₇","Point",["P₇",true,"#451133","name + value","1","0","above","●"]]],["CreateNewObject",["Q₇","Point",["Q₇",true,"#07410B","name + value","1","0","above","●"]]],["CreateNewObject",["R₇","Point",["R₇",true,"#5D0B61","name + value","1","0","above","●"]]],["CreateNewObject",["S₇","Point",["S₇",true,"#1F184F","name + value","1","0","above","●"]]],["CreateNewObject",["T₇","Point",["T₇",true,"#897283","name + value","1","0","above","●"]]],["CreateNewObject",["U₇","Point",["U₇",true,"#079906","name + value","1","0","above","●"]]],["CreateNewObject",["V₇","Point",["V₇",true,"#1DA545","name + value","1","0","above","●"]]],["CreateNewObject",["W₇","Point",["W₇",true,"#4E4A71","name + value","1","0","above","●"]]],["CreateNewObject",["A₈","Point",["A₈",true,"#563577","name + value","1","0","above","●"]]],["CreateNewObject",["B₈","Point",["B₈",true,"#6FA324","name + value","1","0","above","●"]]],["CreateNewObject",["C₈","Point",["C₈",true,"#099187","name + value","1","0","above","●"]]],["CreateNewObject",["D₈","Point",["D₈",true,"#0F976A","name + value","1","0","above","●"]]],["CreateNewObject",["E₈","Point",["E₈",true,"#525B2F","name + value","1","0","above","●"]]],["CreateNewObject",["F₈","Point",["F₈",true,"#249C5A","name + value","1","0","above","●"]]],["CreateNewObject",["J₈","Point",["J₈",true,"#359EAD","name + value","1","0","above","●"]]],["CreateNewObject",["K₈","Point",["K₈",true,"#014113","name + value","1","0","above","●"]]],["CreateNewObject",["L₈","Point",["L₈",true,"#992D2C","name + value","1","0","above","●"]]],["CreateNewObject",["M₈","Point",["M₈",true,"#2A9D50","name + value","1","0","above","●"]]],["CreateNewObject",["N₈","Point",["N₈",true,"#3BA6A8","name + value","1","0","above","●"]]],["CreateNewObject",["O₈","Point",["O₈",true,"#2F2BAB","name + value","1","0","above","●"]]],["CreateNewObject",["P₈","Point",["P₈",true,"#A35282","name + value","1","0","above","●"]]],["CreateNewObject",["Q₈","Point",["Q₈",true,"#346C86","name + value","1","0","above","●"]]],["CreateNewObject",["R₈","Point",["R₈",true,"#738755","name + value","1","0","above","●"]]],["CreateNewObject",["S₈","Point",["S₈",true,"#2E302D","name + value","1","0","above","●"]]],["CreateNewObject",["T₈","Point",["T₈",true,"#187E5B","name + value","1","0","above","●"]]],["CreateNewObject",["U₈","Point",["U₈",true,"#4B868E","name + value","1","0","above","●"]]],["CreateNewObject",["V₈","Point",["V₈",true,"#13937C","name + value","1","0","above","●"]]],["CreateNewObject",["W₈","Point",["W₈",true,"#938E72","name + value","1","0","above","●"]]]],[]],"width":1000,"height":500,"objects":{"Point":[["A",true,"#A35D32","name + value","1","0","above","●"],["B",true,"#AF370C","name + value","1","0","above","●"],["C",true,"#AB9221","name + value","1","0","above","●"],["D",true,"#43A98E","name + value","1","0","above","●"],["E",true,"#1F1C6A","name + value","1","0","above","●"],["F",true,"#407099","name + value","1","0","above","●"],["J",true,"#9B4B7A","name + value","1","0","above","●"],["K",true,"#3B5698","name + value","1","0","above","●"],["L",true,"#0C6A4D","name + value","1","0","above","●"],["M",true,"#350945","name + value","1","0","above","●"],["N",true,"#46511E","name + value","1","0","above","●"],["O",true,"#0B318B","name + value","1","0","above","●"],["P",true,"#354125","name + value","1","0","above","●"],["Q",true,"#598608","name + value","1","0","above","●"],["R",true,"#733D64","name + value","1","0","above","●"],["S",true,"#7477A8","name + value","1","0","above","●"],["T",true,"#5B6034","name + value","1","0","above","●"],["U",true,"#951A57","name + value","1","0","above","●"],["V",true,"#72804E","name + value","1","0","above","●"],["W",true,"#7B1070","name + value","1","0","above","●"],["A₀",true,"#AFA71F","name + value","1","0","above","●"],["B₀",true,"#121733","name + value","1","0","above","●"],["C₀",true,"#363740","name + value","1","0","above","●"],["D₀",true,"#96896E","name + value","1","0","above","●"],["E₀",true,"#051126","name + value","1","0","above","●"],["F₀",true,"#2F004D","name + value","1","0","above","●"],["J₀",true,"#4B064D","name + value","1","0","above","●"],["K₀",true,"#7D6527","name + value","1","0","above","●"],["L₀",true,"#55725F","name + value","1","0","above","●"],["M₀",true,"#A09E28","name + value","1","0","above","●"],["N₀",true,"#3A7E1A","name + value","1","0","above","●"],["O₀",true,"#905361","name + value","1","0","above","●"],["P₀",true,"#824C96","name + value","1","0","above","●"],["Q₀",true,"#0B919F","name + value","1","0","above","●"],["R₀",true,"#308188","name + value","1","0","above","●"],["S₀",true,"#434DA2","name + value","1","0","above","●"],["T₀",true,"#5A4455","name + value","1","0","above","●"],["U₀",true,"#289954","name + value","1","0","above","●"],["V₀",true,"#5D2371","name + value","1","0","above","●"],["W₀",true,"#9E9F13","name + value","1","0","above","●"],["A₁",true,"#6B8E7A","name + value","1","0","above","●"],["B₁",true,"#25186C","name + value","1","0","above","●"],["C₁",true,"#8FA8AD","name + value","1","0","above","●"],["D₁",true,"#5D4528","name + value","1","0","above","●"],["E₁",true,"#26435D","name + value","1","0","above","●"],["F₁",true,"#6B7C9C","name + value","1","0","above","●"],["J₁",true,"#96275E","name + value","1","0","above","●"],["K₁",true,"#3DAF12","name + value","1","0","above","●"],["L₁",true,"#1EA902","name + value","1","0","above","●"],["M₁",true,"#515D07","name + value","1","0","above","●"],["N₁",true,"#711764","name + value","1","0","above","●"],["O₁",true,"#732C94","name + value","1","0","above","●"],["P₁",true,"#66174C","name + value","1","0","above","●"],["Q₁",true,"#75816A","name + value","1","0","above","●"],["R₁",true,"#AF8C59","name + value","1","0","above","●"],["S₁",true,"#1E446B","name + value","1","0","above","●"],["T₁",true,"#A58DA6","name + value","1","0","above","●"],["U₁",true,"#912D54","name + value","1","0","above","●"],["V₁",true,"#8E8886","name + value","1","0","above","●"],["W₁",true,"#26602E","name + value","1","0","above","●"],["A₂",true,"#8C6064","name + value","1","0","above","●"],["B₂",true,"#431889","name + value","1","0","above","●"],["C₂",true,"#8D6959","name + value","1","0","above","●"],["D₂",true,"#A57776","name + value","1","0","above","●"],["E₂",true,"#312374","name + value","1","0","above","●"],["F₂",true,"#3D3150","name + value","1","0","above","●"],["J₂",true,"#6F9C66","name + value","1","0","above","●"],["K₂",true,"#3C2899","name + value","1","0","above","●"],["L₂",true,"#753355","name + value","1","0","above","●"],["M₂",true,"#3F046A","name + value","1","0","above","●"],["N₂",true,"#9E4B51","name + value","1","0","above","●"],["O₂",true,"#199B64","name + value","1","0","above","●"],["P₂",true,"#59893D","name + value","1","0","above","●"],["Q₂",true,"#944D3E","name + value","1","0","above","●"],["R₂",true,"#8D8836","name + value","1","0","above","●"],["S₂",true,"#A52589","name + value","1","0","above","●"],["T₂",true,"#7F723D","name + value","1","0","above","●"],["U₂",true,"#78AB96","name + value","1","0","above","●"],["V₂",true,"#626A23","name + value","1","0","above","●"],["W₂",true,"#401362","name + value","1","0","above","●"],["A₃",true,"#9F0E30","name + value","1","0","above","●"],["B₃",true,"#100C0E","name + value","1","0","above","●"],["C₃",true,"#858161","name + value","1","0","above","●"],["D₃",true,"#7482A8","name + value","1","0","above","●"],["E₃",true,"#0D03A3","name + value","1","0","above","●"],["F₃",true,"#652922","name + value","1","0","above","●"],["J₃",true,"#39A671","name + value","1","0","above","●"],["K₃",true,"#4E0481","name + value","1","0","above","●"],["L₃",true,"#1F9239","name + value","1","0","above","●"],["M₃",true,"#986A07","name + value","1","0","above","●"],["N₃",true,"#4AAE1F","name + value","1","0","above","●"],["O₃",true,"#3C0911","name + value","1","0","above","●"],["P₃",true,"#400A42","name + value","1","0","above","●"],["Q₃",true,"#9A9C5F","name + value","1","0","above","●"],["R₃",true,"#AB7FA9","name + value","1","0","above","●"],["S₃",true,"#4E9D33","name + value","1","0","above","●"],["T₃",true,"#564E4C","name + value","1","0","above","●"],["U₃",true,"#674739","name + value","1","0","above","●"],["V₃",true,"#5D689B","name + value","1","0","above","●"],["W₃",true,"#6B8E57","name + value","1","0","above","●"],["A₄",true,"#169C3E","name + value","1","0","above","●"],["B₄",true,"#95104E","name + value","1","0","above","●"],["C₄",true,"#73379E","name + value","1","0","above","●"],["D₄",true,"#98143F","name + value","1","0","above","●"],["E₄",true,"#A8926F","name + value","1","0","above","●"],["F₄",true,"#3172AB","name + value","1","0","above","●"],["J₄",true,"#8A9C96","name + value","1","0","above","●"],["K₄",true,"#AF001D","name + value","1","0","above","●"],["L₄",true,"#711495","name + value","1","0","above","●"],["M₄",true,"#082241","name + value","1","0","above","●"],["N₄",true,"#631582","name + value","1","0","above","●"],["O₄",true,"#895378","name + value","1","0","above","●"],["P₄",true,"#812404","name + value","1","0","above","●"],["Q₄",true,"#3B2755","name + value","1","0","above","●"],["R₄",true,"#5B6E03","name + value","1","0","above","●"],["S₄",true,"#AF4237","name + value","1","0","above","●"],["T₄",true,"#9C75AC","name + value","1","0","above","●"],["U₄",true,"#926178","name + value","1","0","above","●"],["V₄",true,"#8C3DA7","name + value","1","0","above","●"],["W₄",true,"#95843C","name + value","1","0","above","●"],["A₅",true,"#022359","name + value","1","0","above","●"],["B₅",true,"#8B906E","name + value","1","0","above","●"],["C₅",true,"#317B9A","name + value","1","0","above","●"],["D₅",true,"#559232","name + value","1","0","above","●"],["E₅",true,"#9324A5","name + value","1","0","above","●"],["F₅",true,"#574940","name + value","1","0","above","●"],["J₅",true,"#A93968","name + value","1","0","above","●"],["K₅",true,"#1F6266","name + value","1","0","above","●"],["L₅",true,"#181928","name + value","1","0","above","●"],["M₅",true,"#760C13","name + value","1","0","above","●"],["N₅",true,"#366B7E","name + value","1","0","above","●"],["O₅",true,"#8E6060","name + value","1","0","above","●"],["P₅",true,"#A8158D","name + value","1","0","above","●"],["Q₅",true,"#1D206C","name + value","1","0","above","●"],["R₅",true,"#9C6169","name + value","1","0","above","●"],["S₅",true,"#6F412F","name + value","1","0","above","●"],["T₅",true,"#0B2453","name + value","1","0","above","●"],["U₅",true,"#04839E","name + value","1","0","above","●"],["V₅",true,"#8B29A5","name + value","1","0","above","●"],["W₅",true,"#491438","name + value","1","0","above","●"],["A₆",true,"#00087B","name + value","1","0","above","●"],["B₆",true,"#0F431C","name + value","1","0","above","●"],["C₆",true,"#1D3B07","name + value","1","0","above","●"],["D₆",true,"#7F695A","name + value","1","0","above","●"],["E₆",true,"#AB4383","name + value","1","0","above","●"],["F₆",true,"#9C9E78","name + value","1","0","above","●"],["J₆",true,"#243945","name + value","1","0","above","●"],["K₆",true,"#A49EAA","name + value","1","0","above","●"],["L₆",true,"#822C32","name + value","1","0","above","●"],["M₆",true,"#095D68","name + value","1","0","above","●"],["N₆",true,"#AA361D","name + value","1","0","above","●"],["O₆",true,"#045B40","name + value","1","0","above","●"],["P₆",true,"#2B10AD","name + value","1","0","above","●"],["Q₆",true,"#8F0607","name + value","1","0","above","●"],["R₆",true,"#953472","name + value","1","0","above","●"],["S₆",true,"#7F7663","name + value","1","0","above","●"],["T₆",true,"#9A4294","name + value","1","0","above","●"],["U₆",true,"#924762","name + value","1","0","above","●"],["V₆",true,"#006385","name + value","1","0","above","●"],["W₆",true,"#8C0504","name + value","1","0","above","●"],["A₇",true,"#337BA0","name + value","1","0","above","●"],["B₇",true,"#970A47","name + value","1","0","above","●"],["C₇",true,"#8D071F","name + value","1","0","above","●"],["D₇",true,"#241417","name + value","1","0","above","●"],["E₇",true,"#8DAB44","name + value","1","0","above","●"],["F₇",true,"#555D7B","name + value","1","0","above","●"],["J₇",true,"#537A31","name + value","1","0","above","●"],["K₇",true,"#298D88","name + value","1","0","above","●"],["L₇",true,"#55249C","name + value","1","0","above","●"],["M₇",true,"#AB5E71","name + value","1","0","above","●"],["N₇",true,"#834D89","name + value","1","0","above","●"],["O₇",true,"#7B4EAC","name + value","1","0","above","●"],["P₇",true,"#451133","name + value","1","0","above","●"],["Q₇",true,"#07410B","name + value","1","0","above","●"],["R₇",true,"#5D0B61","name + value","1","0","above","●"],["S₇",true,"#1F184F","name + value","1","0","above","●"],["T₇",true,"#897283","name + value","1","0","above","●"],["U₇",true,"#079906","name + value","1","0","above","●"],["V₇",true,"#1DA545","name + value","1","0","above","●"],["W₇",true,"#4E4A71","name + value","1","0","above","●"],["A₈",true,"#563577","name + value","1","0","above","●"],["B₈",true,"#6FA324","name + value","1","0","above","●"],["C₈",true,"#099187","name + value","1","0","above","●"],["D₈",true,"#0F976A","name + value","1","0","above","●"],["E₈",true,"#525B2F","name + value","1","0","above","●"],["F₈",true,"#249C5A","name + value","1","0","above","●"],["J₈",true,"#359EAD","name + value","1","0","above","●"],["K₈",true,"#014113","name + value","1","0","above","●"],["L₈",true,"#992D2C","name + value","1","0","above","●"],["M₈",true,"#2A9D50","name + value","1","0","above","●"],["N₈",true,"#3BA6A8","name + value","1","0","above","●"],["O₈",true,"#2F2BAB","name + value","1","0","above","●"],["P₈",true,"#A35282","name + value","1","0","above","●"],["Q₈",true,"#346C86","name + value","1","0","above","●"],["R₈",true,"#738755","name + value","1","0","above","●"],["S₈",true,"#2E302D","name + value","1","0","above","●"],["T₈",true,"#187E5B","name + value","1","0","above","●"],["U₈",true,"#4B868E","name + value","1","0","above","●"],["V₈",true,"#13937C","name + value","1","0","above","●"],["W₈",true,"#938E72","name + value","1","0","above","●"]],"Text":[["t",true,"#7F6A2B","null","1","0","center","New text",false],["t₀",true,"#511C6A","null","1","0","center","New text",false],["t₁",true,"#84223B","null","1","0","center","New text",false],["t₂",true,"#A10A90","null","1","0","center","New text",false]]},"type":"logplotv1"} \ No newline at end of file diff --git a/LogarithmPlotter/__main__.py b/common/.mocharc.jsonc similarity index 87% rename from LogarithmPlotter/__main__.py rename to common/.mocharc.jsonc index 6c86e82..56a6b31 100644 --- a/LogarithmPlotter/__main__.py +++ b/common/.mocharc.jsonc @@ -1,20 +1,25 @@ -""" +/** * 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 . -""" -if __name__ == "__main__": - from .logarithmplotter import run - run() + */ + +{ + "recursive": true, + "require": [ + "esm", + "./test/hooks.mjs" + ] +} diff --git a/common/babel.config.json b/common/babel.config.json new file mode 100644 index 0000000..fd63c92 --- /dev/null +++ b/common/babel.config.json @@ -0,0 +1,6 @@ +{ + "presets": ["@babel/preset-env"], + "targets": { + "esmodules": true + } +} \ No newline at end of file diff --git a/common/package-lock.json b/common/package-lock.json new file mode 100644 index 0000000..0e01b59 --- /dev/null +++ b/common/package-lock.json @@ -0,0 +1,4449 @@ +{ + "name": "logarithmplotter", + "version": "0.6.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "logarithmplotter", + "version": "0.6.0", + "license": "GPL-3.0-or-later", + "dependencies": { + "@babel/preset-env": "^7.25.4", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^28.0.0", + "@rollup/plugin-node-resolve": "^15.3.0", + "c8": "^10.1.2", + "rollup": "^4.22.4", + "rollup-plugin-cleanup": "^3.2.1" + }, + "devDependencies": { + "@types/chai": "^5.0.0", + "@types/chai-as-promised": "^8.0.1", + "@types/chai-spies": "^1.0.6", + "@types/mocha": "^10.0.8", + "chai": "^5.1.1", + "chai-as-promised": "^8.0.0", + "chai-spies": "^1.1.0", + "esm": "^3.2.25", + "mocha": "^10.7.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.25.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.7.tgz", + "integrity": "sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.7.tgz", + "integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz", + "integrity": "sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.7.tgz", + "integrity": "sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz", + "integrity": "sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-member-expression-to-functions": "^7.25.7", + "@babel/helper-optimise-call-expression": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/traverse": "^7.25.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.7.tgz", + "integrity": "sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "regexpu-core": "^6.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz", + "integrity": "sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz", + "integrity": "sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", + "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.7.tgz", + "integrity": "sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-wrap-function": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz", + "integrity": "sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.7", + "@babel/helper-optimise-call-expression": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz", + "integrity": "sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.7.tgz", + "integrity": "sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", + "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.7.tgz", + "integrity": "sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.7.tgz", + "integrity": "sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.7.tgz", + "integrity": "sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.7.tgz", + "integrity": "sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/plugin-transform-optional-chaining": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.7.tgz", + "integrity": "sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.7.tgz", + "integrity": "sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.7.tgz", + "integrity": "sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.7.tgz", + "integrity": "sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.7.tgz", + "integrity": "sha512-4B6OhTrwYKHYYgcwErvZjbmH9X5TxQBsaBHdzEIB4l71gR5jh/tuHGlb9in47udL2+wVUcOz5XXhhfhVJwEpEg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-remap-async-to-generator": "^7.25.7", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.7.tgz", + "integrity": "sha512-ZUCjAavsh5CESCmi/xCpX1qcCaAglzs/7tmuvoFnJgA1dM7gQplsguljoTg+Ru8WENpX89cQyAtWoaE0I3X3Pg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-remap-async-to-generator": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.7.tgz", + "integrity": "sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.7.tgz", + "integrity": "sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.7.tgz", + "integrity": "sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.25.7.tgz", + "integrity": "sha512-rvUUtoVlkDWtDWxGAiiQj0aNktTPn3eFynBcMC2IhsXweehwgdI9ODe+XjWw515kEmv22sSOTp/rxIRuTiB7zg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.7.tgz", + "integrity": "sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7", + "@babel/traverse": "^7.25.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.7.tgz", + "integrity": "sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/template": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.7.tgz", + "integrity": "sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.7.tgz", + "integrity": "sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.7.tgz", + "integrity": "sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.7.tgz", + "integrity": "sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.7.tgz", + "integrity": "sha512-UvcLuual4h7/GfylKm2IAA3aph9rwvAM2XBA0uPKU3lca+Maai4jBjjEVUS568ld6kJcgbouuumCBhMd/Yz17w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.7.tgz", + "integrity": "sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg==", + "license": "MIT", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.7.tgz", + "integrity": "sha512-h3MDAP5l34NQkkNulsTNyjdaR+OiB0Im67VU//sFupouP8Q6m9Spy7l66DcaAQxtmCqGdanPByLsnwFttxKISQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.7.tgz", + "integrity": "sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.7.tgz", + "integrity": "sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.7.tgz", + "integrity": "sha512-Ot43PrL9TEAiCe8C/2erAjXMeVSnE/BLEx6eyrKLNFCCw5jvhTHKyHxdI1pA0kz5njZRYAnMO2KObGqOCRDYSA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.7.tgz", + "integrity": "sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.7.tgz", + "integrity": "sha512-iImzbA55BjiovLyG2bggWS+V+OLkaBorNvc/yJoeeDQGztknRnDdYfp2d/UPmunZYEnZi6Lg8QcTmNMHOB0lGA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.7.tgz", + "integrity": "sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.7.tgz", + "integrity": "sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.7.tgz", + "integrity": "sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.7.tgz", + "integrity": "sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.7.tgz", + "integrity": "sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.7.tgz", + "integrity": "sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.7.tgz", + "integrity": "sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.7.tgz", + "integrity": "sha512-FbuJ63/4LEL32mIxrxwYaqjJxpbzxPVQj5a+Ebrc8JICV6YX8nE53jY+K0RZT3um56GoNWgkS2BQ/uLGTjtwfw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.7.tgz", + "integrity": "sha512-8CbutzSSh4hmD+jJHIA8vdTNk15kAzOnFLVVgBSMGr28rt85ouT01/rezMecks9pkU939wDInImwCKv4ahU4IA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.7.tgz", + "integrity": "sha512-1JdVKPhD7Y5PvgfFy0Mv2brdrolzpzSoUq2pr6xsR+m+3viGGeHEokFKsCgOkbeFOQxfB1Vt2F0cPJLRpFI4Zg==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.7.tgz", + "integrity": "sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-replace-supers": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.7.tgz", + "integrity": "sha512-m9obYBA39mDPN7lJzD5WkGGb0GO54PPLXsbcnj1Hyeu8mSRz7Gb4b1A6zxNX32ZuUySDK4G6it8SDFWD1nCnqg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.7.tgz", + "integrity": "sha512-h39agClImgPWg4H8mYVAbD1qP9vClFbEjqoJmt87Zen8pjqK8FTPUwrOXAvqu5soytwxrLMd2fx2KSCp2CHcNg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.7.tgz", + "integrity": "sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz", + "integrity": "sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.7.tgz", + "integrity": "sha512-LzA5ESzBy7tqj00Yjey9yWfs3FKy4EmJyKOSWld144OxkTji81WWnUT8nkLUn+imN/zHL8ZQlOu/MTUAhHaX3g==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.7", + "@babel/helper-create-class-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.7.tgz", + "integrity": "sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.7.tgz", + "integrity": "sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.7.tgz", + "integrity": "sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.7.tgz", + "integrity": "sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.7.tgz", + "integrity": "sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.7.tgz", + "integrity": "sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.7.tgz", + "integrity": "sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.7.tgz", + "integrity": "sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.7.tgz", + "integrity": "sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.7.tgz", + "integrity": "sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.7.tgz", + "integrity": "sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.7.tgz", + "integrity": "sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.7.tgz", + "integrity": "sha512-Gibz4OUdyNqqLj+7OAvBZxOD7CklCtMA5/j0JgUEwOnaRULsPDXmic2iKxL2DX2vQduPR5wH2hjZas/Vr/Oc0g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-plugin-utils": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.7", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.25.7", + "@babel/plugin-syntax-import-attributes": "^7.25.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.7", + "@babel/plugin-transform-async-to-generator": "^7.25.7", + "@babel/plugin-transform-block-scoped-functions": "^7.25.7", + "@babel/plugin-transform-block-scoping": "^7.25.7", + "@babel/plugin-transform-class-properties": "^7.25.7", + "@babel/plugin-transform-class-static-block": "^7.25.7", + "@babel/plugin-transform-classes": "^7.25.7", + "@babel/plugin-transform-computed-properties": "^7.25.7", + "@babel/plugin-transform-destructuring": "^7.25.7", + "@babel/plugin-transform-dotall-regex": "^7.25.7", + "@babel/plugin-transform-duplicate-keys": "^7.25.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.7", + "@babel/plugin-transform-dynamic-import": "^7.25.7", + "@babel/plugin-transform-exponentiation-operator": "^7.25.7", + "@babel/plugin-transform-export-namespace-from": "^7.25.7", + "@babel/plugin-transform-for-of": "^7.25.7", + "@babel/plugin-transform-function-name": "^7.25.7", + "@babel/plugin-transform-json-strings": "^7.25.7", + "@babel/plugin-transform-literals": "^7.25.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.7", + "@babel/plugin-transform-member-expression-literals": "^7.25.7", + "@babel/plugin-transform-modules-amd": "^7.25.7", + "@babel/plugin-transform-modules-commonjs": "^7.25.7", + "@babel/plugin-transform-modules-systemjs": "^7.25.7", + "@babel/plugin-transform-modules-umd": "^7.25.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.7", + "@babel/plugin-transform-new-target": "^7.25.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.7", + "@babel/plugin-transform-numeric-separator": "^7.25.7", + "@babel/plugin-transform-object-rest-spread": "^7.25.7", + "@babel/plugin-transform-object-super": "^7.25.7", + "@babel/plugin-transform-optional-catch-binding": "^7.25.7", + "@babel/plugin-transform-optional-chaining": "^7.25.7", + "@babel/plugin-transform-parameters": "^7.25.7", + "@babel/plugin-transform-private-methods": "^7.25.7", + "@babel/plugin-transform-private-property-in-object": "^7.25.7", + "@babel/plugin-transform-property-literals": "^7.25.7", + "@babel/plugin-transform-regenerator": "^7.25.7", + "@babel/plugin-transform-reserved-words": "^7.25.7", + "@babel/plugin-transform-shorthand-properties": "^7.25.7", + "@babel/plugin-transform-spread": "^7.25.7", + "@babel/plugin-transform-sticky-regex": "^7.25.7", + "@babel/plugin-transform-template-literals": "^7.25.7", + "@babel/plugin-transform-typeof-symbol": "^7.25.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.7", + "@babel/plugin-transform-unicode-property-regex": "^7.25.7", + "@babel/plugin-transform-unicode-regex": "^7.25.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.38.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", + "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", + "integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.0.tgz", + "integrity": "sha512-BJcu+a+Mpq476DMXG+hevgPSl56bkUoi88dKT8t3RyUp8kGuOh+2bU8Gs7zXDlu+fyZggnJ+iOBGrb/O1SorYg==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "fdir": "^6.1.1", + "is-reference": "1.2.1", + "magic-string": "^0.30.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", + "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.2.tgz", + "integrity": "sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.0.0.tgz", + "integrity": "sha512-+DwhEHAaFPPdJ2ral3kNHFQXnTfscEEFsUxzD+d7nlcLrFK23JtNjH71RGasTcHb88b4vVi4mTyfpf8u2L8bdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai-as-promised": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-8.0.1.tgz", + "integrity": "sha512-dAlDhLjJlABwAVYObo9TPWYTRg9NaQM5CXeaeJYcYAkvzUf0JRLIiog88ao2Wqy/20WUnhbbUZcgvngEbJ3YXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/chai-spies": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/chai-spies/-/chai-spies-1.0.6.tgz", + "integrity": "sha512-xkk4HmhBB9OQeTAifa9MJ+6R5/Rq9+ungDe4JidZD+vqZVeiWZwc2i7/pd1ZKjyGlSBIQePoWdyUyFUGT0rv5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.9.tgz", + "integrity": "sha512-sicdRoWtYevwxjOHNMPTl3vSfJM6oyW8o1wXeI7uww6b6xHg8eBznQDNSGBCDJmsE8UMxP05JgZRtsKbTqt//Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/c8": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.2.tgz", + "integrity": "sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==", + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^7.0.1", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001667", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz", + "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chai-as-promised": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-8.0.0.tgz", + "integrity": "sha512-sMsGXTrS3FunP/wbqh/KxM8Kj/aLPXQGkNtvE5wPfSToq8wkkvBpTZo1LIiEVmC4BwkKpag+l5h/20lBMk6nUg==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "check-error": "^2.0.0" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 6" + } + }, + "node_modules/chai-spies": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chai-spies/-/chai-spies-1.1.0.tgz", + "integrity": "sha512-ikaUhQvQWchRYj2K54itFp3nrcxaFRpSDQxDlRzSn9aWgu9Pi7lD8yFxTso4WnQ39+WZ69oB/qOvqp+isJIIWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + }, + "peerDependencies": { + "chai": "*" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.34", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.34.tgz", + "integrity": "sha512-/TZAiChbAflBNjCg+VvstbcwAtIL/VdMFO3NgRFIzBjpvPzWOTIbbO8kNb6RwU4bt9TP7K+3KqBKw/lOU+Y+GA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fdir": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.0.tgz", + "integrity": "sha512-3oB133prH1o4j/L5lLW7uOCF1PlD+/It2L0eL/iAqWMB91RBbqTewABqxhj0ibBd90EEmWZq7ntIWzVaWcXTGQ==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-cleanup": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/js-cleanup/-/js-cleanup-1.2.0.tgz", + "integrity": "sha512-JeDD0yiiSt80fXzAVa/crrS0JDPQljyBG/RpOtaSbyDq03VHa9szJWMaWOYU/bcTn412uMN2MxApXq8v79cUiQ==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.25.7", + "perf-regexes": "^1.0.1", + "skip-regex": "^1.0.2" + }, + "engines": { + "node": "^10.14.2 || >=12.0.0" + } + }, + "node_modules/js-cleanup/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/perf-regexes": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/perf-regexes/-/perf-regexes-1.0.1.tgz", + "integrity": "sha512-L7MXxUDtqr4PUaLFCDCXBfGV/6KLIuSEccizDI7JxT+c9x1G1v04BQ4+4oag84SHaCdrBgQAIs/Cqn+flwFPng==", + "license": "MIT", + "engines": { + "node": ">=6.14" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", + "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.11.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.1.tgz", + "integrity": "sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-cleanup": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-cleanup/-/rollup-plugin-cleanup-3.2.1.tgz", + "integrity": "sha512-zuv8EhoO3TpnrU8MX8W7YxSbO4gmOR0ny06Lm3nkFfq0IVKdBUtHwhVzY1OAJyNCIAdLiyPnOrU0KnO0Fri1GQ==", + "license": "MIT", + "dependencies": { + "js-cleanup": "^1.2.0", + "rollup-pluginutils": "^2.8.2" + }, + "engines": { + "node": "^10.14.2 || >=12.0.0" + }, + "peerDependencies": { + "rollup": ">=2.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "license": "MIT", + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/skip-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/skip-regex/-/skip-regex-1.0.2.tgz", + "integrity": "sha512-pEjMUbwJ5Pl/6Vn6FsamXHXItJXSRftcibixDmNCWbWhic0hzHrwkMZo0IZ7fMRH9KxcWDFSkzhccB4285PutA==", + "license": "MIT", + "engines": { + "node": ">=4.2" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/common/package.json b/common/package.json new file mode 100644 index 0000000..a368ef5 --- /dev/null +++ b/common/package.json @@ -0,0 +1,36 @@ +{ + "name": "logarithmplotter", + "version": "0.6.0", + "description": "2D plotter software to make Bode plots, sequences and distribution functions.", + "main": "src/index.mjs", + "scripts": { + "build": "rollup --config rollup.config.mjs", + "test": "c8 mocha test/**/*.mjs" + }, + "repository": { + "type": "git", + "url": "https://git.ad5001.eu/Ad5001/LogarithmPlotter" + }, + "author": "Ad5001 ", + "license": "GPL-3.0-or-later", + "dependencies": { + "@babel/preset-env": "^7.25.4", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^28.0.0", + "@rollup/plugin-node-resolve": "^15.3.0", + "c8": "^10.1.2", + "rollup": "^4.22.4", + "rollup-plugin-cleanup": "^3.2.1" + }, + "devDependencies": { + "@types/chai": "^5.0.0", + "@types/chai-spies": "^1.0.6", + "@types/chai-as-promised": "^8.0.1", + "@types/mocha": "^10.0.8", + "chai": "^5.1.1", + "chai-as-promised": "^8.0.0", + "chai-spies": "^1.1.0", + "esm": "^3.2.25", + "mocha": "^10.7.3" + } +} diff --git a/common/rollup.config.mjs b/common/rollup.config.mjs new file mode 100644 index 0000000..5c4d38a --- /dev/null +++ b/common/rollup.config.mjs @@ -0,0 +1,44 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { nodeResolve } from "@rollup/plugin-node-resolve" +import commonjs from "@rollup/plugin-commonjs" +import { babel } from "@rollup/plugin-babel" +import cleanup from "rollup-plugin-cleanup" + +const src = "./src/index.mjs" +const dest = "../build/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Common/index.mjs" + +export default { + input: src, + output: { + file: dest, + compact: false, + sourcemap: true, + format: "es" + }, + plugins: [ + nodeResolve({ browser: true }), + commonjs(), + cleanup({ comments: 'some' }), + babel({ + babelHelpers: "bundled" + }), + ] +} + diff --git a/common/src/events.mjs b/common/src/events.mjs new file mode 100644 index 0000000..1ef3777 --- /dev/null +++ b/common/src/events.mjs @@ -0,0 +1,116 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +/** + * We do not inherit the DOM's Event, because not only the DOM part is unnecessary, + * but also because it does not exist within Qt environments. + */ + + +export class BaseEvent { + ___name = "" + + /** + * @property {string} name - Name of the event. + */ + constructor(name) { + this.___name = name + } + + get name() { + return this.___name + } +} + + +/** + * Base class for all classes which can emit events. + */ +export class BaseEventEmitter { + static emits = [] + + /** @type {Record>} */ + #listeners = {} + + constructor() { + for(const eventType of this.constructor.emits) { + this.#listeners[eventType] = new Set() + } + } + + /** + * Adds a listener to an event that can be emitted by this object. + * + * @param {string} eventType - Name of the event to listen to. Throws an error if this object does not emit this kind of event. + * @param {function(BaseEvent)} eventListener - The function to be called back when the event is emitted. + */ + on(eventType, eventListener) { + if(eventType.includes(" ")) // Listen to several different events with the same listener. + for(const type of eventType.split(" ")) + this.on(type, eventListener) + else { + if(!this.constructor.emits.includes(eventType)) { + const className = this.constructor.name + const eventTypes = this.constructor.emits.join(", ") + throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`) + } + if(!this.#listeners[eventType].has(eventListener)) + this.#listeners[eventType].add(eventListener) + } + } + + /** + * Removes a listener from an event that can be emitted by this object. + * + * @param {string} eventType - Name of the event that was listened to. Throws an error if this object does not emit this kind of event. + * @param {function(BaseEvent)} eventListener - The function previously registered as a listener. + * @returns {boolean} True if the listener was removed, false if it was not found. + */ + off(eventType, eventListener) { + if(eventType.includes(" ")) { // Unlisten to several different events with the same listener. + let found = false + for(const type of eventType.split(" ")) + found ||= this.off(type, eventListener) + return found + } else { + if(!this.constructor.emits.includes(eventType)) { + const className = this.constructor.name + const eventTypes = this.constructor.emits.join(", ") + throw new Error(`Cannot listen to unknown event ${eventType} in class ${className}. ${className} only emits: ${eventTypes}`) + } + return this.#listeners[eventType].delete(eventListener) + } + } + + /** + * Emits an event to all of its listeners. + * + * @param {BaseEvent} e + */ + emit(e) { + if(!(e instanceof BaseEvent)) + throw new Error("Cannot emit non event object.") + if(!this.constructor.emits.includes(e.name)) { + const className = this.constructor.name + const eventTypes = this.constructor.emits.join(", ") + throw new Error(`Cannot emit event '${e.name}' from class ${className}. ${className} can only emit: ${eventTypes}`) + } + for(const listener of this.#listeners[e.name]) + listener(e) + } +} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/color.js b/common/src/history/color.mjs similarity index 58% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/color.js rename to common/src/history/color.mjs index 9f921b5..0efa29d 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/color.js +++ b/common/src/history/color.mjs @@ -1,59 +1,61 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library +import EditedProperty from "./editproperty.mjs" +import Objects from "../module/objects.mjs" -.import "editproperty.js" as EP -.import "../objects.js" as Objects - - -class ColorChanged extends EP.EditedProperty { +export default class ColorChanged extends EditedProperty { // Action used everytime when an object's color is changed - type(){return 'ColorChanged'} - - icon(){return 'appearance'} - - + type() { + return "ColorChanged" + } + + icon() { + return "appearance" + } + constructor(targetName = "", targetType = "Point", oldColor = "black", newColor = "white") { super(targetName, targetType, "color", oldColor, newColor) } - + export() { return [this.targetName, this.targetType, this.previousValue, this.newValue] } - - color(darkVer=false){return darkVer ? 'purple' : 'plum'} - - getReadableString() { - return qsTr("%1 %2's color changed from %3 to %4.") - .arg(Objects.types[this.targetType].displayType()).arg(this.targetName) - .arg(this.previousValue).arg(this.newValue) + + color(darkVer = false) { + return darkVer ? "purple" : "plum" } - + + getReadableString() { + return qsTranslate("color", "%1 %2's color changed from %3 to %4.") + .arg(Objects.types[this.targetType].displayType()).arg(this.targetName) + .arg(this.previousValue).arg(this.newValue) + } + formatColor(color) { return `██` } - + getHTMLString() { - return qsTr("%1 %2's color changed from %3 to %4.") - .arg(Objects.types[this.targetType].displayType()) - .arg(' ' + this.targetName + " ") - .arg(this.formatColor(this.previousValue)).arg(this.formatColor(this.newValue)) + return qsTranslate("color", "%1 %2's color changed from %3 to %4.") + .arg(Objects.types[this.targetType].displayType()) + .arg(" " + this.targetName + " ") + .arg(this.formatColor(this.previousValue)).arg(this.formatColor(this.newValue)) } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/common.js b/common/src/history/common.mjs similarity index 61% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/common.js rename to common/src/history/common.mjs index 2baf532..8c46757 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/common.js +++ b/common/src/history/common.mjs @@ -1,111 +1,116 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library +import History from "../module/history.mjs" +import Latex from "../module/latex.mjs" -.import "../math/latex.js" as Latex - -var themeTextColor; -var imageDepth = 2; -var fontSize = 14; - - -class Action { +export class Action { /** * Type of the action. - * + * * @returns {string} */ - type(){return 'Unknown'} - + type() { + return "Unknown" + } + /** * Icon associated with the action. - * + * * @returns {string} */ - icon(){return 'position'} - + 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. - * - * @returns {string} */ - undo() {} - + undo() { + } + /** * Redoes the action. - * - * @returns {string} */ - redo() {} - + redo() { + } + /** * Export the action to a serializable format. * NOTE: These arguments will be reinputed in the constructor in this order. - * - * @returns {string} + * + * @returns {string[]} */ export() { return [this.targetName, this.targetType] } - + /** - * Returns a string with the human readable description of the action. - * + * Returns a string with the human-readable description of the action. + * * @returns {string} */ getReadableString() { - return 'Unknown action' + 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 `` + return `` } - + /** * 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} + * @returns {Promise} */ - renderLatexAsHtml(latexString) { + async renderLatexAsHtml(latexString) { if(!Latex.enabled) throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.") - let latexInfo = Latex.Renderer.render(latexString, imageDepth*(fontSize+2), themeTextColor).split(",") - return `` + const imgDepth = History.imageDepth + const renderArguments = [ + latexString, + imgDepth * (History.fontSize + 2), + History.themeTextColor + ] + let render = Latex.findPrerendered(...renderArguments) + if(render === null) + render = await Latex.requestAsyncRender(...renderArguments) + const { source, width, height } = render + return `` } - + /** - * Returns a string with the HTML-formated description of the action. - * - * @returns {string} + * Returns a string with the HTML-formatted description of the action. + * + * @returns {string|Promise} */ getHTMLString() { return this.getReadableString() diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/create.js b/common/src/history/create.mjs similarity index 57% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/create.js rename to common/src/history/create.mjs index 8eca385..fb27f62 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/create.js +++ b/common/src/history/create.mjs @@ -1,65 +1,69 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library +import Objects from "../module/objects.mjs" +import { Action } from "./common.mjs" -.import "../objects.js" as Objects -.import "common.js" as C +/** + * Action used for the creation of an object. + */ +export default class CreateNewObject extends Action { + type() { + return "CreateNewObject" + } + + icon() { + return "create" + } + + color(darkVer = false) { + return darkVer ? "green" : "lime" + } -class CreateNewObject extends C.Action { - // Action used for the creation of an object - type(){return 'CreateNewObject'} - - icon(){return 'create'} - - color(darkVer=false){return darkVer ? 'green' : 'lime'} - constructor(targetName = "", targetType = "Point", properties = []) { super(targetName, targetType) this.targetProperties = properties } - + undo() { Objects.deleteObject(this.targetName) - //let targetIndex = Objects.getObjectsName(this.targetType).indexOf(this.targetName) - //delete Objects.currentObjectsByName[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() { return [this.targetName, this.targetType, this.targetProperties] } - + getReadableString() { - return qsTr("New %1 %2 created.").arg(Objects.types[this.targetType].displayType()).arg(this.targetName) + return qsTranslate("create", "New %1 %2 created.") + .arg(Objects.types[this.targetType].displayType()) + .arg(this.targetName) } - + getHTMLString() { - return qsTr("New %1 %2 created.") - .arg(Objects.types[this.targetType].displayType()) - .arg('' + this.targetName + "") + return qsTranslate("create", "New %1 %2 created.") + .arg(Objects.types[this.targetType].displayType()) + .arg("" + this.targetName + "") } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/delete.js b/common/src/history/delete.mjs similarity index 69% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/delete.js rename to common/src/history/delete.mjs index c70d2d0..2f8d8fe 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/delete.js +++ b/common/src/history/delete.mjs @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 . */ -.pragma library - -.import "../objects.js" as Objects -.import "create.js" as Create +import Objects from "../module/objects.mjs" +import CreateNewObject from "./create.mjs" -class DeleteObject extends Create.CreateNewObject { - // Action used at the deletion of an object. Basicly the same thing as creating a new object, except Redo & Undo are reversed. +/** + * Action used at the deletion of an object. Basically the same thing as creating a new object, except Redo & Undo are reversed. + */ +export default class DeleteObject extends CreateNewObject { type(){return 'DeleteObject'} icon(){return 'delete'} @@ -39,11 +39,13 @@ class DeleteObject extends Create.CreateNewObject { } getReadableString() { - return qsTr("%1 %2 deleted.").arg(Objects.types[this.targetType].displayType()).arg(this.targetName) + return qsTranslate("delete", "%1 %2 deleted.") + .arg(Objects.types[this.targetType].displayType()) + .arg(this.targetName) } getHTMLString() { - return qsTr("%1 %2 deleted.") + return qsTranslate("delete", "%1 %2 deleted.") .arg(Objects.types[this.targetType].displayType()) .arg('' + this.targetName + "") } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/editproperty.js b/common/src/history/editproperty.mjs similarity index 57% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/editproperty.js rename to common/src/history/editproperty.mjs index 9b712f2..2a81e7a 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/editproperty.js +++ b/common/src/history/editproperty.mjs @@ -1,39 +1,52 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" +import * as MathLib from "../math/index.mjs" +import { Action } from "./common.mjs" +import { DrawableObject } from "../objs/common.mjs" -.import "../objects.js" as Objects -.import "../math/latex.js" as Latex -.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'; +/** + * Action used everytime an object's property has been changed. + */ +export default class EditedProperty extends Action { + 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 @@ -42,12 +55,12 @@ class EditedProperty extends C.Action { 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); + 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); @@ -56,78 +69,91 @@ class EditedProperty extends C.Action { } 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 Common.DrawableObject) { + } else if(this.previousValue instanceof DrawableObject) { return [this.targetName, this.targetType, this.targetProperty, this.previousValue.name, this.newValue.name, true] } else { return [this.targetName, this.targetType, this.targetProperty, this.previousValue, this.newValue, false] } } - + setReadableValues() { - this.prevString = ""; - this.nextString = ""; + this.prevString = "" + this.nextString = "" + this._renderPromises = [] if(this.propertyType instanceof Object) { switch(this.propertyType.type) { case "Enum": this.prevString = this.propertyType.translatedValues[this.propertyType.values.indexOf(this.previousValue)] this.nextString = this.propertyType.translatedValues[this.propertyType.values.indexOf(this.newValue)] - break; + break case "ObjectType": this.prevString = this.previousValue == null ? "null" : this.previousValue.name this.nextString = this.newValue == null ? "null" : this.newValue.name - break; + break case "List": this.prevString = this.previousValue.join(",") this.nextString = this.newValue.name.join(",") - break; + break case "Dict": this.prevString = JSON.stringify(this.previousValue) this.nextString = JSON.stringify(this.newValue) - break; + break case "Expression": this.prevString = this.previousValue == null ? "null" : this.previousValue.toString() this.nextString = this.newValue == null ? "null" : this.newValue.toString() - break; + break } } else { this.prevString = this.previousValue == null ? "null" : this.previousValue.toString() this.nextString = this.newValue == null ? "null" : this.newValue.toString() } // HTML - this.prevHTML = ' '+this.prevString+' ' - this.nextHTML = ' '+this.nextString+' ' - 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) + this.prevHTML = " " + this.prevString + " " + this.nextHTML = " " + this.nextString + " " + if(Latex.enabled && typeof this.propertyType == "object" && this.propertyType.type === "Expression") { + // Store promises so that querying can wait for them to finish. + this._renderPromises = [ + this.renderLatexAsHtml(this.previousValue.latexMarkup).then(prev => this.prevHTML = prev), + this.renderLatexAsHtml(this.newValue.latexMarkup).then(next => this.nextHTML = next) + ] } } - + 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) + return qsTranslate("editproperty", "%1 of %2 %3 changed from \"%4\" to \"%5\".") + .arg(this.targetPropertyReadable) + .arg(Objects.types[this.targetType].displayType()) + .arg(this.targetName).arg(this.prevString).arg(this.nextString) } - - getHTMLString() { - return qsTr('%1 of %2 changed from %3 to %4.') - .arg(this.targetPropertyReadable) - .arg(' ' + this.targetName + ' ') - .arg(this.prevHTML) - .arg(this.nextHTML) + + /** + * + * @return {Promise|string} + */ + async getHTMLString() { + const translation = qsTranslate("editproperty", "%1 of %2 changed from %3 to %4.") + .arg(this.targetPropertyReadable) + .arg(" " + this.targetName + " ") + // Check if we need to wait for LaTeX HTML to be rendered. + if(this.prevHTML === undefined || this.nextHTML === undefined) { + const [prevHTML, nextHTML] = await Promise.all(this._renderPromises) + this.prevHTML = this.prevHTML ?? prevHTML + this.nextHTML = this.nextHTML ?? nextHTML + } + return translation.arg(this.prevHTML).arg(this.nextHTML) } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/historylib.js b/common/src/history/index.mjs similarity index 64% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/historylib.js rename to common/src/history/index.mjs index d9fa812..a2ef1ea 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/historylib.js +++ b/common/src/history/index.mjs @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,30 +18,27 @@ // 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 "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/position.js" as Pos -.import "history/visibility.js" as V -.import "history/name.js" as Name -.import "history/color.js" as Color - -var history = null; +import { Action as A } from "./common.mjs" +import Create from "./create.mjs" +import Delete from "./delete.mjs" +import EP from "./editproperty.mjs" +import Pos from "./position.mjs" +import V from "./visibility.mjs" +import Name from "./name.mjs" +import Color from "./color.mjs" -var Action = Common.Action -var CreateNewObject = Create.CreateNewObject -var DeleteObject = Delete.DeleteObject -var EditedProperty = EP.EditedProperty -var EditedPosition = Pos.EditedPosition -var EditedVisibility = V.EditedVisibility -var NameChanged = Name.NameChanged -var ColorChanged = Color.ColorChanged +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 Actions = { +export const Actions = { "Action": Action, "CreateNewObject": CreateNewObject, "DeleteObject": DeleteObject, diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/name.js b/common/src/history/name.mjs similarity index 75% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/name.js rename to common/src/history/name.mjs index 89f97c3..61bb6b5 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/name.js +++ b/common/src/history/name.mjs @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,17 @@ * along with this program. If not, see . */ -.pragma library +import EditedProperty from "./editproperty.mjs" +import Objects from "../module/objects.mjs" -.import "editproperty.js" as EP -.import "../objects.js" as Objects - - -class NameChanged extends EP.EditedProperty { - // Action used everytime an object's property has been changed +/** + * Action used everytime an object's name has been changed. + */ +export default class NameChanged extends EditedProperty { type(){return 'NameChanged'} icon(){return 'name'} - - + color(darkVer=false){return darkVer ? 'darkorange' : 'orange'} constructor(targetName = "", targetType = "Point", newName = "") { @@ -45,20 +43,16 @@ class NameChanged extends EP.EditedProperty { redo() { Objects.renameObject(this.previousValue, this.newValue) - //let obj = Objects.currentObjectsByName[this.previousValue] - //obj.name = this.newValue - //Objects.currentObjectsByName[this.newValue] = obj - //delete Objects.currentObjectsByName[this.previousValue] } getReadableString() { - return qsTr('%1 %2 renamed to %3.') + return qsTranslate("name", '%1 %2 renamed to %3.') .arg(Objects.types[this.targetType].displayType()) .arg(this.targetName).arg(this.newValue) } getHTMLString() { - return qsTr('%1 %2 renamed to %3.') + return qsTranslate("name", '%1 %2 renamed to %3.') .arg(Objects.types[this.targetType].displayType()) .arg('' + this.targetName + "").arg(''+this.newValue+'') } diff --git a/common/src/history/position.mjs b/common/src/history/position.mjs new file mode 100644 index 0000000..2b9c0ff --- /dev/null +++ b/common/src/history/position.mjs @@ -0,0 +1,109 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" +import * as MathLib from "../math/index.mjs" +import { escapeHTML } from "../utils/index.mjs" +import { Action } from "./common.mjs" + +/** + * Action used for objects that have a X and Y expression properties (points, texts...) + */ +export default class EditedPosition extends Action { + type() { + return "EditedPosition" + } + + icon() { + return "position" + } + + color(darkVer = false) { + return darkVer ? "seagreen" : "lightseagreen" + } + + constructor(targetName = "", targetType = "Point", previousXValue = "", newXValue = "", previousYValue = "", newYValue = "") { + super(targetName, targetType) + let imports = { + "previousXValue": previousXValue, + "previousYValue": previousYValue, + "newXValue": newXValue, + "newYValue": newYValue + } + for(let name in imports) + this[name] = (typeof imports[name]) == "string" ? new MathLib.Expression(imports[name]) : imports[name] + this.setReadableValues() + } + + undo() { + Objects.currentObjectsByName[this.targetName].x = this.previousXValue + Objects.currentObjectsByName[this.targetName].y = this.previousYValue + Objects.currentObjectsByName[this.targetName].update() + } + + redo() { + Objects.currentObjectsByName[this.targetName].x = this.newXValue + Objects.currentObjectsByName[this.targetName].y = this.newYValue + Objects.currentObjectsByName[this.targetName].update() + } + + setReadableValues() { + this.prevString = `(${this.previousXValue.toString()},${this.previousYValue.toString()})` + this.nextString = `(${this.newXValue.toString()},${this.newYValue.toString()})` + this._renderPromises = [] + // Render as LaTeX + if(Latex.enabled) { + const prevMarkup = `\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)` + const nextMarkup = `\\left(${this.newXValue.latexMarkup},${this.newYValue.latexMarkup}\\right)` + this._renderPromises = [ // Will be taken in promise.all + this.renderLatexAsHtml(prevMarkup), + this.renderLatexAsHtml(nextMarkup) + ] + } else { + this.prevHTML = " " + escapeHTML(this.prevString) + " " + this.nextHTML = " " + escapeHTML(this.nextString) + " " + } + + } + + export() { + return [this.targetName, this.targetType, + this.previousXValue.toEditableString(), this.newXValue.toEditableString(), + this.previousYValue.toEditableString(), this.newYValue.toEditableString()] + } + + getReadableString() { + return qsTranslate("position", "Position of %1 %2 set from \"%3\" to \"%4\".") + .arg(Objects.types[this.targetType].displayType()) + .arg(this.targetName).arg(this.prevString).arg(this.nextString) + } + + async getHTMLString() { + const translation = qsTranslate("position", "Position of %1 set from %2 to %3.") + .arg(" " + this.targetName + " ") + // Check if we need to wait for LaTeX HTML to be rendered. + if(this.prevHTML === undefined || this.nextHTML === undefined) { + const [prevHTML, nextHTML] = await Promise.all(this._renderPromises) + this.prevHTML = this.prevHTML ?? prevHTML + this.nextHTML = this.nextHTML ?? nextHTML + } + return translation.arg(this.prevHTML).arg(this.nextHTML) + + } +} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/visibility.js b/common/src/history/visibility.mjs similarity index 60% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/visibility.js rename to common/src/history/visibility.mjs index 78981eb..9254d67 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/history/visibility.js +++ b/common/src/history/visibility.mjs @@ -1,57 +1,61 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library - -.import "editproperty.js" as EP -.import "../objects.js" as Objects +import EditedProperty from "./editproperty.mjs" +import Objects from "../module/objects.mjs" -class EditedVisibility extends EP.EditedProperty { - // Action used when an object's shown or hidden. - type(){return 'EditedVisibility'} - - icon(){return 'visibility'} - - color(darkVer=false){ - return this.newValue ? - (darkVer ? 'darkgray' : 'whitesmoke') : - (darkVer ? 'dimgray' : 'lightgray') +/** + * Action used when an object's shown or hidden. + */ +export default class EditedVisibility extends EditedProperty { + type() { + return "EditedVisibility" } - + + icon() { + return "visibility" + } + + color(darkVer = false) { + return this.newValue ? + (darkVer ? "darkgray" : "whitesmoke") : + (darkVer ? "dimgray" : "lightgray") + } + constructor(targetName = "", targetType = "Point", newValue = true) { super(targetName, targetType, "visible", !newValue, newValue) } - + export() { return [this.targetName, this.targetType, this.newValue] } - + getReadableString() { - return (this.newValue ? qsTr('%1 %2 shown.') : qsTr('%1 %2 hidden.')) + return (this.newValue ? qsTranslate("visibility", "%1 %2 shown.") : qsTranslate("visibility", "%1 %2 hidden.")) .arg(Objects.types[this.targetType].displayType()) .arg(this.targetName) } - + getHTMLString() { - return (this.newValue ? qsTr('%1 %2 shown.') : qsTr('%1 %2 hidden.')) + return (this.newValue ? qsTranslate("visibility", "%1 %2 shown.") : qsTranslate("visibility", "%1 %2 hidden.")) .arg(Objects.types[this.targetType].displayType()) - .arg('' + this.targetName + "") + .arg("" + this.targetName + "") } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/parsing.js b/common/src/index.mjs similarity index 61% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/parsing.js rename to common/src/index.mjs index b2cc6bc..57efb01 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/parsing.js +++ b/common/src/index.mjs @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,13 @@ * along with this program. If not, see . */ -.pragma library +import js from "./lib/polyfills/js.mjs" -.import "reference.js" as Reference -.import "tokenizer.js" as TK -.import "common.js" as Common +export * as Utils from "./utils/index.mjs" -var Input = Common.InputExpression -var TokenType = TK.TokenType -var Token = TK.Token -var Tokenizer = TK.ExpressionTokenizer +import * as ObjsAutoload from "./objs/autoload.mjs" -var FUNCTIONS_LIST = Reference.FUNCTIONS_LIST -var FUNCTIONS = Reference.FUNCTIONS -var FUNCTIONS_USAGE = Reference.FUNCTIONS_USAGE -var CONSTANTS_LIST = Reference.CONSTANTS_LIST -var CONSTANTS = Reference.CONSTANTS +export * as Modules from "./module/index.mjs" +export * as MathLib from "./math/index.mjs" +export * as HistoryLib from "./history/index.mjs" +export * as Parsing from "./parsing/index.mjs" diff --git a/common/src/lib/expr-eval/expression.mjs b/common/src/lib/expr-eval/expression.mjs new file mode 100644 index 0000000..45854e8 --- /dev/null +++ b/common/src/lib/expr-eval/expression.mjs @@ -0,0 +1,524 @@ +/** + * Based on ndef.parser, by Raphael Graf + * http://www.undefined.ch/mparser/index.html + * + * Ported to JavaScript and modified by Matthew Crumley + * https://silentmatt.com/javascript-expression-evaluator/ + * + * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu) + * + * Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code + * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, + * but don't feel like you have to let me know or ask permission. + */ + +import { + Instruction, + IOP3, IOP2, IOP1, + INUMBER, IARRAY, + IVAR, IVARNAME, + IEXPR, IEXPREVAL, + IMEMBER, IFUNCALL, + IENDSTATEMENT, + unaryInstruction, binaryInstruction, ternaryInstruction +} from "./instruction.mjs" + +/** + * Simplifies the given instructions + * @param {Instruction[]} tokens + * @param {Record.} unaryOps + * @param {Record.} binaryOps + * @param {Record.} ternaryOps + * @param {Record.} values + * @return {Instruction[]} + */ +function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) { + const nstack = [] + const newexpression = [] + let n1, n2, n3 + let f + for(let i = 0; i < tokens.length; i++) { + let item = tokens[i] + const type = item.type + if(type === INUMBER || type === IVARNAME) { + if(Array.isArray(item.value)) { + nstack.push.apply(nstack, simplify(item.value.map(function(x) { + return new Instruction(INUMBER, x) + }).concat(new Instruction(IARRAY, item.value.length)), unaryOps, binaryOps, ternaryOps, values)) + } else { + nstack.push(item) + } + } else if(type === IVAR && values.hasOwnProperty(item.value)) { + item = new Instruction(INUMBER, values[item.value]) + nstack.push(item) + } else if(type === IOP2 && nstack.length > 1) { + n2 = nstack.pop() + n1 = nstack.pop() + f = binaryOps[item.value] + item = new Instruction(INUMBER, f(n1.value, n2.value)) + nstack.push(item) + } else if(type === IOP3 && nstack.length > 2) { + n3 = nstack.pop() + n2 = nstack.pop() + n1 = nstack.pop() + if(item.value === "?") { + nstack.push(n1.value ? n2.value : n3.value) + } else { + f = ternaryOps[item.value] + item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)) + nstack.push(item) + } + } else if(type === IOP1 && nstack.length > 0) { + n1 = nstack.pop() + f = unaryOps[item.value] + item = new Instruction(INUMBER, f(n1.value)) + nstack.push(item) + } else if(type === IEXPR) { + while(nstack.length > 0) { + newexpression.push(nstack.shift()) + } + newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values))) + } else if(type === IMEMBER && nstack.length > 0) { + n1 = nstack.pop() + if(item.value in n1.value) + nstack.push(new Instruction(INUMBER, n1.value[item.value])) + else + throw new Error(qsTranslate("error", "Cannot find property %1 of object %2.").arg(item.value).arg(n1)) + } else { + while(nstack.length > 0) { + newexpression.push(nstack.shift()) + } + newexpression.push(item) + } + } + while(nstack.length > 0) { + newexpression.push(nstack.shift()) + } + return newexpression +} + +/** + * In the given instructions, replaces variable by expr. + * @param {Instruction[]} tokens + * @param {string} variable + * @param {ExprEvalExpression} expr + * @return {Instruction[]} + */ +function substitute(tokens, variable, expr) { + const newexpression = [] + for(let i = 0; i < tokens.length; i++) { + let item = tokens[i] + const type = item.type + if(type === IVAR && item.value === variable) { + for(let j = 0; j < expr.tokens.length; j++) { + const expritem = expr.tokens[j] + let replitem + if(expritem.type === IOP1) { + replitem = unaryInstruction(expritem.value) + } else if(expritem.type === IOP2) { + replitem = binaryInstruction(expritem.value) + } else if(expritem.type === IOP3) { + replitem = ternaryInstruction(expritem.value) + } else { + replitem = new Instruction(expritem.type, expritem.value) + } + newexpression.push(replitem) + } + } else if(type === IEXPR) { + newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr))) + } else { + newexpression.push(item) + } + } + return newexpression +} + +/** + * Evaluates the given instructions for a given Expression with given values. + * @param {Instruction[]} tokens + * @param {ExprEvalExpression} expr + * @param {Record.} values + * @return {number} + */ +function evaluate(tokens, expr, values) { + const nstack = [] + let n1, n2, n3 + let f, args, argCount + + if(isExpressionEvaluator(tokens)) { + return resolveExpression(tokens, values) + } + + for(let i = 0; i < tokens.length; i++) { + const item = tokens[i] + const type = item.type + if(type === INUMBER || type === IVARNAME) { + nstack.push(item.value) + } else if(type === IOP2) { + n2 = nstack.pop() + n1 = nstack.pop() + if(item.value === "and") { + nstack.push(n1 ? !!evaluate(n2, expr, values) : false) + } else if(item.value === "or") { + nstack.push(n1 ? true : !!evaluate(n2, expr, values)) + } else { + f = expr.binaryOps[item.value] + nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values))) + } + } else if(type === IOP3) { + n3 = nstack.pop() + n2 = nstack.pop() + n1 = nstack.pop() + if(item.value === "?") { + nstack.push(evaluate(n1 ? n2 : n3, expr, values)) + } else { + f = expr.ternaryOps[item.value] + nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values), resolveExpression(n3, values))) + } + } else if(type === IVAR) { + // Check for variable value + if(/^__proto__|prototype|constructor$/.test(item.value)) { + throw new Error("WARNING: Prototype access detected and denied. If you downloaded this file from the internet, this file might be a virus.") + } else if(item.value in expr.functions) { + nstack.push(expr.functions[item.value]) + } else if(item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) { + nstack.push(expr.unaryOps[item.value]) + } else { + const v = values[item.value] + if(v !== undefined) { + nstack.push(v) + } else { + throw new Error(qsTranslate("error", "Undefined variable %1.").arg(item.value)) + } + } + } else if(type === IOP1) { + n1 = nstack.pop() + f = expr.unaryOps[item.value] + nstack.push(f(resolveExpression(n1, values))) + } else if(type === IFUNCALL) { + argCount = item.value + args = [] + while(argCount-- > 0) { + args.unshift(resolveExpression(nstack.pop(), values)) + } + f = nstack.pop() + if(f.apply && f.call) { + nstack.push(f.apply(undefined, args)) + } else if(f.execute) { + // Objects & expressions execution + if(args.length >= 1) + nstack.push(f.execute.apply(f, args)) + else + throw new Error(qsTranslate("error", "In order to be executed, object %1 must have at least one argument.").arg(f)) + } else { + throw new Error(qsTranslate("error", "%1 cannot be executed.").arg(f)) + } + } else if(type === IEXPR) { + nstack.push(createExpressionEvaluator(item, expr)) + } else if(type === IEXPREVAL) { + nstack.push(item) + } else if(type === IMEMBER) { + n1 = nstack.pop() + if(item.value in n1) + if(n1[item.value].execute && n1[item.value].cached) + nstack.push(n1[item.value].execute()) + else + nstack.push(n1[item.value]) + else + throw new Error(qsTranslate("error", "Cannot find property %1 of object %2.").arg(item.value).arg(n1)) + } else if(type === IENDSTATEMENT) { + nstack.pop() + } else if(type === IARRAY) { + argCount = item.value + args = [] + while(argCount-- > 0) { + args.unshift(nstack.pop()) + } + nstack.push(args) + } else { + throw new Error(qsTranslate("error", "Invalid expression.")) + } + } + if(nstack.length > 1) { + throw new Error(qsTranslate("error", "Invalid expression (parity).")) + } + // Explicitly return zero to avoid test issues caused by -0 + return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values) +} + +function createExpressionEvaluator(token, expr) { + if(isExpressionEvaluator(token)) return token + return { + type: IEXPREVAL, + value: function(scope) { + return evaluate(token.value, expr, scope) + } + } +} + +function isExpressionEvaluator(n) { + return n && n.type === IEXPREVAL +} + +function resolveExpression(n, values) { + return isExpressionEvaluator(n) ? n.value(values) : n +} + +/** + * Converts the given instructions to a string + * If toJS is active, can be evaluated with eval, otherwise it can be reparsed by the parser. + * @param {Instruction[]} tokens + * @param {boolean} toJS + * @return {string} + */ +function expressionToString(tokens, toJS) { + let nstack = [] + let n1, n2, n3 + let f, args, argCount + for(let i = 0; i < tokens.length; i++) { + const item = tokens[i] + const type = item.type + if(type === INUMBER) { + if(typeof item.value === "number" && item.value < 0) { + nstack.push("(" + item.value + ")") + } else if(Array.isArray(item.value)) { + nstack.push("[" + item.value.map(escapeValue).join(", ") + "]") + } else { + nstack.push(escapeValue(item.value)) + } + } else if(type === IOP2) { + n2 = nstack.pop() + n1 = nstack.pop() + f = item.value + if(toJS) { + if(f === "^") { + nstack.push("Math.pow(" + n1 + ", " + n2 + ")") + } else if(f === "and") { + nstack.push("(!!" + n1 + " && !!" + n2 + ")") + } else if(f === "or") { + nstack.push("(!!" + n1 + " || !!" + n2 + ")") + } else if(f === "||") { + nstack.push("(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((" + n1 + "),(" + n2 + ")))") + } else if(f === "==") { + nstack.push("(" + n1 + " === " + n2 + ")") + } else if(f === "!=") { + nstack.push("(" + n1 + " !== " + n2 + ")") + } else if(f === "[") { + nstack.push(n1 + "[(" + n2 + ") | 0]") + } else { + nstack.push("(" + n1 + " " + f + " " + n2 + ")") + } + } else { + if(f === "[") { + nstack.push(n1 + "[" + n2 + "]") + } else { + nstack.push("(" + n1 + " " + f + " " + n2 + ")") + } + } + } else if(type === IOP3) { + n3 = nstack.pop() + n2 = nstack.pop() + n1 = nstack.pop() + f = item.value + if(f === "?") { + nstack.push("(" + n1 + " ? " + n2 + " : " + n3 + ")") + } else { + throw new Error(qsTranslate("error", "Invalid expression.")) + } + } else if(type === IVAR || type === IVARNAME) { + nstack.push(item.value) + } else if(type === IOP1) { + n1 = nstack.pop() + f = item.value + if(f === "-" || f === "+") { + nstack.push("(" + f + n1 + ")") + } else if(toJS) { + if(f === "not") { + nstack.push("(" + "!" + n1 + ")") + } else if(f === "!") { + nstack.push("fac(" + n1 + ")") + } else { + nstack.push(f + "(" + n1 + ")") + } + } else if(f === "!") { + nstack.push("(" + n1 + "!)") + } else { + nstack.push("(" + f + " " + n1 + ")") + } + } else if(type === IFUNCALL) { + argCount = item.value + args = [] + while(argCount-- > 0) { + args.unshift(nstack.pop()) + } + f = nstack.pop() + nstack.push(f + "(" + args.join(", ") + ")") + } else if(type === IMEMBER) { + n1 = nstack.pop() + nstack.push(n1 + "." + item.value) + } else if(type === IARRAY) { + argCount = item.value + args = [] + while(argCount-- > 0) { + args.unshift(nstack.pop()) + } + nstack.push("[" + args.join(", ") + "]") + } else if(type === IEXPR) { + nstack.push("(" + expressionToString(item.value, toJS) + ")") + } else if(type === IENDSTATEMENT) { + + } else { + throw new Error(qsTranslate("error", "Invalid expression.")) + } + } + if(nstack.length > 1) { + if(toJS) { + nstack = [nstack.join(",")] + } else { + nstack = [nstack.join(";")] + } + } + return String(nstack[0]) +} + +export function escapeValue(v) { + if(typeof v === "string") { + return JSON.stringify(v).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029") + } + return v +} + +/** + * Pushes all symbols from tokens into the symbols array. + * @param {Instruction[]} tokens + * @param {string[]} symbols + * @param {{withMembers: (boolean|undefined)}}options + */ +function getSymbols(tokens, symbols, options) { + options = options || {} + const withMembers = !!options.withMembers + let prevVar = null + + for(let i = 0; i < tokens.length; i++) { + const item = tokens[i] + if(item.type === IVAR || item.type === IVARNAME) { + if(!withMembers && !symbols.includes(item.value)) { + symbols.push(item.value) + } else if(prevVar !== null) { + if(!symbols.includes(prevVar)) { + symbols.push(prevVar) + } + prevVar = item.value + } else { + prevVar = item.value + } + } else if(item.type === IMEMBER && withMembers && prevVar !== null) { + prevVar += "." + item.value + } else if(item.type === IEXPR) { + getSymbols(item.value, symbols, options) + } else if(prevVar !== null) { + if(!symbols.includes(prevVar)) { + symbols.push(prevVar) + } + prevVar = null + } + } + + if(prevVar !== null && !symbols.includes(prevVar)) { + symbols.push(prevVar) + } +} + +export class ExprEvalExpression { + /** + * @param {Instruction[]} tokens + * @param {Parser} parser + */ + constructor(tokens, parser) { + this.tokens = tokens + this.parser = parser + this.unaryOps = parser.unaryOps + this.binaryOps = parser.binaryOps + this.ternaryOps = parser.ternaryOps + this.functions = parser.functions + } + + /** + * Simplifies the expression. + * @param {Object|undefined} values + * @returns {ExprEvalExpression} + */ + simplify(values) { + values = values || {} + return new ExprEvalExpression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser) + } + + /** + * Creates a new expression where the variable is substituted by the given expression. + * @param {string} variable + * @param {string|ExprEvalExpression} expr + * @returns {ExprEvalExpression} + */ + substitute(variable, expr) { + if(!(expr instanceof ExprEvalExpression)) { + expr = this.parser.parse(String(expr)) + } + + return new ExprEvalExpression(substitute(this.tokens, variable, expr), this.parser) + } + + /** + * Calculates the value of the expression by giving all variables and their corresponding values. + * @param {Object} values + * @returns {number} + */ + evaluate(values) { + values = Object.assign({}, values, this.parser.consts) + return evaluate(this.tokens, this, values) + } + + toString() { + return expressionToString(this.tokens, false) + } + + + /** + * Returns the list of symbols (string of characters) which are not defined + * as constants or functions. + * @returns {string[]} + */ + variables(options = {}) { + const vars = [] + getSymbols(this.tokens, vars, options) + const functions = this.functions + const consts = this.parser.consts + return vars.filter((name) => { + return !(name in functions) && !(name in consts) + }) + } + + + /** + * Converts the expression to a JS function. + * @param {string} param - Parsed variables for the function. + * @param {Object.} variables - Default variables to provide. + * @returns {function(...any)} + */ + toJSFunction(param, variables) { + const expr = this + const f = new Function(param, "with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return " + expressionToString(this.simplify(variables).tokens, true) + "; }") // eslint-disable-line no-new-func + return function() { + return f.apply(expr, arguments) + } + } +} \ No newline at end of file diff --git a/common/src/lib/expr-eval/instruction.mjs b/common/src/lib/expr-eval/instruction.mjs new file mode 100644 index 0000000..9157fea --- /dev/null +++ b/common/src/lib/expr-eval/instruction.mjs @@ -0,0 +1,82 @@ +/** + * Based on ndef.parser, by Raphael Graf + * http://www.undefined.ch/mparser/index.html + * + * Ported to JavaScript and modified by Matthew Crumley + * https://silentmatt.com/javascript-expression-evaluator/ + * + * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu) + * + * Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code + * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, + * but don't feel like you have to let me know or ask permission. + */ + +export const INUMBER = "INUMBER" +export const IOP1 = "IOP1" +export const IOP2 = "IOP2" +export const IOP3 = "IOP3" +export const IVAR = "IVAR" +export const IVARNAME = "IVARNAME" +export const IFUNCALL = "IFUNCALL" +export const IEXPR = "IEXPR" +export const IEXPREVAL = "IEXPREVAL" +export const IMEMBER = "IMEMBER" +export const IENDSTATEMENT = "IENDSTATEMENT" +export const IARRAY = "IARRAY" + + +export class Instruction { + /** + * + * @param {string} type + * @param {any} value + */ + constructor(type, value) { + this.type = type + this.value = (value !== undefined && value !== null) ? value : 0 + } + + toString() { + switch(this.type) { + case INUMBER: + case IOP1: + case IOP2: + case IOP3: + case IVAR: + case IVARNAME: + case IENDSTATEMENT: + return this.value + case IFUNCALL: + return "CALL " + this.value + case IARRAY: + return "ARRAY " + this.value + case IMEMBER: + return "." + this.value + default: + return "Invalid Instruction" + } + } +} + +export function unaryInstruction(value) { + return new Instruction(IOP1, value) +} + +export function binaryInstruction(value) { + return new Instruction(IOP2, value) +} + +export function ternaryInstruction(value) { + return new Instruction(IOP3, value) +} \ No newline at end of file diff --git a/common/src/lib/expr-eval/parser.mjs b/common/src/lib/expr-eval/parser.mjs new file mode 100644 index 0000000..98f4539 --- /dev/null +++ b/common/src/lib/expr-eval/parser.mjs @@ -0,0 +1,160 @@ +/** + * Based on ndef.parser, by Raphael Graf + * http://www.undefined.ch/mparser/index.html + * + * Ported to JavaScript and modified by Matthew Crumley + * https://silentmatt.com/javascript-expression-evaluator/ + * + * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu) + * + * Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code + * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, + * but don't feel like you have to let me know or ask permission. + */ + +import * as Polyfill from "./polyfill.mjs" +import { ParserState } from "./parserstate.mjs" +import { TEOF, TokenStream } from "./tokens.mjs" +import { ExprEvalExpression } from "./expression.mjs" + +const optionNameMap = { + "+": "add", + "-": "subtract", + "*": "multiply", + "/": "divide", + "%": "remainder", + "^": "power", + "!": "factorial", + "<": "comparison", + ">": "comparison", + "<=": "comparison", + ">=": "comparison", + "==": "comparison", + "!=": "comparison", + "||": "concatenate", + "and": "logical", + "or": "logical", + "not": "logical", + "?": "conditional", + ":": "conditional", + "[": "array" +} + +export class Parser { + constructor(options) { + this.options = options || {} + this.unaryOps = { + sin: Math.sin, + cos: Math.cos, + tan: Math.tan, + asin: Math.asin, + acos: Math.acos, + atan: Math.atan, + sinh: Math.sinh || Polyfill.sinh, + cosh: Math.cosh || Polyfill.cosh, + tanh: Math.tanh || Polyfill.tanh, + asinh: Math.asinh || Polyfill.asinh, + acosh: Math.acosh || Polyfill.acosh, + atanh: Math.atanh || Polyfill.atanh, + sqrt: Math.sqrt, + cbrt: Math.cbrt || Polyfill.cbrt, + log: Math.log, + log2: Math.log2 || Polyfill.log2, + ln: Math.log, + lg: Math.log10 || Polyfill.log10, + log10: Math.log10 || Polyfill.log10, + expm1: Math.expm1 || Polyfill.expm1, + log1p: Math.log1p || Polyfill.log1p, + abs: Math.abs, + ceil: Math.ceil, + floor: Math.floor, + round: Math.round, + trunc: Math.trunc || Polyfill.trunc, + "-": Polyfill.neg, + "+": Number, + exp: Math.exp, + not: Polyfill.not, + length: Polyfill.stringOrArrayLength, + "!": Polyfill.factorial, + sign: Math.sign || Polyfill.sign + } + this.unaryOpsList = Object.keys(this.unaryOps) + + this.binaryOps = { + "+": Polyfill.add, + "-": Polyfill.sub, + "*": Polyfill.mul, + "/": Polyfill.div, + "%": Polyfill.mod, + "^": Math.pow, + "||": Polyfill.concat, + "==": Polyfill.equal, + "!=": Polyfill.notEqual, + ">": Polyfill.greaterThan, + "<": Polyfill.lessThan, + ">=": Polyfill.greaterThanEqual, + "<=": Polyfill.lessThanEqual, + and: Polyfill.andOperator, + or: Polyfill.orOperator, + "in": Polyfill.inOperator, + "[": Polyfill.arrayIndex + } + + this.ternaryOps = { + "?": Polyfill.condition + } + + this.functions = { + random: Polyfill.random, + fac: Polyfill.factorial, + min: Polyfill.min, + max: Polyfill.max, + hypot: Math.hypot || Polyfill.hypot, + pyt: Math.hypot || Polyfill.hypot, + pow: Math.pow, + atan2: Math.atan2, + "if": Polyfill.condition, + gamma: Polyfill.gamma, + "Γ": Polyfill.gamma, + roundTo: Polyfill.roundTo, + } + + // These constants will automatically be replaced the MOMENT they are parsed. + // (Original consts from the parser) + this.builtinConsts = {} + // These consts will only be replaced when the expression is evaluated. + this.consts = {} + + } + + parse(expr) { + const instr = [] + const parserState = new ParserState( + this, + new TokenStream(this, expr), + { allowMemberAccess: this.options.allowMemberAccess } + ) + + parserState.parseExpression(instr) + parserState.expect(TEOF, QT_TRANSLATE_NOOP("error", "EOF")) + + return new ExprEvalExpression(instr, this) + } + + isOperatorEnabled(op) { + const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op + const operators = this.options.operators || {} + + return !(optionName in operators) || !!operators[optionName] + } +} \ No newline at end of file diff --git a/common/src/lib/expr-eval/parserstate.mjs b/common/src/lib/expr-eval/parserstate.mjs new file mode 100644 index 0000000..d4b3a3b --- /dev/null +++ b/common/src/lib/expr-eval/parserstate.mjs @@ -0,0 +1,398 @@ +/** + * Based on ndef.parser, by Raphael Graf + * http://www.undefined.ch/mparser/index.html + * + * Ported to JavaScript and modified by Matthew Crumley + * https://silentmatt.com/javascript-expression-evaluator/ + * + * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu) + * + * Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code + * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, + * but don't feel like you have to let me know or ask permission. + */ + +import { TBRACKET, TCOMMA, TEOF, TNAME, TNUMBER, TOP, TPAREN, TSTRING } from "./tokens.mjs" +import { + Instruction, + IARRAY, IEXPR, IFUNCALL, IMEMBER, + INUMBER, IVAR, + ternaryInstruction, binaryInstruction, unaryInstruction +} from "./instruction.mjs" + +const COMPARISON_OPERATORS = ["==", "!=", "<", "<=", ">=", ">", "in"] +const ADD_SUB_OPERATORS = ["+", "-", "||"] +const TERM_OPERATORS = ["*", "/", "%"] + +export class ParserState { + /** + * + * @param {Parser} parser + * @param {TokenStream} tokenStream + * @param {{[operators]: Object., [allowMemberAccess]: boolean}} options + */ + constructor(parser, tokenStream, options) { + this.parser = parser + this.tokens = tokenStream + this.current = null + this.nextToken = null + this.next() + this.savedCurrent = null + this.savedNextToken = null + this.allowMemberAccess = options.allowMemberAccess !== false + } + + /** + * Queries the next token for parsing. + * @return {Token} + */ + next() { + this.current = this.nextToken + this.nextToken = this.tokens.next() + return this.nextToken + } + + /** + * Checks if a given Token matches a condition (called if function, one of if array, and exact match otherwise) + * @param {Token} token + * @param {Array|function(Token): boolean|string|number|boolean} [value] + * @return {boolean} + */ + tokenMatches(token, value) { + if(typeof value === "undefined") { + return true + } else if(Array.isArray(value)) { + return value.includes(token.value) + } else if(typeof value === "function") { + return value(token) + } else { + return token.value === value + } + } + + /** + * Saves the current state (current and next token) to be restored later. + */ + save() { + this.savedCurrent = this.current + this.savedNextToken = this.nextToken + this.tokens.save() + } + + /** + * Restores a previous state (current and next token) from last save. + */ + restore() { + this.tokens.restore() + this.current = this.savedCurrent + this.nextToken = this.savedNextToken + } + + /** + * Checks if the next token matches the given type and value, and if so, consume the current token. + * Returns true if the check matches. + * @param {string} type + * @param {any} [value] + * @return {boolean} + */ + accept(type, value) { + if(this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) { + this.next() + return true + } + return false + } + + /** + * Throws an error if the next token does not match the given type and value. Otherwise, consumes the current token. + * @param {string} type + * @param {any} [value] + */ + expect(type, value) { + if(!this.accept(type, value)) { + throw new Error(qsTranslate("error", "Parse error [position %1]: %2") + .arg(this.tokens.pos) + .arg(qsTranslate("error", "Expected %1").arg(value || type))) + } + } + + /** + * Converts enough Tokens to form an expression atom (generally the next part of the expression) into an instruction + * and pushes it to the instruction list. + * Throws an error if an unexpected token gets parsed. + * @param {Instruction[]} instr + */ + parseAtom(instr) { + const prefixOperators = this.tokens.unaryOpsList + + if(this.accept(TNAME) || this.accept(TOP, prefixOperators)) { + instr.push(new Instruction(IVAR, this.current.value)) + } else if(this.accept(TNUMBER)) { + instr.push(new Instruction(INUMBER, this.current.value)) + } else if(this.accept(TSTRING)) { + instr.push(new Instruction(INUMBER, this.current.value)) + } else if(this.accept(TPAREN, "(")) { + this.parseExpression(instr) + this.expect(TPAREN, ")") + } else if(this.accept(TBRACKET, "[")) { + if(this.accept(TBRACKET, "]")) { + instr.push(new Instruction(IARRAY, 0)) + } else { + const argCount = this.parseArrayList(instr) + instr.push(new Instruction(IARRAY, argCount)) + } + } else { + throw new Error(qsTranslate("error", "Unexpected %1").arg(this.nextToken)) + } + } + + /** + * Consumes the next tokens to compile a general expression which should return a value, and compiles + * the instructions into the list. + * @param {Instruction[]} instr + */ + parseExpression(instr) { + const exprInstr = [] + this.parseConditionalExpression(exprInstr) + instr.push(...exprInstr) + } + + /** + * Parses an array indice, and return the number of arguments found at the end. + * @param {Instruction[]} instr + * @return {number} + */ + parseArrayList(instr) { + let argCount = 0 + + while(!this.accept(TBRACKET, "]")) { + this.parseExpression(instr) + ++argCount + while(this.accept(TCOMMA)) { + this.parseExpression(instr) + ++argCount + } + } + + return argCount + } + + /** + * Parses a tertiary statement ( ? : ) and pushes it into the instruction + * list. + * @param {Instruction[]} instr + */ + parseConditionalExpression(instr) { + this.parseOrExpression(instr) + while(this.accept(TOP, "?")) { + const trueBranch = [] + const falseBranch = [] + this.parseConditionalExpression(trueBranch) + this.expect(TOP, ":") + this.parseConditionalExpression(falseBranch) + instr.push(new Instruction(IEXPR, trueBranch)) + instr.push(new Instruction(IEXPR, falseBranch)) + instr.push(ternaryInstruction("?")) + } + } + + /** + * Parses a binary or statement ( or ) and pushes it into the instruction list. + * @param {Instruction[]} instr + */ + parseOrExpression(instr) { + this.parseAndExpression(instr) + while(this.accept(TOP, "or")) { + const falseBranch = [] + this.parseAndExpression(falseBranch) + instr.push(new Instruction(IEXPR, falseBranch)) + instr.push(binaryInstruction("or")) + } + } + + /** + * Parses a binary and statement ( and ) and pushes it into the instruction list. + * @param {Instruction[]} instr + */ + parseAndExpression(instr) { + this.parseComparison(instr) + while(this.accept(TOP, "and")) { + const trueBranch = [] + this.parseComparison(trueBranch) + instr.push(new Instruction(IEXPR, trueBranch)) + instr.push(binaryInstruction("and")) + } + } + + /** + * Parses a binary equality statement ( == and so on) and pushes it into the instruction list. + * @param {Instruction[]} instr + */ + parseComparison(instr) { + this.parseAddSub(instr) + while(this.accept(TOP, COMPARISON_OPERATORS)) { + const op = this.current + this.parseAddSub(instr) + instr.push(binaryInstruction(op.value)) + } + } + + /** + * Parses add, minus and concat operations and pushes them into the instruction list. + * @param {Instruction[]} instr + */ + parseAddSub(instr) { + this.parseTerm(instr) + while(this.accept(TOP, ADD_SUB_OPERATORS)) { + const op = this.current + this.parseTerm(instr) + instr.push(binaryInstruction(op.value)) + } + } + + /** + * Parses times, divide and modulo operations and pushes them into the instruction list. + * @param {Instruction[]} instr + */ + parseTerm(instr) { + this.parseFactor(instr) + while(this.accept(TOP, TERM_OPERATORS)) { + const op = this.current + this.parseFactor(instr) + instr.push(binaryInstruction(op.value)) + } + } + + /** + * Parses prefix operations (+, -, but also functions like sin or cos which don't need parentheses) + * @param {Instruction[]} instr + */ + parseFactor(instr) { + const prefixOperators = this.tokens.unaryOpsList + + this.save() + if(this.accept(TOP, prefixOperators)) { + if(this.current.value !== "-" && this.current.value !== "+") { + if(this.nextToken.type === TPAREN && this.nextToken.value === "(") { + this.restore() + this.parseExponential(instr) + return + } else if(this.nextToken.type === TCOMMA || this.nextToken.type === TEOF || (this.nextToken.type === TPAREN && this.nextToken.value === ")")) { + this.restore() + this.parseAtom(instr) + return + } + } + + const op = this.current + this.parseFactor(instr) + instr.push(unaryInstruction(op.value)) + } else { + this.parseExponential(instr) + } + } + + /** + * + * @param {Instruction[]} instr + */ + parseExponential(instr) { + this.parsePostfixExpression(instr) + while(this.accept(TOP, "^")) { + this.parseFactor(instr) + instr.push(binaryInstruction("^")) + } + } + + + /** + * Parses factorial '!' (after the expression to apply it to). + * @param {Instruction[]} instr + */ + parsePostfixExpression(instr) { + this.parseFunctionCall(instr) + while(this.accept(TOP, "!")) { + instr.push(unaryInstruction("!")) + } + } + + /** + * Parse a function (name + parentheses + arguments). + * @param {Instruction[]} instr + */ + parseFunctionCall(instr) { + const prefixOperators = this.tokens.unaryOpsList + + if(this.accept(TOP, prefixOperators)) { + const op = this.current + this.parseAtom(instr) + instr.push(unaryInstruction(op.value)) + } else { + this.parseMemberExpression(instr) + while(this.accept(TPAREN, "(")) { + if(this.accept(TPAREN, ")")) { + instr.push(new Instruction(IFUNCALL, 0)) + } else { + const argCount = this.parseArgumentList(instr) + instr.push(new Instruction(IFUNCALL, argCount)) + } + } + } + } + + /** + * Parses a list of arguments, return their quantity. + * @param {Instruction[]} instr + * @return {number} + */ + parseArgumentList(instr) { + let argCount = 0 + + while(!this.accept(TPAREN, ")")) { + this.parseExpression(instr) + ++argCount + while(this.accept(TCOMMA)) { + this.parseExpression(instr) + ++argCount + } + } + + return argCount + } + + parseMemberExpression(instr) { + this.parseAtom(instr) + while(this.accept(TOP, ".") || this.accept(TBRACKET, "[")) { + const op = this.current + + if(op.value === ".") { + if(!this.allowMemberAccess) { + throw new Error(qsTranslate("error", "Unexpected \".\": member access is not permitted")) + } + + this.expect(TNAME) + instr.push(new Instruction(IMEMBER, this.current.value)) + } else if(op.value === "[") { + if(!this.tokens.isOperatorEnabled("[")) { + throw new Error(qsTranslate("error", "Unexpected \"[]\": arrays are disabled.")) + } + + this.parseExpression(instr) + this.expect(TBRACKET, "]") + instr.push(binaryInstruction("[")) + } else { + throw new Error(qsTranslate("error", "Unexpected symbol: %1.").arg(op.value)) + } + } + } +} diff --git a/common/src/lib/expr-eval/polyfill.mjs b/common/src/lib/expr-eval/polyfill.mjs new file mode 100644 index 0000000..65f05c7 --- /dev/null +++ b/common/src/lib/expr-eval/polyfill.mjs @@ -0,0 +1,313 @@ +/** + * Based on ndef.parser, by Raphael Graf + * http://www.undefined.ch/mparser/index.html + * + * Ported to JavaScript and modified by Matthew Crumley + * https://silentmatt.com/javascript-expression-evaluator/ + * + * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu) + * + * Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code + * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, + * but don't feel like you have to let me know or ask permission. + */ + +export function add(a, b) { + return Number(a) + Number(b) +} + +export function sub(a, b) { + return a - b +} + +export function mul(a, b) { + return a * b +} + +export function div(a, b) { + return a / b +} + +export function mod(a, b) { + return a % b +} + +export function concat(a, b) { + if(Array.isArray(a) && Array.isArray(b)) { + return a.concat(b) + } + return "" + a + b +} + +export function equal(a, b) { + return a === b +} + +export function notEqual(a, b) { + return a !== b +} + +export function greaterThan(a, b) { + return a > b +} + +export function lessThan(a, b) { + return a < b +} + +export function greaterThanEqual(a, b) { + return a >= b +} + +export function lessThanEqual(a, b) { + return a <= b +} + +export function andOperator(a, b) { + return Boolean(a && b) +} + +export function orOperator(a, b) { + return Boolean(a || b) +} + +export function inOperator(a, b) { + return b.includes(a) +} + +export function sinh(a) { + return ((Math.exp(a) - Math.exp(-a)) / 2) +} + +export function cosh(a) { + return ((Math.exp(a) + Math.exp(-a)) / 2) +} + +export function tanh(a) { + if(a === Infinity) return 1 + if(a === -Infinity) return -1 + return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a)) +} + +export function asinh(a) { + if(a === -Infinity) return a + return Math.log(a + Math.sqrt((a * a) + 1)) +} + +export function acosh(a) { + return Math.log(a + Math.sqrt((a * a) - 1)) +} + +export function atanh(a) { + return (Math.log((1 + a) / (1 - a)) / 2) +} + +export function log10(a) { + return Math.log(a) * Math.LOG10E +} + +export function neg(a) { + return -a +} + +export function not(a) { + return !a +} + +export function trunc(a) { + return a < 0 ? Math.ceil(a) : Math.floor(a) +} + +export function random(a) { + return Math.random() * (a || 1) +} + +export function factorial(a) { // a! + return gamma(a + 1) +} + +export function isInteger(value) { + return isFinite(value) && (value === Math.round(value)) +} + +const GAMMA_G = 4.7421875 +const GAMMA_P = [ + 0.99999999999999709182, + 57.156235665862923517, -59.597960355475491248, + 14.136097974741747174, -0.49191381609762019978, + 0.33994649984811888699e-4, + 0.46523628927048575665e-4, -0.98374475304879564677e-4, + 0.15808870322491248884e-3, -0.21026444172410488319e-3, + 0.21743961811521264320e-3, -0.16431810653676389022e-3, + 0.84418223983852743293e-4, -0.26190838401581408670e-4, + 0.36899182659531622704e-5 +] + +// Gamma function from math.js +export function gamma(n) { + let t, x + + if(isInteger(n)) { + if(n <= 0) { + return isFinite(n) ? Infinity : NaN + } + + if(n > 171) { + return Infinity // Will overflow + } + + let value = n - 2 + let res = n - 1 + while(value > 1) { + res *= value + value-- + } + + if(res === 0) { + res = 1 // 0! is per definition 1 + } + + return res + } + + if(n < 0.5) { + return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n)) + } + + if(n >= 171.35) { + return Infinity // will overflow + } + + if(n > 85.0) { // Extended Stirling Approx + const twoN = n * n + const threeN = twoN * n + const fourN = threeN * n + const fiveN = fourN * n + return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) * + (1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) - + (571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) + + (5246819 / (75246796800 * fiveN * n))) + } + + --n + x = GAMMA_P[0] + for(let i = 1; i < GAMMA_P.length; ++i) { + x += GAMMA_P[i] / (n + i) + } + + t = n + GAMMA_G + 0.5 + return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x +} + +export function stringOrArrayLength(s) { + if(Array.isArray(s)) + return s.length + return String(s).length +} + +export function hypot() { + let sum = 0 + let larg = 0 + for(let i = 0; i < arguments.length; i++) { + const arg = Math.abs(arguments[i]) + let div + if(larg < arg) { + div = larg / arg + sum = (sum * div * div) + 1 + larg = arg + } else if(arg > 0) { + div = arg / larg + sum += div * div + } else { + sum += arg + } + } + return larg === Infinity ? Infinity : larg * Math.sqrt(sum) +} + +export function condition(cond, yep, nope) { + return cond ? yep : nope +} + +/** + * Decimal adjustment of a number. + * From @escopecz. + * + * @param {number} value - The number. + * @param {Integer} exp - The exponent (the 10 logarithm of the adjustment base). + * @return {number} - The adjusted value. + */ +export function roundTo(value, exp) { + // If the exp is undefined or zero... + if(typeof exp === "undefined" || +exp === 0) { + return Math.round(value) + } + value = +value + exp = -(+exp) + // If the value is not a number or the exp is not an integer... + if(isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) { + return NaN + } + // Shift + value = value.toString().split("e") + value = Math.round(+(value[0] + "e" + (value[1] ? (+value[1] - exp) : -exp))) + // Shift back + value = value.toString().split("e") + return +(value[0] + "e" + (value[1] ? (+value[1] + exp) : exp)) +} + +export function arrayIndex(array, index) { + return array[index | 0] +} + +export function max(array) { + if(arguments.length === 1 && Array.isArray(array)) { + return Math.max.apply(Math, array) + } else if(arguments.length >= 1) { + return Math.max.apply(Math, arguments) + } else { + throw new EvalError(qsTranslate("error", "Function %1 must have at least one argument.").arg("max")) + } +} + +export function min(array) { + if(arguments.length === 1 && Array.isArray(array)) { + return Math.min.apply(Math, array) + } else if(arguments.length >= 1) { + return Math.min.apply(Math, arguments) + } else { + throw new EvalError(qsTranslate("error", "Function %1 must have at least one argument.").arg("min")) + } +} + +export function sign(x) { + return ((x > 0) - (x < 0)) || +x +} + +const ONE_THIRD = 1 / 3 + +export function cbrt(x) { + return x < 0 ? -Math.pow(-x, ONE_THIRD) : Math.pow(x, ONE_THIRD) +} + +export function expm1(x) { + return Math.exp(x) - 1 +} + +export function log1p(x) { + return Math.log(1 + x) +} + +export function log2(x) { + return Math.log(x) / Math.LN2 +} \ No newline at end of file diff --git a/common/src/lib/expr-eval/tokens.mjs b/common/src/lib/expr-eval/tokens.mjs new file mode 100644 index 0000000..254d30a --- /dev/null +++ b/common/src/lib/expr-eval/tokens.mjs @@ -0,0 +1,575 @@ +/** + * Based on ndef.parser, by Raphael Graf + * http://www.undefined.ch/mparser/index.html + * + * Ported to JavaScript and modified by Matthew Crumley + * https://silentmatt.com/javascript-expression-evaluator/ + * + * Ported to QMLJS with modifications done accordingly done by Ad5001 (https://ad5001.eu) + * + * Copyright (c) 2015 Matthew Crumley, 2021-2025 Ad5001 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * You are free to use and modify this code in anyway you find useful. Please leave this comment in the code + * to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, + * but don't feel like you have to let me know or ask permission. + */ + +export const TEOF = "TEOF" +export const TOP = "TOP" +export const TNUMBER = "TNUMBER" +export const TSTRING = "TSTRING" +export const TPAREN = "TPAREN" +export const TBRACKET = "TBRACKET" +export const TCOMMA = "TCOMMA" +export const TNAME = "TNAME" + + +// Additional variable characters. +export const ADDITIONAL_VARCHARS = [ + "α", "β", "γ", "δ", "ε", "ζ", "η", + "π", "θ", "κ", "λ", "μ", "ξ", "ρ", + "ς", "σ", "τ", "φ", "χ", "ψ", "ω", + "Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ", + "Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ", + "ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ", + "ₜ", "¹", "²", "³", "⁴", "⁵", "⁶", + "⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃", + "₄", "₅", "₆", "₇", "₈", "₉", "₀", + "∞", "π" +] + +export class Token { + /** + * + * @param {string} type - Type of the token (see above). + * @param {any} value - Value of the token. + * @param {number} index - Index in the string of the token. + */ + constructor(type, value, index) { + this.type = type + this.value = value + this.index = index + } + + toString() { + return this.type + ": " + this.value + } +} + +const unicodeCodePointPattern = /^[0-9a-f]{4}$/i + +export class TokenStream { + /** + * + * @param {Parser} parser + * @param {string} expression + */ + constructor(parser, expression) { + this.pos = 0 + this.current = null + this.unaryOps = parser.unaryOps + this.unaryOpsList = parser.unaryOpsList + this.binaryOps = parser.binaryOps + this.ternaryOps = parser.ternaryOps + this.builtinConsts = parser.builtinConsts + this.expression = expression + this.savedPosition = 0 + this.savedCurrent = null + this.options = parser.options + this.parser = parser + } + + /** + * + * @param {string} type - Type of the token (see above). + * @param {any} value - Value of the token. + * @param {number} [pos] - Index in the string of the token. + */ + newToken(type, value, pos) { + return new Token(type, value, pos != null ? pos : this.pos) + } + + /** + * Saves the current position and token into the object. + */ + save() { + this.savedPosition = this.pos + this.savedCurrent = this.current + } + + + /** + * Restored the saved position and token into the current. + */ + restore() { + this.pos = this.savedPosition + this.current = this.savedCurrent + } + + /** + * Consumes the character at the current position and advance it + * until it makes a valid token, and returns it. + * @returns {Token} + */ + next() { + if(this.pos >= this.expression.length) { + return this.newToken(TEOF, "EOF") + } + + if(this.isWhitespace()) { + return this.next() + } else if(this.isRadixInteger() || + this.isNumber() || + this.isOperator() || + this.isString() || + this.isParen() || + this.isBracket() || + this.isComma() || + this.isNamedOp() || + this.isConst() || + this.isName()) { + return this.current + } else { + this.parseError(qsTranslate("error", "Unknown character \"%1\".").arg(this.expression.charAt(this.pos))) + } + } + + /** + * Checks if the character at the current position starts a string, and if so, consumes it as the current token + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isString() { + const startPos = this.pos + const quote = this.expression.charAt(startPos) + let r = false + + if(quote === "'" || quote === "\"") { + let index = this.expression.indexOf(quote, startPos + 1) + while(index >= 0 && this.pos < this.expression.length) { + this.pos = index + 1 + if(this.expression.charAt(index - 1) !== "\\") { + const rawString = this.expression.substring(startPos + 1, index) + this.current = this.newToken(TSTRING, this.unescape(rawString), startPos) + r = true + break + } + index = this.expression.indexOf(quote, index + 1) + } + } + return r + } + + /** + * Checks if the character at the current pos is a parenthesis, and if so consumes it into current + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isParen() { + const c = this.expression.charAt(this.pos) + if(c === "(" || c === ")") { + this.current = this.newToken(TPAREN, c) + this.pos++ + return true + } + return false + } + + /** + * Checks if the character at the current pos is a bracket, and if so consumes it into current + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isBracket() { + const c = this.expression.charAt(this.pos) + if((c === "[" || c === "]") && this.isOperatorEnabled("[")) { + this.current = this.newToken(TBRACKET, c) + this.pos++ + return true + } + return false + } + + /** + * Checks if the character at the current pos is a comma, and if so consumes it into current + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isComma() { + const c = this.expression.charAt(this.pos) + if(c === ",") { + this.current = this.newToken(TCOMMA, ",") + this.pos++ + return true + } + return false + } + + /** + * Checks if the current character is an identifier and makes a const, and if so, consumes it as the current token + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isConst() { + const startPos = this.pos + let i = startPos + for(; i < this.expression.length; i++) { + const c = this.expression.charAt(i) + if(c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) { + if(i === this.pos || (c !== "_" && c !== "." && (c < "0" || c > "9"))) { + break + } + } + } + if(i > startPos) { + const str = this.expression.substring(startPos, i) + if(str in this.builtinConsts) { + this.current = this.newToken(TNUMBER, this.builtinConsts[str]) + this.pos += str.length + return true + } + } + return false + } + + /** + * Checks if the current character is an identifier and makes a function or an operator, and if so, consumes it as the current token + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isNamedOp() { + const startPos = this.pos + let i = startPos + for(; i < this.expression.length; i++) { + const c = this.expression.charAt(i) + if(c.toUpperCase() === c.toLowerCase()) { + if(i === this.pos || (c !== "_" && (c < "0" || c > "9"))) { + break + } + } + } + if(i > startPos) { + const str = this.expression.substring(startPos, i) + if(this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) { + this.current = this.newToken(TOP, str) + this.pos += str.length + return true + } + } + return false + } + + /** + * Checks if the current character is an identifier and makes a variable, and if so, consumes it as the current token + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isName() { + const startPos = this.pos + let i = startPos + let hasLetter = false + for(; i < this.expression.length; i++) { + const c = this.expression.charAt(i) + if(c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) { + if(i === this.pos && (c === "$" || c === "_")) { + if(c === "_") { + hasLetter = true + } + } else if(i === this.pos || !hasLetter || (c !== "_" && (c < "0" || c > "9"))) { + break + } + } else { + hasLetter = true + } + } + if(hasLetter) { + const str = this.expression.substring(startPos, i) + this.current = this.newToken(TNAME, str) + this.pos += str.length + return true + } + return false + } + + /** + * Checks if the character at the current position is a whitespace, and if so, consumes all consecutive whitespaces + * and returns true. Otherwise, returns false. + * @returns {boolean} + * + */ + isWhitespace() { + let r = false + let c = this.expression.charAt(this.pos) + while(c === " " || c === "\t" || c === "\n" || c === "\r") { + r = true + this.pos++ + if(this.pos >= this.expression.length) { + break + } + c = this.expression.charAt(this.pos) + } + return r + } + + /** + * Checks if the current character is a zero, and checks whether it forms a radix number, and if so, consumes it as the current token + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isRadixInteger() { + let pos = this.pos + + if(pos >= this.expression.length - 2 || this.expression.charAt(pos) !== "0") { + return false + } + ++pos + + let radix + let validDigit + if(this.expression.charAt(pos) === "x") { + radix = 16 + validDigit = /^[0-9a-f]$/i + pos++ + } else if(this.expression.charAt(pos) === "b") { + radix = 2 + validDigit = /^[01]$/i + pos++ + } else { + return false + } + + let valid = false + const startPos = pos + + while(pos < this.expression.length) { + const c = this.expression.charAt(pos) + if(validDigit.test(c)) { + pos++ + valid = true + } else { + break + } + } + + if(valid) { + this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix)) + this.pos = pos + } + return valid + } + + /** + * Checks if the current character is a digit, and checks whether it forms a number, and if so, consumes it as the current token + * and returns true. Otherwise, returns false. + * @returns {boolean} + */ + isNumber() { + const startPos = this.pos + let valid = false + let pos = startPos + let resetPos = startPos + let foundDot = false + let foundDigits = false + let c + + // Check for digit with dot. + while(pos < this.expression.length) { + c = this.expression.charAt(pos) + if((c >= "0" && c <= "9") || (!foundDot && c === ".")) { + if(c === ".") { + foundDot = true + } else { + foundDigits = true + } + pos++ + valid = foundDigits + } else { + break + } + } + + if(valid) { + resetPos = pos + } + + // Check for e exponents. + if(c === "e" || c === "E") { + pos++ + let acceptSign = true + let validExponent = false + while(pos < this.expression.length) { + c = this.expression.charAt(pos) + if(acceptSign && (c === "+" || c === "-")) { + acceptSign = false + } else if(c >= "0" && c <= "9") { + validExponent = true + acceptSign = false + } else { + break + } + pos++ + } + + if(!validExponent) { + pos = resetPos + } + } + + // Use parseFloat now that we've identified the number. + if(valid) { + this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos))) + this.pos = pos + } else { + this.pos = resetPos + } + return valid + } + + /** + * Checks if the current character is an operator, checks whether it's enabled and if so, consumes it as the current token + * and returns true. Otherwise, returns false. + * @return {boolean} + */ + isOperator() { + const startPos = this.pos + const c = this.expression.charAt(this.pos) + + if(c === "+" || c === "-" || c === "*" || c === "/" || c === "%" || c === "^" || c === "?" || c === ":" || c === ".") { + this.current = this.newToken(TOP, c) + } else if(c === "∙" || c === "•") { + this.current = this.newToken(TOP, "*") + } else if(c === ">") { + if(this.expression.charAt(this.pos + 1) === "=") { + this.current = this.newToken(TOP, ">=") + this.pos++ + } else { + this.current = this.newToken(TOP, ">") + } + } else if(c === "<") { + if(this.expression.charAt(this.pos + 1) === "=") { + this.current = this.newToken(TOP, "<=") + this.pos++ + } else { + this.current = this.newToken(TOP, "<") + } + } else if(c === "|") { + if(this.expression.charAt(this.pos + 1) === "|") { + this.current = this.newToken(TOP, "||") + this.pos++ + } else { + return false + } + } else if(c === "=") { + if(this.expression.charAt(this.pos + 1) === "=") { + this.current = this.newToken(TOP, "==") + this.pos++ + } else { + return false + } + } else if(c === "!") { + if(this.expression.charAt(this.pos + 1) === "=") { + this.current = this.newToken(TOP, "!=") + this.pos++ + } else { + this.current = this.newToken(TOP, c) + } + } else { + return false + } + this.pos++ + + if(this.isOperatorEnabled(this.current.value)) { + return true + } else { + this.pos = startPos + return false + } + } + + /** + * Replaces a backslash and a character by its unescaped value. + * @param {string} v - string to un escape. + */ + unescape(v) { + let index = v.indexOf("\\") + if(index < 0) { + return v + } + + let buffer = v.substring(0, index) + while(index >= 0) { + const c = v.charAt(++index) + switch(c) { + case "'": + buffer += "'" + break + case "\"": + buffer += "\"" + break + case "\\": + buffer += "\\" + break + case "/": + buffer += "/" + break + case "b": + buffer += "\b" + break + case "f": + buffer += "\f" + break + case "n": + buffer += "\n" + break + case "r": + buffer += "\r" + break + case "t": + buffer += "\t" + break + case "u": + // interpret the following 4 characters as the hex of the unicode code point + const codePoint = v.substring(index + 1, index + 5) + if(!unicodeCodePointPattern.test(codePoint)) { + this.parseError(qsTranslate("error", "Illegal escape sequence: %1.").arg("\\u" + codePoint)) + } + buffer += String.fromCharCode(parseInt(codePoint, 16)) + index += 4 + break + default: + throw this.parseError(qsTranslate("error", "Illegal escape sequence: %1.").arg("\\" + c)) + } + ++index + const backslash = v.indexOf("\\", index) + buffer += v.substring(index, backslash < 0 ? v.length : backslash) + index = backslash + } + + return buffer + } + + /** + * Shorthand for the parser's method to check if an operator is enabled. + * @param {string} op + * @return {boolean} + */ + isOperatorEnabled(op) { + return this.parser.isOperatorEnabled(op) + } + + /** + * Throws a translated error. + * @param {string} msg + */ + parseError(msg) { + throw new Error(qsTranslate("error", "Parse error [position %1]: %2").arg(this.pos).arg(msg)) + } +} diff --git a/common/src/lib/polyfills/js.mjs b/common/src/lib/polyfills/js.mjs new file mode 100644 index 0000000..75d149c --- /dev/null +++ b/common/src/lib/polyfills/js.mjs @@ -0,0 +1,136 @@ +/*! + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// JS polyfills to add because they're not implemented in the QML Scripting engine. +// CoreJS does not work well with it (as well as doubles the compiled size), so this is preferable. +function notPolyfilled(name) { + return function() { throw new Error(`${name} not polyfilled`) } +} + +/** + * @param {number} depth + * @this {Array} + * @returns {Array} + */ +function arrayFlat(depth = 1) { + const newArray = [] + for(const element of this) { + if(element instanceof Array) + newArray.push(...(depth > 1 ? element.flat(depth - 1) : element)) + else + newArray.push(element) + } + return newArray +} + +/** + * @param {function(any, number, Array): any} callbackFn + * @param {object} thisArg + * @this {Array} + * @returns {Array} + */ +function arrayFlatMap(callbackFn, thisArg) { + const newArray = [] + for(let i = 0; i < this.length; i++) { + const value = callbackFn.call(thisArg ?? this, this[i], i, this) + if(value instanceof Array) + newArray.push(...value) + else + newArray.push(value) + } + return newArray +} + +/** + * Replaces all instances of from by to. + * @param {string} from + * @param {string} to + * @this {string} + * @return {String} + */ +function stringReplaceAll(from, to) { + return this.split(from).join(to) +} + +/** + * Returns the value of an element of the array at a given index. + * Accepts negative indexes. + * @this {Array|string} + * @param {number} index + * @return {*} + */ +function arrayAt(index) { + if(typeof index !== "number") + throw new Error(`${index} is not a number`) + return index >= 0 ? this[index] : this[this.length + index] +} + + +const polyfills = { + 2017: [ + [Object, "entries", notPolyfilled("Object.entries")], + [Object, "values", notPolyfilled("Object.values")], + [Object, "getOwnPropertyDescriptors", notPolyfilled("Object.getOwnPropertyDescriptors")], + [String.prototype, "padStart", notPolyfilled("String.prototype.padStart")], + [String.prototype, "padEnd", notPolyfilled("String.prototype.padEnd")] + ], + 2018: [ + [Promise.prototype, "finally", notPolyfilled("Object.entries")] + ], + 2019: [ + [String.prototype, "trimStart", notPolyfilled("String.prototype.trimStart")], + [String.prototype, "trimEnd", notPolyfilled("String.prototype.trimEnd")], + [Object, "fromEntries", notPolyfilled("Object.fromEntries")], + [Array.prototype, "flat", arrayFlat], + [Array.prototype, "flatMap", arrayFlatMap] + ], + 2020: [ + [String.prototype, "matchAll", notPolyfilled("String.prototype.matchAll")], + [Promise, "allSettled", notPolyfilled("Promise.allSettled")] + ], + 2021: [ + [Promise, "any", notPolyfilled("Promise.any")], + [String.prototype, "replaceAll", stringReplaceAll] + ], + 2022: [ + [Array.prototype, "at", arrayAt], + [String.prototype, "at", arrayAt], + [Object, "hasOwn", notPolyfilled("Object.hasOwn")] + ], + 2023: [ + [Array.prototype, "findLast", notPolyfilled("Array.prototype.findLast")], + [Array.prototype, "toReversed", notPolyfilled("Array.prototype.toReversed")], + [Array.prototype, "toSorted", notPolyfilled("Array.prototype.toSorted")], + [Array.prototype, "toSpliced", notPolyfilled("Array.prototype.toSpliced")], + [Array.prototype, "with", notPolyfilled("Array.prototype.with")] + ], + 2025: [ + [Object, "groupBy", notPolyfilled("Object.groupBy")], + [Map, "groupBy", notPolyfilled("Map.groupBy")] + ] +} + +// Fulfill polyfill. +for(const [year, entries] of Object.entries(polyfills)) { + const defined = entries.filter(x => x[0][x[1]] !== undefined) + console.info(`ES${year} support: ${defined.length === entries.length} (${defined.length}/${entries.length})`) + // Apply polyfills + for(const [context, functionName, polyfill] of entries.filter(x => x[0][x[1]] === undefined)) { + context[functionName] = polyfill + } +} diff --git a/common/src/lib/polyfills/qt.mjs b/common/src/lib/polyfills/qt.mjs new file mode 100644 index 0000000..0e9a9b5 --- /dev/null +++ b/common/src/lib/polyfills/qt.mjs @@ -0,0 +1,46 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Type polyfills for IDEs. +// Never directly imported. +// Might need to be reimplemented in other implemententations. + +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(category, 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}; + } +} \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/domain.js b/common/src/math/domain.mjs similarity index 65% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/domain.js rename to common/src/math/domain.mjs index 6c47a23..b81e527 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/domain.js +++ b/common/src/math/domain.mjs @@ -1,59 +1,67 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library - -.import "expression.js" as Expr +import { Expression, executeExpression } from "./expression.mjs" /** * Main abstract domain class * It doesn't represent any kind of domain and is meant to be extended. */ -class Domain { - constructor() {} - +export class Domain { + constructor() { + this.latexMarkup = "#INVALID" + } + /** * Checks whether x is included in the domain. * @param {number} x - The x value. - * @return {bool} true if included, false otherwise. + * @return {boolean} true if included, false otherwise. */ - includes(x) { return false } - + includes(x) { + return false + } + /** * Returns a string representation of the domain. * @return {string} String representation of the domain. */ - toString() { return '???' } - + toString() { + return "???" + } + /** * Returns a new domain that is the union between this domain and another. * @param {Domain} domain - Domain to unionise with this. * @return {Domain} newly created domain. */ - union(domain) { return domain } - + union(domain) { + return domain + } + /** * Returns a new domain that is the intersection between this domain and another. * @param {Domain} domain - Domain to get the interscection with this. * @return {Domain} newly created domain. */ - intersection(domain) { return this } - + intersection(domain) { + return this + } + /** * Imports a domain from a string. * @return {Domain} Found domain, string otherwise. @@ -63,24 +71,20 @@ class Domain { case "R": case "ℝ": return Domain.R - break; case "RE": case "R*": case "ℝ*": return Domain.RE - break; case "RP": case "R+": case "ℝ⁺": case "ℝ+": return Domain.RP - break; case "RM": case "R-": case "ℝ⁻": case "ℝ-": return Domain.RM - break; case "RPE": case "REP": case "R+*": @@ -90,7 +94,6 @@ class Domain { case "ℝ*+": case "ℝ+*": return Domain.RPE - break; case "RME": case "REM": case "R-*": @@ -100,7 +103,6 @@ class Domain { case "ℝ-*": case "ℝ*-": return Domain.RME - break; case "ℕ": case "N": case "ZP": @@ -108,12 +110,10 @@ class Domain { case "ℤ⁺": case "ℤ+": return Domain.N - break; case "NLOG": case "ℕˡᵒᵍ": case "ℕLOG": return Domain.NLog - break; case "NE": case "NP": case "N*": @@ -130,17 +130,14 @@ class Domain { case "ℤ+*": case "ℤ*+": return Domain.NE - break; case "Z": case "ℤ": return Domain.Z - break; case "ZM": case "Z-": case "ℤ⁻": case "ℤ-": return Domain.ZM - break; case "ZME": case "ZEM": case "Z-*": @@ -150,15 +147,12 @@ class Domain { case "ℤ-*": case "ℤ*-": return Domain.ZME - break; case "ZE": case "Z*": case "ℤ*": return Domain.ZE - break; default: - return new EmptySet() - break; + return Domain.EmptySet } } } @@ -166,50 +160,63 @@ class Domain { /** * Represents an empty set. */ -class EmptySet extends Domain { +export class EmptySet extends Domain { constructor() { super() this.displayName = "∅" this.latexMarkup = "\\emptyset" } - - includes(x) { return false } - - toString() { return this.displayName } - - union(domain) { return domain } - - intersection(domain) { return this } - - static import(frm) { return new EmptySet() } + + includes(x) { + return false + } + + toString() { + return this.displayName + } + + union(domain) { + return domain + } + + intersection(domain) { + return this + } + + static import(frm) { + return new EmptySet() + } } +Domain.EmptySet = new EmptySet() // To prevent use prior to declaration. + /** * Domain classes for ranges (e.g ]0;3[, [1;2[ ...) */ -class Range extends Domain { +export class Range extends Domain { constructor(begin, end, openBegin, openEnd) { super() - if(typeof begin == 'number' || typeof begin == 'string') begin = new Expr.Expression(begin.toString()) + if(typeof begin == "number" || typeof begin == "string") begin = new Expression(begin.toString()) this.begin = begin - if(typeof end == 'number' || typeof end == 'string') end = new Expr.Expression(end.toString()) + if(typeof end == "number" || typeof end == "string") end = new Expression(end.toString()) this.end = end this.openBegin = openBegin this.openEnd = openEnd this.displayName = (openBegin ? "]" : "[") + begin.toString() + ";" + end.toString() + (openEnd ? "[" : "]") this.latexMarkup = `\\mathopen${openBegin ? "]" : "["}${this.begin.latexMarkup};${this.end.latexMarkup}\\mathclose${openEnd ? "[" : "]"}` } - + includes(x) { - if(typeof x == 'string') x = Expr.executeExpression(x) + if(typeof x == "string") x = executeExpression(x) + if(x instanceof Expression) x = x.execute() return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) && ((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute())) } - + toString() { return this.displayName } - + union(domain) { if(domain instanceof EmptySet) return this if(domain instanceof DomainSet) return domain.union(this) @@ -218,7 +225,7 @@ class Range extends Domain { if(domain instanceof MinusDomain) return new UnionDomain(this, domain) if(domain instanceof Range) return new UnionDomain(this, domain) } - + intersection(domain) { if(domain instanceof EmptySet) return domain if(domain instanceof DomainSet) return domain.intersection(this) @@ -227,11 +234,11 @@ class Range extends Domain { if(domain instanceof MinusDomain) return new IntersectionDomain(this, domain) if(domain instanceof Range) return new IntersectionDomain(this, domain) } - + static import(frm) { - var openBegin = frm.trim().charAt(0) == "]" - var openEnd = frm.trim().charAt(frm.length -1) == "[" - var [begin, end] = frm.substr(1, frm.length-2).split(";") + let openBegin = frm.trim().charAt(0) === "]" + let openEnd = frm.trim().charAt(frm.length - 1) === "[" + let [begin, end] = frm.substring(1, frm.length - 1).split(";") return new Range(begin.trim(), end.trim(), openBegin, openEnd) } } @@ -239,45 +246,49 @@ class Range extends Domain { /** * Domain classes for special domains (N, Z, ...) */ -class SpecialDomain extends Domain { +export class SpecialDomain extends Domain { /** * @constructs SpecialDomain * @param {string} displayName + * @param {string} latexMarkup - markup representing the domain. * @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 {bool} moveSupported - Only true if next and previous functions are valid. - * @param items + * @param {boolean} moveSupported - Only true if next and previous functions are valid. */ - constructor(displayName, isValid, next = x => true, previous = x => true, - moveSupported = true) { + constructor(displayName, latexMarkup, isValid, next = () => true, previous = () => true, + moveSupported = true) { super() this.displayName = displayName + this.latexMarkup = latexMarkup this.isValid = isValid this.nextValue = next this.prevValue = previous this.moveSupported = moveSupported } - + includes(x) { - if(typeof x == 'string') x = Expr.executeExpression(x) + if(x instanceof Expression) x = x.execute() + if(typeof x == "string") x = executeExpression(x) return this.isValid(x) } - + next(x) { - if(typeof x == 'string') x = Expr.executeExpression(x) + if(x instanceof Expression) x = x.execute() + if(typeof x == "string") x = executeExpression(x) return this.nextValue(x) } - + previous(x) { - if(typeof x == 'string') x = Expr.executeExpression(x) + if(x instanceof Expression) x = x.execute() + if(typeof x == "string") x = executeExpression(x) return this.prevValue(x) } - + toString() { return this.displayName } - + union(domain) { if(domain instanceof EmptySet) return this if(domain instanceof DomainSet) return domain.union(this) @@ -286,7 +297,7 @@ class SpecialDomain extends Domain { if(domain instanceof MinusDomain) return new UnionDomain(this, domain) if(domain instanceof Range) return new UnionDomain(this, domain) } - + intersection(domain) { if(domain instanceof EmptySet) return domain if(domain instanceof DomainSet) return domain.intersection(this) @@ -300,122 +311,125 @@ class SpecialDomain extends Domain { /** * Domain classes for sets (e.g {0;3}, {0;1;2;pi} ...) */ -class DomainSet extends SpecialDomain { +export class DomainSet extends SpecialDomain { constructor(values) { - super('', x => true, x => x, true) - var newVals = {} + super("", () => true, x => x, true) + let newVals = {} this.executedValues = [] - for(var value of values) { - var expr = new Expr.Expression(value.toString()) - var ex = expr.execute() + for(let value of values) { + let expr = new Expression(value.toString()) + let ex = expr.execute() newVals[ex] = expr this.executedValues.push(ex) } - this.executedValues.sort((a,b) => a-b) + this.executedValues.sort((a, b) => a - b) this.values = this.executedValues.map(val => newVals[val]) this.displayName = "{" + this.values.join(";") + "}" this.latexMarkup = `\\{${this.values.join(";")}\\}` } - + includes(x) { - if(typeof x == 'string') x = Expr.executeExpression(x) - for(var value of this.values) - if(x == value.execute()) return true + if(x instanceof Expression) x = x.execute() + if(typeof x == "string") x = executeExpression(x) + for(let value of this.values) + if(x === value.execute()) return true return false } - + next(x) { - if(typeof x == 'string') x = Expr.executeExpression(x) + if(x instanceof Expression) x = x.execute() + if(typeof x == "string") x = executeExpression(x) if(x < this.executedValues[0]) return this.executedValues[0] - for(var i = 1; i < this.values.length; i++) { - var prevValue = this.executedValues[i-1] - var value = this.executedValues[i] + for(let i = 1; i < this.values.length; i++) { + let prevValue = this.executedValues[i - 1] + let value = this.executedValues[i] if(x >= prevValue && x < value) return value } return null } - + previous(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(var i = 1; i < this.values.length; i++) { - var prevValue = this.executedValues[i-1] - var value = this.executedValues[i] + if(x instanceof Expression) x = x.execute() + if(typeof x == "string") x = 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] if(x > prevValue && x <= value) return prevValue } return null } - + toString() { return this.displayName } - + union(domain) { if(domain instanceof EmptySet) return this if(domain instanceof DomainSet) { - var newValues = [] - var values = this.values.concat(domain.values).filter(function(val){ + let newValues = [] + let 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) } - var notIncludedValues = [] - for(var value in this.values) { - var value = this.executedValues[i] + let notIncludedValues = [] + for(let i = 0; i < this.values.length; i++) { + let 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)) + 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) { - var domValues = domain.values.map(expr => expr.execute()) - this.values = this.values.filter(function(val){ + let domValues = domain.values.map(expr => expr.execute()) + this.values = this.values.filter(function(val) { return domValues.indexOf(val.execute()) >= 0 }) return this } - var includedValues = [] - for(var i in this.values) { - var value = this.executedValues[i] + let includedValues = [] + for(let i in this.values) { + let 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)) + 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)) } - + static import(frm) { - return new DomainSet(frm.substr(1, frm.length-2).split(";")) + return new DomainSet(frm.substring(1, frm.length - 1).split(";")) } } /** * Domain representing the union between two domains. */ -class UnionDomain extends Domain { +export class UnionDomain extends Domain { constructor(dom1, dom2) { super() this.dom1 = dom1 @@ -423,15 +437,15 @@ class UnionDomain extends Domain { this.displayName = this.dom1.toString() + " ∪ " + this.dom2.toString() this.latexMarkup = `${dom1.latexMarkup}\\cup${dom2.latexMarkup}` } - + includes(x) { return this.dom1.includes(x) || this.dom2.includes(x) } - + toString() { return this.displayName } - + union(domain) { if(domain instanceof EmptySet) return this if(domain instanceof DomainSet) return domain.union(this) @@ -440,7 +454,7 @@ class UnionDomain extends Domain { if(domain instanceof IntersectionDomain) return new UnionDomain(this, domain) if(domain instanceof MinusDomain) return new MinusDomain(this, domain) } - + intersection(domain) { if(domain instanceof EmptySet) return domain if(domain instanceof DomainSet) return domain.intersection(this) @@ -448,12 +462,12 @@ class UnionDomain extends Domain { if(domain instanceof IntersectionDomain) return this.dom1.intersection(domain.dom1).intersection(this.dom2).intersection(domain.dom2) if(domain instanceof MinusDomain) return new IntersectionDomain(this, domain) } - + static import(frm) { - var domains = frm.trim().split("∪") - if(domains.length == 1) domains = frm.trim().split("U") // Fallback - var dom2 = parseDomain(domains.pop()) - var dom1 = parseDomain(domains.join('∪')) + 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("∪")) return dom1.union(dom2) } } @@ -461,7 +475,7 @@ class UnionDomain extends Domain { /** * Domain representing the intersection between two domains. */ -class IntersectionDomain extends Domain { +export class IntersectionDomain extends Domain { constructor(dom1, dom2) { super() this.dom1 = dom1 @@ -469,24 +483,24 @@ class IntersectionDomain extends Domain { this.displayName = dom1.toString() + " ∩ " + dom2.toString() this.latexMarkup = `${dom1.latexMarkup}\\cap${dom2.latexMarkup}` } - + includes(x) { return this.dom1.includes(x) && this.dom2.includes(x) } - + toString() { return this.displayName } - + union(domain) { if(domain instanceof EmptySet) return this if(domain instanceof DomainSet) return domain.union(this) if(domain instanceof Range) return domain.union(this) - if(domain instanceof UnionDomain) return this.dom1.union(domain.dom1).union(this.dom2).union(domain.dom2) + if(domain instanceof UnionDomain) return this.dom1.union(domain.dom1).union(this.dom2).union(domain.dom2) if(domain instanceof IntersectionDomain) return new UnionDomain(this, domain) if(domain instanceof MinusDomain) return new MinusDomain(this, domain) } - + intersection(domain) { if(domain instanceof EmptySet) return domain if(domain instanceof DomainSet) return domain.intersection(this) @@ -494,11 +508,11 @@ class IntersectionDomain extends Domain { if(domain instanceof IntersectionDomain) return new IntersectionDomain(this, domain) if(domain instanceof MinusDomain) return new IntersectionDomain(this, domain) } - + static import(frm) { - var domains = frm.trim().split("∩") - var dom1 = parseDomain(domains.pop()) - var dom2 = parseDomain(domains.join('∩')) + let domains = frm.trim().split("∩") + let dom1 = parseDomain(domains.pop()) + let dom2 = parseDomain(domains.join("∩")) return dom1.intersection(dom2) } } @@ -506,7 +520,7 @@ class IntersectionDomain extends Domain { /** * Domain representing the minus between two domains. */ -class MinusDomain extends Domain { +export class MinusDomain extends Domain { constructor(dom1, dom2) { super() this.dom1 = dom1 @@ -514,20 +528,20 @@ class MinusDomain extends Domain { this.displayName = dom1.toString() + "∖" + dom2.toString() this.latexMarkup = `${dom1.latexMarkup}\\setminus${dom2.latexMarkup}` } - + includes(x) { return this.dom1.includes(x) && !this.dom2.includes(x) } - + toString() { return this.displayName } - + static import(frm) { - var domains = frm.trim().split("∖") - if(domains.length == 1) domains = frm.trim().split("\\") // Fallback - var dom1 = parseDomain(domains.shift()) - var dom2 = parseDomain(domains.join('∪')) + let domains = frm.trim().split("∖") + if(domains.length === 1) domains = frm.trim().split("\\") // Fallback + let dom1 = parseDomain(domains.shift()) + let dom2 = parseDomain(domains.join("∪")) return new MinusDomain(dom1, dom2) } } @@ -536,89 +550,104 @@ Domain.RE = new MinusDomain("R", "{0}") Domain.RE.displayName = "ℝ*" Domain.RE.latexMarkup = "\\mathbb{R}^{*}" -Domain.R = new Range(-Infinity,Infinity,true,true) +Domain.R = new Range(-Infinity, Infinity, true, true) Domain.R.displayName = "ℝ" Domain.R.latexMarkup = "\\mathbb{R}" -Domain.RP = new Range(0,Infinity,true,false) +Domain.RP = new Range(0, Infinity, true, false) Domain.RP.displayName = "ℝ⁺" Domain.RP.latexMarkup = "\\mathbb{R}^{+}" -Domain.RM = new Range(-Infinity,0,true,false) +Domain.RM = new Range(-Infinity, 0, true, false) Domain.RM.displayName = "ℝ⁻" Domain.RM.latexMarkup = "\\mathbb{R}^{-}" -Domain.RPE = new Range(0,Infinity,true,true) +Domain.RPE = new Range(0, Infinity, true, true) Domain.RPE.displayName = "ℝ⁺*" Domain.RPE.latexMarkup = "\\mathbb{R}^{+*}" -Domain.RME = new Range(-Infinity,0,true,true) +Domain.RME = new Range(-Infinity, 0, true, true) Domain.RME.displayName = "ℝ⁻*" Domain.RME.latexMarkup = "\\mathbb{R}^{+*}" -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, - 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.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.latexMarkup = "\\mathbb{Z}^{*}" -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, - 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, - function(x) { - var x10pow = Math.pow(10, x.toString().length-1) - return Math.max(1, (Math.floor(x/x10pow)+1)*x10pow) - }, - function(x) { - 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}" +Domain.N = new SpecialDomain( + "ℕ", "\\mathbb{N}", + x => x % 1 === 0 && x >= 0, + x => Math.max(Math.floor(x) + 1, 0), + x => Math.max(Math.ceil(x) - 1, 0) +) +Domain.NE = new SpecialDomain( + "ℕ*", "\\mathbb{N}^{*}", + x => x % 1 === 0 && x > 0, + x => Math.max(Math.floor(x) + 1, 1), + x => Math.max(Math.ceil(x) - 1, 1) +) +Domain.Z = new SpecialDomain( + "ℤ", "\\mathbb{Z}", + x => x % 1 === 0, + x => Math.floor(x) + 1, + x => Math.ceil(x) - 1 +) +Domain.ZE = new SpecialDomain( + "ℤ*", "\\mathbb{Z}^{*}", + 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.ZM = new SpecialDomain( + "ℤ⁻", "\\mathbb{Z}^{-}", + x => x % 1 === 0 && x <= 0, + x => Math.min(Math.floor(x) + 1, 0), + x => Math.min(Math.ceil(x) - 1, 0) +) +Domain.ZME = new SpecialDomain( + "ℤ⁻*", "\\mathbb{Z}^{-*}", + x => x % 1 === 0 && x < 0, + x => Math.min(Math.floor(x) + 1, -1), + x => Math.min(Math.ceil(x) - 1, -1) +) +Domain.NLog = new SpecialDomain( + "ℕˡᵒᵍ", "\\mathbb{N}^{log}", + x => x / Math.pow(10, Math.ceil(Math.log10(x))) % 1 === 0 && x > 0, + x => { + let x10pow = Math.pow(10, Math.ceil(Math.log10(x))) + return Math.max(1, (Math.floor(x / x10pow) + 1) * x10pow) + }, + x => { + let x10pow = Math.pow(10, Math.ceil(Math.log10(x))) + return Math.max(1, (Math.ceil(x / x10pow) - 1) * x10pow) + } +) -var refedDomains = [] +let refedDomains = [] /** - * Parses a domain, that can use parenthesises. + * Parses a domain, that can use parentheses. * e.g (N ∪ [-1;0[) ∩ (Z \ {0;3}) * @param {string} domain - string of the domain to be parsed. * @returns {Domain} Parsed domain. */ -function parseDomain(domain) { - if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain) - var domStr +export function parseDomain(domain) { + if(!domain.includes(")") && !domain.includes("(")) return parseDomainSimple(domain) + let domStr while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) { - var dom = parseDomainSimple(domStr[1].trim()); - domain = domain.replace(domStr[0], 'D' + refedDomains.length) + let dom = parseDomainSimple(domStr[1].trim()) + domain = domain.replace(domStr[0], "D" + refedDomains.length) refedDomains.push(dom) } return parseDomainSimple(domain) } /** - * Parses a domain, without parenthesises. + * Parses a domain, without parentheses. * e.g N ∪ [-1;0[, Z \ {0;3}, N+*... * @param {string} domain - string of the domain to be parsed. * @returns {Domain} Parsed domain. */ -function parseDomainSimple(domain) { +export 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.at(0) === "{" && domain.at(-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.substring(1))] return new EmptySet() } diff --git a/common/src/math/expression.mjs b/common/src/math/expression.mjs new file mode 100644 index 0000000..2502110 --- /dev/null +++ b/common/src/math/expression.mjs @@ -0,0 +1,147 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +import * as Utils from "../utils/index.mjs" +import { ExprEvalExpression } from "../lib/expr-eval/expression.mjs" +import Latex from "../module/latex.mjs" +import ExprParser from "../module/expreval.mjs" +import Objects from "../module/objects.mjs" + +const NUMBER_MATCHER = /^\d*\.\d+(e[+-]\d+)?$/ + +/** + * Represents any kind of x-based or non variable based expression. + */ +export class Expression { + /** + * + * @param {string|ExprEvalExpression} expr + */ + constructor(expr) { + if(typeof expr === "string") { + this.expr = Utils.exponentsToExpression(expr) + this.calc = ExprParser.parse(this.expr).simplify() + } else if(expr instanceof ExprEvalExpression) { + // Passed an expression here directly. + this.calc = expr.simplify() + this.expr = expr.toString() + } else { + const type = expr != null ? "a " + expr.constructor.name : expr + throw new Error(`Cannot create an expression with ${type}.`) + } + this.canBeCached = this.isConstant() + this.cachedValue = null + if(this.canBeCached && this.allRequirementsFulfilled()) + this.recache() + this.latexMarkup = Latex.expression(this.calc.tokens) + } + + /** + * Return all the variables used in calc + * @return {string[]} + */ + variables() { + return this.calc.variables() + } + + /** + * Checks if the current expression is constant (does not depend on a variable, be it x or n). + * @return {boolean} + */ + isConstant() { + let vars = this.calc.variables() + return !vars.includes("x") && !vars.includes("n") + } + + /** + * Returns the list of object names this expression is dependant on. + * @return {string[]} + */ + requiredObjects() { + return this.calc.variables().filter(objName => objName !== "x" && objName !== "n") + } + + /** + * Checks if all the objects required for this expression are defined. + * @return {boolean} + */ + allRequirementsFulfilled() { + return this.requiredObjects().every(objName => objName in Objects.currentObjectsByName) + } + + /** + * Returns a list of names whose corresponding objects this expression is dependant on and are missing. + * @return {string[]} + */ + undefinedVariables() { + return this.requiredObjects().filter(objName => !(objName in Objects.currentObjectsByName)) + } + + recache() { + this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName) + } + + execute(x = 1) { + if(this.canBeCached) { + if(this.cachedValue == null) + this.recache() + return this.cachedValue + } + ExprParser.currentVars = Object.assign({ "x": x }, Objects.currentObjectsByName) + return this.calc.evaluate(ExprParser.currentVars) + } + + simplify(x) { + let expr = new Expression(this.calc.substitute("x", x).simplify()) + if(expr.allRequirementsFulfilled() && expr.execute() === 0) + expr = new Expression("0") + return expr + } + + toEditableString() { + return this.calc.toString() + } + + toString(forceSign = false) { + let str = Utils.makeExpressionReadable(this.calc.toString()) + if(str !== undefined && str.match(NUMBER_MATCHER)) { + const decimals = str.split(".")[1].split("e")[0] + const zeros = decimals.split("0").length + const nines = decimals.split("9").length + if(zeros > 7 || nines > 7) { + // Likely rounding error + str = parseFloat(str).toDecimalPrecision(8).toString() + } + } + if(str[0] === "(" && str.at(-1) === ")") + str = str.substring(1, str.length - 1) + if(str[0] !== "-" && forceSign) + str = "+" + str + return str + } +} + +/** + * Parses and executes the given expression + * @param {string} expr + * @return {number} + */ +export function executeExpression(expr) { + return (new Expression(expr.toString())).execute() +} diff --git a/common/src/math/index.mjs b/common/src/math/index.mjs new file mode 100644 index 0000000..c517958 --- /dev/null +++ b/common/src/math/index.mjs @@ -0,0 +1,40 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +import * as Expr from "./expression.mjs" +import * as Seq from "./sequence.mjs" +import * as Dom from "./domain.mjs" + + +export const Expression = Expr.Expression +export const executeExpression = Expr.executeExpression +export const Sequence = Seq.Sequence + +// Domains +export const Domain = Dom.Domain +export const EmptySet = Dom.EmptySet +export const Range = Dom.Range +export const SpecialDomain = Dom.SpecialDomain +export const DomainSet = Dom.DomainSet +export const UnionDomain = Dom.UnionDomain +export const IntersectionDomain = Dom.IntersectionDomain +export const MinusDomain = Dom.MinusDomain + +export const parseDomain = Dom.parseDomain +export const parseDomainSimple = Dom.parseDomainSimple diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/sequence.js b/common/src/math/sequence.mjs similarity index 56% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/sequence.js rename to common/src/math/sequence.mjs index 352a386..6049e53 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/math/sequence.js +++ b/common/src/math/sequence.mjs @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,16 @@ * along with this program. If not, see . */ -.pragma library - -.import "common.js" as C -.import "expression.js" as Expr -.import "../utils.js" as Utils -.import "../math/latex.js" as Latex - +import * as Expr from "./expression.mjs" +import * as Utils from "../utils/index.mjs" +import Latex from "../module/latex.mjs" +import Objects from "../module/objects.mjs" +import ExprParser from "../module/expreval.mjs" /** * Represents mathematical object for sequences. */ -class Sequence extends Expr.Expression { +export class Sequence extends Expr.Expression { constructor(name, baseValues = {}, valuePlus = 1, expr = "") { // u[n+valuePlus] = expr super(expr) @@ -35,9 +33,9 @@ class Sequence extends Expr.Expression { this.baseValues = baseValues this.calcValues = Object.assign({}, baseValues) this.latexValues = Object.assign({}, baseValues) - for(var n in this.calcValues) + for(let n in this.calcValues) if(['string', 'number'].includes(typeof this.calcValues[n])) { - let parsed = C.parser.parse(this.calcValues[n].toString()).simplify() + let parsed = ExprParser.parse(this.calcValues[n].toString()).simplify() this.latexValues[n] = Latex.expression(parsed.tokens) this.calcValues[n] = parsed.evaluate() } @@ -45,7 +43,7 @@ class Sequence extends Expr.Expression { } isConstant() { - return this.expr.indexOf("n") == -1 + return this.expr.indexOf("n") === -1 } execute(n = 1) { @@ -56,28 +54,31 @@ class Sequence extends Expr.Expression { } simplify(n = 1) { - if(n in this.calcValues) - return Utils.makeExpressionReadable(this.calcValues[n].toString()) - this.cache(n) - return Utils.makeExpressionReadable(this.calcValues[n].toString()) + if(!(n in this.calcValues)) + this.cache(n) + return this.calcValues[n].toString() } cache(n = 1) { - var str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString()) - var expr = C.parser.parse(str).simplify() - C.currentVars = Object.assign( + let str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString()) + let expr = 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 + ExprParser.currentVars = Object.assign( {'n': n-this.valuePlus}, // Just in case, add n (for custom functions) - C.currentObjectsByName + Objects.currentObjectsByName, + {[this.name]: this.calcValues} ) - C.currentVars[this.name] = this.calcValues - this.calcValues[n] = expr.evaluate(C.currentVars) + this.calcValues[n] = expr.evaluate(ExprParser.currentVars) } toString(forceSign=false) { - 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"}` + 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"}` ret += Object.keys(this.baseValues).map( n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}` ).join('; ') @@ -85,10 +86,10 @@ class Sequence extends Expr.Expression { } toLatexString(forceSign=false) { - 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"}\\\\` + 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"}\\\\` ret += Object.keys(this.latexValues).map( n => `${this.name}_{${n}} = ${this.latexValues[n]}` ).join('; ') + "\\end{array}" diff --git a/common/src/module/canvas.mjs b/common/src/module/canvas.mjs new file mode 100644 index 0000000..1137ae5 --- /dev/null +++ b/common/src/module/canvas.mjs @@ -0,0 +1,607 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Module } from "./common.mjs" +import { CanvasInterface, DialogInterface } from "./interface.mjs" +import { textsup } from "../utils/index.mjs" +import { Expression } from "../math/index.mjs" +import Latex from "./latex.mjs" +import Objects from "./objects.mjs" +import History from "./history.mjs" +import Settings from "./settings.mjs" + + +class CanvasAPI extends Module { + + + /** @type {CanvasInterface} */ + #canvas = null + /** @type {CanvasRenderingContext2D} */ + #ctx = null + /** Lock to prevent asynchronous stuff from printing stuff that is outdated. */ + #redrawCount = 0 + /** @type {{show(string, string, string)}} */ + #drawingErrorDialog = null + + + constructor() { + super("Canvas", { + canvas: CanvasInterface, + drawingErrorDialog: DialogInterface + }) + + /** + * + * @type {Object.} + */ + this.axesSteps = { + x: { + expression: null, + value: -1, + maxDraw: -1 + }, + y: { + expression: null, + value: -1, + maxDraw: -1 + } + } + } + + /** + * Initialize the module. + * @param {CanvasInterface} canvas + * @param {{show(string, string, string)}} drawingErrorDialog + */ + initialize({ canvas, drawingErrorDialog }) { + super.initialize({ canvas, drawingErrorDialog }) + this.#canvas = canvas + this.#drawingErrorDialog = drawingErrorDialog + } + + get width() { + if(!this.initialized) throw new Error("Attempting width before initialize!") + return this.#canvas.width + } + + get height() { + if(!this.initialized) throw new Error("Attempting height before initialize!") + return this.#canvas.height + } + + /** + * Minimum x of the diagram, provided from settings. + * @returns {number} + */ + get xmin() { + if(!this.initialized) throw new Error("Attempting xmin before initialize!") + return Settings.xmin + } + + /** + * Zoom on the x-axis of the diagram, provided from settings. + * @returns {number} + */ + get xzoom() { + if(!this.initialized) throw new Error("Attempting xzoom before initialize!") + return Settings.xzoom + } + + /** + * Maximum y of the diagram, provided from settings. + * @returns {number} + */ + get ymax() { + if(!this.initialized) throw new Error("Attempting ymax before initialize!") + return Settings.ymax + } + + /** + * Zoom on the y-axis of the diagram, provided from settings. + * @returns {number} + */ + get yzoom() { + if(!this.initialized) throw new Error("Attempting yzoom before initialize!") + return Settings.yzoom + } + + /** + * Label used on the x-axis, provided from settings. + * @returns {string} + */ + get xlabel() { + if(!this.initialized) throw new Error("Attempting xlabel before initialize!") + return Settings.xlabel + } + + /** + * Label used on the y-axis, provided from settings. + * @returns {string} + */ + get ylabel() { + if(!this.initialized) throw new Error("Attempting ylabel before initialize!") + return Settings.ylabel + } + + /** + * Width of lines that will be drawn into the canvas, provided from settings. + * @returns {number} + */ + get linewidth() { + if(!this.initialized) throw new Error("Attempting linewidth before initialize!") + return Settings.linewidth + } + + /** + * Font size of the text that will be drawn into the canvas, provided from settings. + * @returns {number} + */ + get textsize() { + if(!this.initialized) throw new Error("Attempting textsize before initialize!") + return Settings.textsize + } + + /** + * True if the canvas should be in logarithmic mode, false otherwise. + * @returns {boolean} + */ + get logscalex() { + if(!this.initialized) throw new Error("Attempting logscalex before initialize!") + return Settings.logscalex + } + + /** + * True if the x graduation should be shown, false otherwise. + * @returns {boolean} + */ + get showxgrad() { + if(!this.initialized) throw new Error("Attempting showxgrad before initialize!") + return Settings.showxgrad + } + + /** + * True if the y graduation should be shown, false otherwise. + * @returns {boolean} + */ + get showygrad() { + if(!this.initialized) throw new Error("Attempting showygrad before initialize!") + return Settings.showygrad + } + + /** + * Max power of the logarithmic scaled on the x axis in logarithmic mode. + * @returns {number} + */ + get maxgradx() { + if(!this.initialized) throw new Error("Attempting maxgradx before initialize!") + return Math.min( + 309, // 10e309 = Infinity (beyond this land be dragons) + Math.max( + Math.ceil(Math.abs(Math.log10(this.xmin))), + Math.ceil(Math.abs(Math.log10(this.px2x(this.width)))) + ) + ) + } + + // + // Methods to draw the canvas + // + + requestPaint() { + if(!this.initialized) throw new Error("Attempting requestPaint before initialize!") + this.#canvas.requestPaint() + } + + /** + * Redraws the entire canvas + */ + redraw() { + if(!this.initialized) throw new Error("Attempting redraw before initialize!") + if(this.#ctx == null) + this.#ctx = this.#canvas.getContext("2d") + this.#redrawCount = (this.#redrawCount + 1) % 10000 + this._computeAxes() + this._reset() + this._drawGrid() + this._drawAxes() + this._drawLabels() + this.#ctx.lineWidth = this.linewidth + for(let objType in Objects.currentObjects) { + for(let obj of Objects.currentObjects[objType]) { + this.#ctx.strokeStyle = obj.color + this.#ctx.fillStyle = obj.color + if(obj.visible) + try { + obj.draw(this) + } catch(e) { + // Drawing throws an error. Generally, it's due to a new modification (or the opening of a file) + console.error(e) + console.log(e.stack) + this.#drawingErrorDialog.show(objType, obj.name, e.message) + History.undo() + } + } + } + this.#ctx.lineWidth = 1 + } + + /** + * Calculates information for drawing gradations for axes. + * @private + */ + _computeAxes() { + let exprY = new Expression(`x*(${Settings.yaxisstep})`) + let y1 = exprY.execute(1) + let exprX = new Expression(`x*(${Settings.xaxisstep})`) + let x1 = exprX.execute(1) + this.axesSteps = { + x: { + expression: exprX, + value: x1, + maxDraw: Math.ceil(Math.max(Math.abs(this.xmin), Math.abs(this.px2x(this.width))) / x1) + }, + y: { + expression: exprY, + value: y1, + maxDraw: Math.ceil(Math.max(Math.abs(this.ymax), Math.abs(this.px2y(this.height))) / y1) + } + } + } + + /** + * Resets the canvas to a blank one with default setting. + * @private + */ + _reset() { + // Reset + this.#ctx.fillStyle = "#FFFFFF" + this.#ctx.strokeStyle = "#000000" + this.#ctx.font = `${this.textsize}px sans-serif` + this.#ctx.fillRect(0, 0, this.width, this.height) + } + + /** + * Draws the grid. + * @private + */ + _drawGrid() { + this.#ctx.strokeStyle = "#C0C0C0" + if(this.logscalex) { + for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) { + for(let xmulti = 1; xmulti < 10; xmulti++) { + this.drawXLine(Math.pow(10, xpow) * xmulti) + } + } + } else { + for(let x = 0; x < this.axesSteps.x.maxDraw; x += 1) { + this.drawXLine(x * this.axesSteps.x.value) + this.drawXLine(-x * this.axesSteps.x.value) + } + } + for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) { + this.drawYLine(y * this.axesSteps.y.value) + this.drawYLine(-y * this.axesSteps.y.value) + } + } + + /** + * Draws the graph axes. + * @private + */ + _drawAxes() { + this.#ctx.strokeStyle = "#000000" + let axisypos = this.logscalex ? 1 : 0 + this.drawXLine(axisypos) + this.drawYLine(0) + let axisypx = this.x2px(axisypos) // X coordinate of Y axis + let axisxpx = this.y2px(0) // Y coordinate of X axis + // Drawing arrows + this.drawLine(axisypx, 0, axisypx - 10, 10) + this.drawLine(axisypx, 0, axisypx + 10, 10) + this.drawLine(this.width, axisxpx, this.width - 10, axisxpx - 10) + this.drawLine(this.width, axisxpx, this.width - 10, axisxpx + 10) + } + + /** + * Resets the canvas to a blank one with default setting. + * @private + */ + _drawLabels() { + let axisypx = this.x2px(this.logscalex ? 1 : 0) // X coordinate of Y axis + let axisxpx = this.y2px(0) // Y coordinate of X axis + // Labels + this.#ctx.fillStyle = "#000000" + this.#ctx.font = `${this.textsize}px sans-serif` + this.#ctx.fillText(this.ylabel, axisypx + 10, 24) + let textWidth = this.#ctx.measureText(this.xlabel).width + this.#ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5) + // Axis graduation labels + this.#ctx.font = `${this.textsize - 4}px sans-serif` + + let txtMinus = this.#ctx.measureText("-").width + if(this.showxgrad) { + if(this.logscalex) { + for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow += 1) { + textWidth = this.#ctx.measureText("10" + textsup(xpow)).width + if(xpow !== 0) + this.drawVisibleText("10" + textsup(xpow), this.x2px(Math.pow(10, xpow)) - textWidth / 2, axisxpx + 16 + (6 * (xpow === 1))) + } + } else { + for(let x = 1; x < this.axesSteps.x.maxDraw; x += 1) { + let drawX = x * this.axesSteps.x.value + let txtX = this.axesSteps.x.expression.simplify(x).toString().replace(/^\((.+)\)$/, "$1") + let textHeight = this.measureText(txtX).height + this.drawVisibleText(txtX, this.x2px(drawX) - 4, axisxpx + this.textsize / 2 + textHeight) + this.drawVisibleText("-" + txtX, this.x2px(-drawX) - 4, axisxpx + this.textsize / 2 + textHeight) + } + } + } + if(this.showygrad) { + for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) { + let drawY = y * this.axesSteps.y.value + let txtY = this.axesSteps.y.expression.simplify(y).toString().replace(/^\((.+)\)$/, "$1") + textWidth = this.#ctx.measureText(txtY).width + this.drawVisibleText(txtY, axisypx - 6 - textWidth, this.y2px(drawY) + 4 + (10 * (y === 0))) + if(y !== 0) + this.drawVisibleText("-" + txtY, axisypx - 6 - textWidth - txtMinus, this.y2px(-drawY) + 4) + } + } + this.#ctx.fillStyle = "#FFFFFF" + } + + // + // Public functions + // + + /** + * Draws an horizontal line at x plot coordinate. + * @param {number} x + */ + drawXLine(x) { + if(this.isVisible(x, this.ymax)) { + this.drawLine(this.x2px(x), 0, this.x2px(x), this.height) + } + } + + /** + * Draws an vertical line at y plot coordinate + * @param {number} y + * @private + */ + drawYLine(y) { + if(this.isVisible(this.xmin, y)) { + this.drawLine(0, this.y2px(y), this.width, this.y2px(y)) + } + } + + /** + * Writes multiline text onto the canvas. + * NOTE: The x and y properties here are relative to the canvas, not the plot. + * @param {string} text + * @param {number} x + * @param {number} y + */ + drawVisibleText(text, x, y) { + if(x > 0 && x < this.width && y > 0 && y < this.height) { + text.toString().split("\n").forEach((txt, i) => { + this.#ctx.fillText(txt, x, y + (this.textsize * i)) + }) + } + } + + /** + * Draws an image onto the canvas. + * NOTE: The x, y width and height properties here are relative to the canvas, not the plot. + * @param {CanvasImageSource} image + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + drawVisibleImage(image, x, y, width, height) { + this.#canvas.markDirty(Qt.rect(x, y, width, height)) + this.#ctx.drawImage(image, x, y, width, height) + } + + /** + * Measures the width and height of a multiline text that would be drawn onto the canvas. + * @param {string} text + * @returns {{width: number, height: number}} + */ + measureText(text) { + let theight = 0 + let twidth = 0 + let defaultHeight = this.textsize * 1.2 // Approximate but good enough! + for(let txt of text.split("\n")) { + theight += defaultHeight + if(this.#ctx.measureText(txt).width > twidth) twidth = this.#ctx.measureText(txt).width + } + return { "width": twidth, "height": theight } + } + + /** + * Converts an x coordinate to its relative position on the canvas. + * It supports both logarithmic and non-logarithmic scale depending on the currently selected mode. + * @param {number} x + * @returns {number} + */ + x2px(x) { + if(this.logscalex) { + const logxmin = Math.log(this.xmin) + return (Math.log(x) - logxmin) * this.xzoom + } else + return (x - this.xmin) * this.xzoom + } + + /** + * Converts an y coordinate to it's relative position on the canvas. + * The y-axis not supporting logarithmic scale, it only supports linear conversion. + * @param {number} y + * @returns {number} + */ + y2px(y) { + return (this.ymax - y) * this.yzoom + } + + /** + * Converts an x px position on the canvas to it's corresponding coordinate on the plot. + * It supports both logarithmic and non-logarithmic scale depending on the currently selected mode. + * @param {number} px + * @returns {number} + */ + px2x(px) { + if(this.logscalex) { + return Math.exp(px / this.xzoom + Math.log(this.xmin)) + } else + return (px / this.xzoom + this.xmin) + } + + /** + * Converts an x px position on the canvas to it's corresponding coordinate on the plot. + * It supports both logarithmic and non logarithmic scale depending on the currently selected mode. + * @param {number} px + * @returns {number} + */ + px2y(px) { + return -(px / this.yzoom - this.ymax) + } + + /** + * Checks whether a plot point (x, y) is visible or not on the canvas. + * @param {number} x + * @param {number} y + * @returns {boolean} + */ + isVisible(x, y) { + return (this.x2px(x) >= 0 && this.x2px(x) <= this.width) && (this.y2px(y) >= 0 && this.y2px(y) <= this.height) + } + + /** + * Draws a line from plot point (x1, y1) to plot point (x2, y2). + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + */ + drawLine(x1, y1, x2, y2) { + this.#ctx.beginPath() + this.#ctx.moveTo(x1, y1) + this.#ctx.lineTo(x2, y2) + this.#ctx.stroke() + } + + /** + * Draws a dashed line from plot point (x1, y1) to plot point (x2, y2). + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @param {number} dashPxSize + */ + drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) { + this.#ctx.setLineDash([dashPxSize / 2, dashPxSize]) + this.drawLine(x1, y1, x2, y2) + this.#ctx.setLineDash([]) + } + + /** + * Renders latex markup ltxText to an image and loads it. Returns a dictionary with three values: source, width and height. + * @param {string} ltxText + * @param {string} color + * @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback + */ + renderLatexImage(ltxText, color, callback) { + const currentRedrawCount = this.#redrawCount + const onRendered = (imgData) => { + if(!this.#canvas.isImageLoaded(imgData.source) && !this.#canvas.isImageLoading(imgData.source)) { + // Wait until the image is loaded to callback. + this.#canvas.loadImageAsync(imgData.source).then(() => { + if(this.#redrawCount === currentRedrawCount) + callback(imgData) + else + console.log("1. Discard render of", imgData.source, this.#redrawCount, currentRedrawCount) + }) + } else { + // Callback directly + if(this.#redrawCount === currentRedrawCount) + callback(imgData) + else + console.log("2. Discard render of", imgData.source, this.#redrawCount, currentRedrawCount) + } + } + const prerendered = Latex.findPrerendered(ltxText, this.textsize, color) + if(prerendered !== null) + onRendered(prerendered) + else + Latex.requestAsyncRender(ltxText, this.textsize, color).then(onRendered) + } + + // + // Context methods + // + + get font() { + return this.#ctx.font + } + + set font(value) { + return this.#ctx.font = value + } + + /** + * Draws an act on the canvas centered on a point. + * @param {number} x + * @param {number} y + * @param {number} radius + * @param {number} startAngle + * @param {number} endAngle + * @param {boolean} counterclockwise + */ + arc(x, y, radius, startAngle, endAngle, counterclockwise = false) { + this.#ctx.beginPath() + this.#ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise) + this.#ctx.stroke() + } + + /** + * Draws a filled circle centered on a point. + * @param {number} x + * @param {number} y + * @param {number} radius + */ + disc(x, y, radius) { + this.#ctx.beginPath() + this.#ctx.arc(x, y, radius, 0, 2 * Math.PI) + this.#ctx.fill() + } + + /** + * Draws a filled rectangle onto the canvas. + * @param {number} x + * @param {number} y + * @param {number} w + * @param {number} h + */ + fillRect(x, y, w, h) { + this.#ctx.fillRect(x, y, w, h) + } +} + +/** @type {CanvasAPI} */ +Modules.Canvas = Modules.Canvas || new CanvasAPI() +export default Modules.Canvas diff --git a/common/src/module/common.mjs b/common/src/module/common.mjs new file mode 100644 index 0000000..f5fa8aa --- /dev/null +++ b/common/src/module/common.mjs @@ -0,0 +1,74 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Interface } from "./interface.mjs" +import { BaseEventEmitter } from "../events.mjs" + +// Define Modules interface before they are imported. +globalThis.Modules = globalThis.Modules || {} + +/** + * Base class for global APIs in runtime. + */ +export class Module extends BaseEventEmitter { + /** @type {string} */ + #name + /** @type {Object.} */ + #initializationParameters + /** @type {boolean} */ + #initialized = false + + /** + * + * @param {string} name - Name of the API + * @param {Object.} initializationParameters - List of parameters for the initialize function. + */ + constructor(name, initializationParameters = {}) { + super() + console.log(`Loading module ${name}...`) + this.#name = name + this.#initializationParameters = initializationParameters + } + + get name() { + return this.#name; + } + + get initialized() { + return this.#initialized + } + + /** + * Checks if all requirements are defined. + * @param {Object.} options + */ + initialize(options) { + if(this.#initialized) + throw new Error(`Cannot reinitialize module ${this.#name}.`) + console.log(`Initializing ${this.#name}...`) + for(const [name, value] of Object.entries(this.#initializationParameters)) { + if(!options.hasOwnProperty(name)) + throw new Error(`Option '${name}' of initialize of module ${this.#name} does not exist.`) + if(typeof value === "function" && value.prototype instanceof Interface) + Interface.checkImplementation(value, options[name]) + else if(typeof value !== typeof options[name]) + throw new Error(`Option '${name}' of initialize of module ${this.#name} is not a '${value}' (${typeof options[name]}).`) + } + this.#initialized = true + } +} diff --git a/common/src/module/expreval.mjs b/common/src/module/expreval.mjs new file mode 100644 index 0000000..dd89f4d --- /dev/null +++ b/common/src/module/expreval.mjs @@ -0,0 +1,116 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Module } from "./common.mjs" +import { Parser } from "../lib/expr-eval/parser.mjs" + +const EVAL_VARIABLES = { + // 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 Module { + #parser = new Parser() + + constructor() { + super("ExprParser") + this.currentVars = {} + this.#parser = new Parser() + + this.#parser.consts = Object.assign({}, this.#parser.consts, EVAL_VARIABLES) + + 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, variable + if(args.length === 1) { + // Parse object + f = args[0] + if(typeof f !== "object" || !f.execute) + throw EvalError(qsTranslate("usage", "Usage:\n%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:\n%1").arg(usage2)) + f = this.#parser.parse(f).toJSFunction(variable, this.currentVars) + } else + throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2)) + return f + } + + /** + * @param {string} expression - Expression to parse + * @returns {ExprEvalExpression} + */ + parse(expression) { + return this.#parser.parse(expression) + } + + integral(a = null, b = null, ...args) { + let usage1 = qsTranslate("usage", "integral(, , )") + let usage2 = qsTranslate("usage", "integral(, , , )") + let f = this.parseArgumentsForFunction(args, usage1, usage2) + if(typeof a !== "number" || typeof b !== "number") + throw EvalError(qsTranslate("usage", "Usage:\n%1\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(, )") + let usage2 = qsTranslate("usage", "derivative(, , )") + let x = args.pop() + let f = this.parseArgumentsForFunction(args, usage1, usage2) + if(typeof x !== "number") + throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2)) + + let derivative_precision = 1e-8 + return (f(x + derivative_precision / 2) - f(x - derivative_precision / 2)) / derivative_precision + } +} + +/** @type {ExprParserAPI} */ +Modules.ExprParser = Modules.ExprParser || new ExprParserAPI() + +export default Modules.ExprParser + diff --git a/common/src/module/history.mjs b/common/src/module/history.mjs new file mode 100644 index 0000000..45a37f0 --- /dev/null +++ b/common/src/module/history.mjs @@ -0,0 +1,184 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Module } from "./common.mjs" +import { HelperInterface, NUMBER, STRING } from "./interface.mjs" +import { BaseEvent } from "../events.mjs" +import { Action, Actions } from "../history/index.mjs" + + + +class ClearedEvent extends BaseEvent { + constructor() { + super("cleared") + } +} + +class LoadedEvent extends BaseEvent { + constructor() { + super("loaded") + } +} + +class AddedEvent extends BaseEvent { + constructor(action) { + super("added") + this.action = action + } +} + +class UndoneEvent extends BaseEvent { + constructor(action) { + super("undone") + this.undid = action + } +} + +class RedoneEvent extends BaseEvent { + constructor(action) { + super("redone") + this.redid = action + } +} + +class HistoryAPI extends Module { + static emits = ["cleared", "loaded", "added", "undone", "redone"] + + #helper + + constructor() { + super("History", { + helper: HelperInterface, + themeTextColor: STRING, + imageDepth: NUMBER, + fontSize: NUMBER + }) + // History QML object + /** @type {Action[]} */ + this.undoStack = [] + /** @type {Action[]} */ + this.redoStack = [] + + this.themeTextColor = "#FF0000" + this.imageDepth = 2 + this.fontSize = 28 + } + + /** + * @param {HelperInterface} historyObj + * @param {string} themeTextColor + * @param {number} imageDepth + * @param {number} fontSize + */ + initialize({ helper, themeTextColor, imageDepth, fontSize }) { + super.initialize({ helper, themeTextColor, imageDepth, fontSize }) + this.#helper = helper + this.themeTextColor = themeTextColor + this.imageDepth = imageDepth + this.fontSize = fontSize + } + + /** + * Undoes the Action at the top of the undo stack and pushes it to the top of the redo stack. + */ + undo() { + if(!this.initialized) throw new Error("Attempting undo before initialize!") + if(this.undoStack.length > 0) { + const action = this.undoStack.pop() + action.undo() + this.redoStack.push(action) + this.emit(new UndoneEvent(action)) + } + } + + /** + * Redoes the Action at the top of the redo stack and pushes it to the top of the undo stack. + */ + redo() { + if(!this.initialized) throw new Error("Attempting redo before initialize!") + if(this.redoStack.length > 0) { + const action = this.redoStack.pop() + action.redo() + this.undoStack.push(action) + this.emit(new RedoneEvent(action)) + } + } + + /** + * Clears both undo and redo stacks completely. + */ + clear() { + if(!this.initialized) throw new Error("Attempting clear before initialize!") + this.undoStack = [] + this.redoStack = [] + this.emit(new ClearedEvent()) + } + + /** + * Adds an instance of HistoryLib.Action to history. + * @param action + */ + addToHistory(action) { + if(!this.initialized) throw new Error("Attempting addToHistory before initialize!") + if(action instanceof Action) { + console.log("Added new entry to history: " + action.getReadableString()) + this.undoStack.push(action) + if(this.#helper.getSetting("reset_redo_stack")) + this.redoStack = [] + this.emit(new AddedEvent(action)) + } + } + + /** + * Unserializes both the undo stack and redo stack from serialized content. + * @param {[string, any[]][]} undoSt + * @param {[string, any[]][]} redoSt + */ + unserialize(undoSt, redoSt) { + if(!this.initialized) throw new Error("Attempting unserialize before initialize!") + this.clear() + for(const [name, args] of undoSt) + this.undoStack.push( + new Actions[name](...args) + ) + for(const [name, args] of redoSt) + this.redoStack.push( + new Actions[name](...args) + ) + this.emit(new LoadedEvent()) + } + + /** + * Serializes history into JSON-able content. + * @return {[[string, any[]], [string, any[]]]} + */ + serialize() { + if(!this.initialized) throw new Error("Attempting serialize before initialize!") + let undoSt = [], redoSt = []; + for(const action of this.undoStack) + undoSt.push([ action.type(), action.export() ]) + for(const action of this.redoStack) + redoSt.push([ action.type(), action.export() ]) + return [undoSt, redoSt] + } +} + +/** @type {HistoryAPI} */ +Modules.History = Modules.History || new HistoryAPI() + +export default Modules.History diff --git a/common/src/module/index.mjs b/common/src/module/index.mjs new file mode 100644 index 0000000..6fdff97 --- /dev/null +++ b/common/src/module/index.mjs @@ -0,0 +1,37 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Objects from "./objects.mjs" +import Settings from "./settings.mjs" +import ExprParser from "./expreval.mjs" +import Latex from "./latex.mjs" +import History from "./history.mjs" +import Canvas from "./canvas.mjs" +import IO from "./io.mjs" +import Preferences from "./preferences.mjs" + +export default { + Objects, + Settings, + ExprParser, + Latex, + History, + Canvas, + IO, + Preferences +} diff --git a/common/src/module/interface.mjs b/common/src/module/interface.mjs new file mode 100644 index 0000000..6947acd --- /dev/null +++ b/common/src/module/interface.mjs @@ -0,0 +1,141 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * + * @author Ad5001 + * @license GPL-3.0-or-later + * @copyright (C) 2021-2025 Ad5001 + * @preserve + * + * 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 . + */ + +export const NUMBER = 0 +export const STRING = "string" +export const BOOLEAN = true +export const OBJECT = {} +export const FUNCTION = () => { + throw new Error("Cannot call function of an interface.") +} + + +export class Interface { + /** + * Checks if the class to check implements the given interface. + * Throws an error if the implementation does not conform to the interface. + * @param {typeof Interface} interface_ + * @param {object} classToCheck + */ + static checkImplementation(interface_, classToCheck) { + const properties = new interface_() + const interfaceName = interface_.name + const toCheckName = classToCheck.constructor.name + for(const [property, value] of Object.entries(properties)) + if(property !== "implement") { + if(classToCheck[property] === undefined) + // Check if the property exist + throw new Error(`Property '${property}' (${typeof value}) is present in interface ${interfaceName}, but not in implementation ${toCheckName}.`) + else if((typeof value) !== (typeof classToCheck[property])) + // Compare the types + throw new Error(`Property '${property}' of ${interfaceName} implementation ${toCheckName} is a '${typeof classToCheck[property]}' and not a '${typeof value}'.`) + else if((typeof value) === "object") + // Test type of object. + if(value instanceof Interface) + Interface.checkImplementation(value, classToCheck[property]) + else if(value.prototype && !(classToCheck[property] instanceof value)) + throw new Error(`Property '${property}' of ${interfaceName} implementation ${toCheckName} is not '${value.constructor.name}'.`) + } + } +} + + +export class CanvasInterface extends Interface { + /** @type {function(string): CanvasRenderingContext2D} */ + getContext = FUNCTION + /** @type {function(rect)} */ + markDirty = FUNCTION + /** @type {function(string): Promise} */ + loadImageAsync = FUNCTION + /** @type {function(string)} */ + isImageLoading = FUNCTION + /** @type {function(string)} */ + isImageLoaded = FUNCTION + /** @type {function()} */ + requestPaint = FUNCTION +} + +export class RootInterface extends Interface { + width = NUMBER + height = NUMBER +} + +export class DialogInterface extends Interface { + show = FUNCTION +} + +export class LatexInterface extends Interface { + supportsAsyncRender = BOOLEAN + /** + * @param {string} markup - LaTeX markup to render + * @param {number} fontSize - Font size (in pt) to render + * @param {string} color - Color of the text to render + * @returns {string} - Comma separated data of the image (source, width, height) + */ + renderSync = FUNCTION + /** + * @param {string} markup - LaTeX markup to render + * @param {number} fontSize - Font size (in pt) to render + * @param {string} color - Color of the text to render + * @returns {Promise} - Comma separated data of the image (source, width, height) + */ + renderAsync = FUNCTION + /** + * @param {string} markup - LaTeX markup to render + * @param {number} fontSize - Font size (in pt) to render + * @param {string} color - Color of the text to render + * @returns {string} - Comma separated data of the image (source, width, height) + */ + findPrerendered = FUNCTION + /** + * Checks if the Latex installation is valid + * @returns {boolean} + */ + checkLatexInstallation = FUNCTION +} + +export class HelperInterface extends Interface { + /** + * Gets a setting from the config + * @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin") + * @returns {string|number|boolean} Value of the setting + */ + getSetting = FUNCTION + /** + * Sets a setting in the config + * @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin") + * @param {string|number|boolean} value + */ + setSetting = FUNCTION + /** + * Sends data to be written + * @param {string} file + * @param {string} dataToWrite - just JSON encoded, requires the "LPFv1" mime to be added before writing + */ + write = FUNCTION + /** + * Requests data to be read from a file + * @param {string} file + * @returns {string} the loaded data - just JSON encoded, requires the "LPFv1" mime to be stripped + */ + load = FUNCTION +} diff --git a/common/src/module/io.mjs b/common/src/module/io.mjs new file mode 100644 index 0000000..4887954 --- /dev/null +++ b/common/src/module/io.mjs @@ -0,0 +1,213 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Module } from "./common.mjs" +import Objects from "./objects.mjs" +import History from "./history.mjs" +import Settings from "./settings.mjs" +import { DialogInterface, RootInterface } from "./interface.mjs" +import { BaseEvent } from "../events.mjs" + + +class LoadedEvent extends BaseEvent { + constructor() { + super("loaded") + } +} + +class SavedEvent extends BaseEvent { + constructor() { + super("saved") + } +} + +class ModifiedEvent extends BaseEvent { + constructor() { + super("modified") + } +} + +class IOAPI extends Module { + static emits = ["loaded", "saved", "modified"] + + /** @type {RootInterface} */ + #rootElement + /** @type {{show: function(string)}} */ + #alert + #saved = true + + constructor() { + super("IO", { + alert: DialogInterface, + root: RootInterface + }) + + // Settings.on("changed", this.__emitModified.bind(this)) + History.on("added undone redone", this.__emitModified.bind(this)) + } + + __emitModified() { + this.#saved = false + this.emit(new ModifiedEvent()) + } + + + /** + * True if no changes have been made since last save, false otherwise. + * @return {boolean} + */ + get saved() { return this.#saved } + + /** + * Initializes module with QML elements. + * @param {RootInterface} root + * @param {{show: function(string)}} alert + */ + initialize({ root, alert }) { + super.initialize({ root, alert }) + this.#rootElement = root + this.#alert = alert + } + + /** + * Saves the diagram to a certain \c filename. + * @param {string} filename + */ + saveDiagram(filename) { + if(!this.initialized) throw new Error("Attempting saveDiagram before initialize!") + // Add extension if necessary + if(["lpf"].indexOf(filename.split(".")[filename.split(".").length - 1]) === -1) + filename += ".lpf" + Settings.set("saveFilename", filename, false) + let objs = {} + for(let objType in Objects.currentObjects) { + objs[objType] = [] + for(let obj of Objects.currentObjects[objType]) { + objs[objType].push(obj.export()) + } + } + let settings = { + "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": 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())) + this.#saved = true + this.emit(new SavedEvent()) + } + + /** + * Loads the diagram from a certain \c filename. + * @param {string} filename + */ + async loadDiagram(filename) { + if(!this.initialized) throw new Error("Attempting loadDiagram before initialize!") + if(!History.initialized) throw new Error("Attempting loadDiagram before history is initialized!") + 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(data.hasOwnProperty("type") && data["type"] === "logplotv1") { + History.clear() + // Importing settings + Settings.set("saveFilename", filename, false) + Settings.set("xzoom", parseFloat(data["xzoom"]) || 100, false) + Settings.set("yzoom", parseFloat(data["yzoom"]) || 10, false) + Settings.set("xmin", parseFloat(data["xmin"]) || 5 / 10, false) + Settings.set("ymax", parseFloat(data["ymax"]) || 24, false) + Settings.set("xaxisstep", data["xaxisstep"] || "4", false) + Settings.set("yaxisstep", data["yaxisstep"] || "4", false) + Settings.set("xlabel", data["xaxislabel"] || "", false) + Settings.set("ylabel", data["yaxislabel"] || "", false) + Settings.set("logscalex", data["logscalex"] === true, false) + if("showxgrad" in data) + Settings.set("showxgrad", data["showxgrad"], false) + if("showygrad" in data) + Settings.set("showygrad", data["showygrad"], false) + if("linewidth" in data) + Settings.set("linewidth", data["linewidth"], false) + if("textsize" in data) + Settings.set("textsize", data["textsize"], false) + this.#rootElement.height = parseFloat(data["height"]) || 500 + this.#rootElement.width = parseFloat(data["width"]) || 1000 + + // Importing objects + Objects.currentObjects = {} + for(let key of Object.keys(Objects.currentObjectsByName)) { + delete Objects.currentObjectsByName[key] + // Required to keep the same reference for the copy of the object used in expression variable detection. + // Another way would be to change the reference as well, but I feel like the code would be less clean. + } + for(let objType in data["objects"]) { + if(Object.keys(Objects.types).includes(objType)) { + Objects.currentObjects[objType] = [] + for(let objData of data["objects"][objType]) { + /** @type {DrawableObject} */ + let obj = Objects.types[objType].import(...objData) + Objects.currentObjects[objType].push(obj) + Objects.currentObjectsByName[obj.name] = obj + } + } else { + error += qsTranslate("io", "Unknown object type: %1.").arg(objType) + "\n" + } + } + + // Updating object dependencies. + for(let objName in Objects.currentObjectsByName) + Objects.currentObjectsByName[objName].update() + + // Importing history + if("history" in data) + History.unserialize(...data["history"]) + + } else { + error = qsTranslate("io", "Invalid file provided.") + } + if(error !== "") { + console.log(error) + this.#alert.show(qsTranslate("io", "Could not load file: ") + error) + // TODO: Error handling + return + } + this.#alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename)) + this.#saved = true + this.emit(new LoadedEvent()) + } + +} + +/** @type {IOAPI} */ +Modules.IO = Modules.IO || new IOAPI() + +export default Modules.IO diff --git a/common/src/module/latex.mjs b/common/src/module/latex.mjs new file mode 100644 index 0000000..27fbdd4 --- /dev/null +++ b/common/src/module/latex.mjs @@ -0,0 +1,374 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Module } from "./common.mjs" +import { BaseEvent } from "../events.mjs" +import * as Instruction from "../lib/expr-eval/instruction.mjs" +import { escapeValue } from "../lib/expr-eval/expression.mjs" +import { HelperInterface, LatexInterface } from "./interface.mjs" + +const unicodechars = ["pi", "∞", + "α", "β", "γ", "δ", "ε", "ζ", "η", + "π", "θ", "κ", "λ", "μ", "ξ", "ρ", + "ς", "σ", "τ", "φ", "χ", "ψ", "ω", + "Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ", + "Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ", + "ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ", + "ₜ", "¹", "²", "³", "⁴", "⁵", "⁶", + "⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃", + "₄", "₅", "₆", "₇", "₈", "₉", "₀" +] +const equivalchars = ["\\pi", "\\infty", + "\\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}", +] + + + + +class AsyncRenderStartedEvent extends BaseEvent { + constructor(markup, fontSize, color) { + super("async-render-started") + this.markup = markup + this.fontSize = fontSize + this.color = color + } +} + + +class AsyncRenderFinishedEvent extends BaseEvent { + constructor(markup, fontSize, color) { + super("async-render-finished") + this.markup = markup + this.fontSize = fontSize + this.color = color + } +} + +/** + * Class containing the result of a LaTeX render. + * + * @property {string} source - Exported PNG file + * @property {number} width + * @property {number} height + */ +class LatexRenderResult { + constructor(source, width, height) { + this.source = source + this.width = parseFloat(width) + this.height = parseFloat(height) + } +} + +class LatexAPI extends Module { + static emits = ["async-render-started", "async-render-finished"] + + /** @type {LatexInterface} */ + #latex = null + + constructor() { + super("Latex", { + latex: LatexInterface, + helper: HelperInterface + }) + /** + * true if latex has been enabled by the user, false otherwise. + */ + this.enabled = false + this.promises = new Set() + } + + /** + * @param {LatexInterface} latex + * @param {HelperInterface} helper + */ + initialize({ latex, helper }) { + super.initialize({ latex, helper }) + this.#latex = latex + this.enabled = helper.getSetting("enable_latex") + } + + /** + * Checks if the given markup (with given font size and color) has already been + * rendered, and if so, returns its data. Otherwise, returns null. + * + * @param {string} markup - LaTeX markup to render. + * @param {number} fontSize - Font size (in pt) to render. + * @param {string} color - Color of the text to render. + * @returns {LatexRenderResult|null} + */ + findPrerendered(markup, fontSize, color) { + if(!this.initialized) throw new Error("Attempting findPrerendered before initialize!") + const data = this.#latex.findPrerendered(markup, fontSize, color) + let ret = null + if(data !== "") + ret = new LatexRenderResult(...data.split(",")) + return ret + } + + /** + * Prepares and renders a latex string into a png file asynchronously. + * + * @param {string} markup - LaTeX markup to render. + * @param {number} fontSize - Font size (in pt) to render. + * @param {string} color - Color of the text to render. + * @returns {Promise} + */ + async requestAsyncRender(markup, fontSize, color) { + if(!this.initialized) throw new Error("Attempting requestAsyncRender before initialize!") + let render + if(this.#latex.supportsAsyncRender) { + this.emit(new AsyncRenderStartedEvent(markup, fontSize, color)) + // Storing promise so that it does not get dereferenced. + const promise = this.#latex.renderAsync(markup, fontSize, color) + this.promises.add(promise) + render = await promise + this.promises.delete(promise) + this.emit(new AsyncRenderFinishedEvent(markup, fontSize, color)) + } else { + render = this.#latex.renderSync(markup, fontSize, color) + } + const args = render.split(",") + return new LatexRenderResult(...args) + } + + /** + * Puts element within parenthesis. + * + * @param {string|number} 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|number} elem - element to put within parenthesis. + * @param {Array} contents - Array of elements to put within parenthesis. + * @returns {string} + */ + parif(elem, contents) { + elem = elem.toString() + const contains = contents.some(x => elem.indexOf(x) > 0) + if(contains && (elem[0] !== "(" || elem.at(-1) !== ")")) + return this.par(elem) + if(!contains && elem[0] === "(" && elem.at(-1) === ")") + return elem.removeEnclosure() + 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].removeEnclosure().replaceAll(args[1].removeEnclosure(), "x")}}{dx}` + else + return `\\frac{d${args[0]}}{dx}(${args[1]})` + case "integral": + if(args.length === 4) + return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2].removeEnclosure()} d${args[3].removeEnclosure()}` + else + return `\\int\\limits_{${args[0]}}^{${args[1]}}${args[2]}(t) dt` + case "sqrt": + const arg = this.parif(args.join(", "), []) + return `\\sqrt{${arg}}` + case "abs": + return `\\left|${args.join(", ")}\\right|` + case "floor": + return `\\left\\lfloor{${args.join(", ")}}\\right\\rfloor` + case "ceil": + return `\\left\\lceil{${args.join(", ")}}\\right\\rceil` + default: + return `\\mathrm{${f}}\\left(${args.join(", ")}\\right)` + } + } + + /** + * 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.replaceAll(unicodechars[i], "$" + equivalchars[i] + "$") + } + } else { + for(let i = 0; i < unicodechars.length; i++) { + if(vari.includes(unicodechars[i])) + vari = vari.replaceAll(unicodechars[i], equivalchars[i]) + } + } + return vari + } + + /** + * Converts expr-eval instructions to a latex string. + * + * @param {Instruction[]} instructions - expr-eval tokens list + * @returns {string} + */ + expression(instructions) { + let nstack = [] + let n1, n2, n3 + let f, args, argCount + for(let item of instructions) { + let type = item.type + + switch(type) { + case Instruction.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(escapeValue).join(", ") + "]") + } else { + nstack.push(escapeValue(item.value)) + } + break + case Instruction.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} " + this.parif(n2, ["+", "-", "*", "/", "!", "^"])) + break + case "[": + nstack.push(n1 + "[" + n2 + "]") + break + default: + throw new EvalError("Unknown operator " + item.value + ".") + } + break + case Instruction.IOP3: // Ternary 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 " + item.value + ".") + } + break + case Instruction.IVAR: + case Instruction.IVARNAME: + nstack.push(this.variable(item.value.toString())) + break + case Instruction.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(this.functionToLatex(f, [this.parif(n1, ["+", "-", "*", "/", "^"])])) + break + } + break + case Instruction.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 Instruction.IMEMBER: + n1 = nstack.pop() + nstack.push(n1 + "." + item.value) + break + case Instruction.IARRAY: + argCount = item.value + args = [] + while(argCount-- > 0) { + args.unshift(nstack.pop()) + } + nstack.push("[" + args.join(", ") + "]") + break + case Instruction.IEXPR: + nstack.push("(" + this.expression(item.value) + ")") + break + case Instruction.IENDSTATEMENT: + break + default: + throw new EvalError("invalid Expression") + } + } + return String(nstack[0]) + } +} + +/** @type {LatexAPI} */ +Modules.Latex = Modules.Latex || new LatexAPI() +/** @type {LatexAPI} */ +export default Modules.Latex diff --git a/common/src/module/objects.mjs b/common/src/module/objects.mjs new file mode 100644 index 0000000..e98a15f --- /dev/null +++ b/common/src/module/objects.mjs @@ -0,0 +1,134 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Module } from "./common.mjs" +import { textsub } from "../utils/index.mjs" + +class ObjectsAPI extends Module { + + constructor() { + super("Objects") + + /** + * List of object constructors. + * @type {Object.} + */ + this.types = {} + /** + * List of objects for each type of object. + * @type {Object.} + */ + this.currentObjects = {} + /** + * List of objects matched by their name. + * @type {Object.} + */ + this.currentObjectsByName = {} + } + + /** + * Creates a new name for an object, based on the allowedLetters. + * If variables with each of the allowedLetters is created, a subscript + * number is added to the name. + * @param {string} allowedLetters + * @param {string} prefix - Prefix to the name. + * @return {string} New unused name for a new object. + */ + getNewName(allowedLetters, prefix = "") { + // Allows to get a new name, based on the allowed letters, + // as well as adding a sub number when needs be. + let newid = 0 + let ret + do { + let letter = allowedLetters[newid % allowedLetters.length] + let num = Math.floor((newid - (newid % allowedLetters.length)) / allowedLetters.length) + ret = prefix + letter + (num > 0 ? textsub(num - 1) : "") + newid += 1 + } while(ret in this.currentObjectsByName) + return ret + } + + /** + * 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) { + const 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) { + const 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") + return this.getExecutableTypes().flatMap( + elemType => this.currentObjects[elemType].map(obj => obj.name) + ) + 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} Newly created object. + */ + createNewRegisteredObject(objType, args = []) { + if(Object.keys(this.types).indexOf(objType) === -1) return null // Object type does not exist. + const 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 diff --git a/common/src/module/preferences.mjs b/common/src/module/preferences.mjs new file mode 100644 index 0000000..5d20f92 --- /dev/null +++ b/common/src/module/preferences.mjs @@ -0,0 +1,40 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import { Module } from "./common.mjs" +import General from "../preferences/general.mjs" +import Editor from "../preferences/expression.mjs" +import DefaultGraph from "../preferences/default.mjs" + +/** + * Module for application wide settings. + */ +class PreferencesAPI extends Module { + constructor() { + super("Preferences") + + this.categories = { + [QT_TRANSLATE_NOOP("settingCategory", "general")]: General, + [QT_TRANSLATE_NOOP("settingCategory", "editor")]: Editor, + [QT_TRANSLATE_NOOP("settingCategory", "default")]: DefaultGraph + } + } +} + +/** @type {CanvasAPI} */ +Modules.Preferences = Modules.Preferences || new PreferencesAPI() +export default Modules.Preferences diff --git a/common/src/module/settings.mjs b/common/src/module/settings.mjs new file mode 100644 index 0000000..c740383 --- /dev/null +++ b/common/src/module/settings.mjs @@ -0,0 +1,186 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Module } from "./common.mjs" +import { BaseEvent } from "../events.mjs" +import { HelperInterface } from "./interface.mjs" + + +/** + * Base event for when a setting was changed. + */ +class ChangedEvent extends BaseEvent { + /** + * + * @param {string} property - Name of the property that was chagned + * @param {string|number|boolean} oldValue - Old value of the property + * @param {string|number|boolean} newValue - Current (new) value of the property + * @param {boolean} byUser - True if the user is at the source of the change in the setting. + */ + constructor(property, oldValue, newValue, byUser) { + super("changed") + + this.property = property + this.oldValue = oldValue + this.newValue = newValue + this.byUser = byUser + } +} + +/** + * Module for graph settings. + */ +class SettingsAPI extends Module { + static emits = ["changed"] + + #nonConfigurable = ["saveFilename"] + + /** @type {Map} */ + #properties = new Map([ + ["saveFilename", ""], + ["xzoom", 100], + ["yzoom", 10], + ["xmin", .5], + ["ymax", 25], + ["xaxisstep", "4"], + ["yaxisstep", "4"], + ["xlabel", ""], + ["ylabel", ""], + ["linewidth", 1], + ["textsize", 18], + ["logscalex", true], + ["showxgrad", true], + ["showygrad", true] + ]) + + constructor() { + super("Settings", { + helper: HelperInterface + }) + } + + /** + * + * @param {HelperInterface} helper + */ + initialize({ helper }) { + super.initialize({ helper }) + // Initialize default values. + for(const key of this.#properties.keys()) + if(!this.#nonConfigurable.includes(key)) + this.set(key, helper.getSetting("default_graph."+key), false) + } + + /** + * Sets a setting to a given value + * + * @param {string} property + * @param {string|number|boolean} value + * @param {boolean} byUser - Set to true if the user is at the origin of this change. + */ + set(property, value, byUser) { + if(!this.#properties.has(property)) + throw new Error(`Property ${property} is not a setting.`) + const oldValue = this.#properties.get(property) + const propType = typeof oldValue + if(byUser) + console.debug("Setting", property, "from", oldValue, "to", value, `(${typeof value}, ${byUser})`) + if(propType !== typeof value) + throw new Error(`Value of ${property} must be a ${propType} (${typeof value} provided).`) + this.#properties.set(property, value) + const evt = new ChangedEvent(property, oldValue, value, byUser === true) + this.emit(evt) + } + + /** + * Name of the currently opened file. + * @returns {string} + */ + get saveFilename() { return this.#properties.get("saveFilename") } + + /** + * Zoom on the x axis of the diagram. + * @returns {number} + */ + get xzoom() { return this.#properties.get("xzoom") } + /** + * Zoom on the y axis of the diagram. + * @returns {number} + */ + get yzoom() { return this.#properties.get("yzoom") } + /** + * Minimum x of the diagram. + * @returns {number} + */ + get xmin() { return this.#properties.get("xmin") } + /** + * Maximum y of the diagram. + * @returns {number} + */ + get ymax() { return this.#properties.get("ymax") } + /** + * Step of the x axis graduation (expression). + * @note Only available in non-logarithmic mode. + * @returns {string} + */ + get xaxisstep() { return this.#properties.get("xaxisstep") } + /** + * Step of the y axis graduation (expression). + * @returns {string} + */ + get yaxisstep() { return this.#properties.get("yaxisstep") } + /** + * Label used on the x axis. + * @returns {string} + */ + get xlabel() { return this.#properties.get("xlabel") } + /** + * Label used on the y axis. + * @returns {string} + */ + get ylabel() { return this.#properties.get("ylabel") } + /** + * Width of lines that will be drawn into the canvas. + * @returns {number} + */ + get linewidth() { return this.#properties.get("linewidth") } + /** + * Font size of the text that will be drawn into the canvas. + * @returns {number} + */ + get textsize() { return this.#properties.get("textsize") } + /** + * true if the canvas should be in logarithmic mode, false otherwise. + * @returns {boolean} + */ + get logscalex() { return this.#properties.get("logscalex") } + /** + * true if the x graduation should be shown, false otherwise. + * @returns {boolean} + */ + get showxgrad() { return this.#properties.get("showxgrad") } + /** + * true if the y graduation should be shown, false otherwise. + * @returns {boolean} + */ + get showygrad() { return this.#properties.get("showygrad") } +} + +Modules.Settings = Modules.Settings || new SettingsAPI() +export default Modules.Settings + diff --git a/common/src/objs/autoload.mjs b/common/src/objs/autoload.mjs new file mode 100644 index 0000000..7ecac04 --- /dev/null +++ b/common/src/objs/autoload.mjs @@ -0,0 +1,57 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import Objects from "../module/objects.mjs" +import { DrawableObject } from "./common.mjs" +import Point from "./point.mjs" +import Text from "./text.mjs" +import Function from "./function.mjs" +import BodeMagnitude from "./bodemagnitude.mjs" +import BodePhase from "./bodephase.mjs" +import BodeMagnitudeSum from "./bodemagnitudesum.mjs" +import BodePhaseSum from "./bodephasesum.mjs" +import XCursor from "./xcursor.mjs" +import Sequence from "./sequence.mjs" +import DistributionFunction from "./distribution.mjs" + +/** + * Registers the object obj in the object list. + * @param {DrawableObject} obj - Object to be registered. + */ +function registerObject(obj) { + // Registers an object to be used in LogarithmPlotter. + if(obj.prototype instanceof DrawableObject) { + if(!Objects.types[obj.type()]) + Objects.types[obj.type()] = obj + } else { + console.error("Could not register object " + (obj?.type() ?? obj.constructor.name) + ", as it isn't a DrawableObject.") + } +} + +if(Object.keys(Objects.types).length === 0) { + registerObject(Point) + registerObject(Text) + registerObject(Function) + registerObject(BodeMagnitude) + registerObject(BodePhase) + registerObject(BodeMagnitudeSum) + registerObject(BodePhaseSum) + registerObject(XCursor) + registerObject(Sequence) + registerObject(DistributionFunction) +} diff --git a/common/src/objs/bodemagnitude.mjs b/common/src/objs/bodemagnitude.mjs new file mode 100644 index 0000000..4dbcd28 --- /dev/null +++ b/common/src/objs/bodemagnitude.mjs @@ -0,0 +1,162 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { parseDomain, executeExpression, Expression, EmptySet, Domain } from "../math/index.mjs" +import { CreateNewObject } from "../history/index.mjs" +import * as P from "../parameters.mjs" +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" +import History from "../module/history.mjs" + +import { ExecutableObject } from "./common.mjs" +import Function from "./function.mjs" + +export default class BodeMagnitude extends ExecutableObject { + static type() { + return "Gain Bode" + } + + static displayType() { + return qsTranslate("bodemagnitude", "Bode Magnitude") + } + + static displayTypeMultiple() { + return qsTranslate("bodemagnitude", "Bode Magnitudes") + } + + static properties() { + return { + [QT_TRANSLATE_NOOP("prop", "om_0")]: new P.ObjectType("Point"), + [QT_TRANSLATE_NOOP("prop", "pass")]: P.Enum.BodePass, + [QT_TRANSLATE_NOOP("prop", "gain")]: new P.Expression(), + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position, + [QT_TRANSLATE_NOOP("prop", "labelX")]: "number", + [QT_TRANSLATE_NOOP("prop", "omGraduation")]: "boolean" + } + } + + constructor(name = null, visible = true, color = null, labelContent = "name + value", + om_0 = "", pass = "high", gain = "20", labelPosition = "above", labelX = 1, omGraduation = false) { + if(name == null) name = Objects.getNewName("G") + if(name === "G") name = "G₀" // G is reserved for sum of BODE magnitudes (Somme gains Bode). + super(name, visible, color, labelContent) + if(typeof om_0 == "string") { + // Point name or create one + om_0 = Objects.currentObjectsByName[om_0] + if(om_0 == null) { + // Create new point + om_0 = Objects.createNewRegisteredObject("Point", [Objects.getNewName("ω"), true, this.color, "name"]) + History.addToHistory(new CreateNewObject(om_0.name, "Point", om_0.export())) + om_0.update() + labelPosition = "below" + } + om_0.requiredBy.push(this) + } + /** @type {Point} */ + this.om_0 = om_0 + this.pass = pass + if(typeof gain == "number" || typeof gain == "string") gain = new Expression(gain.toString()) + this.gain = gain + this.labelPosition = labelPosition + this.labelX = labelX + this.omGraduation = omGraduation + } + + getReadableString() { + let pass = this.pass === "low" ? qsTranslate("bodemagnitude", "low-pass") : qsTranslate("bodemagnitude", "high-pass") + return `${this.name}: ${pass}; ${this.om_0.name} = ${this.om_0.x}\n ${" ".repeat(this.name.length)}${this.gain.toString(true)} dB/dec` + } + + getLatexString() { + let pass = this.pass === "low" ? qsTranslate("bodemagnitude", "low-pass") : qsTranslate("bodemagnitude", "high-pass") + return `\\mathrm{${Latex.variable(this.name)}:}\\begin{array}{l} + \\textsf{${pass}};${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup} \\\\ + ${this.gain.latexMarkup}\\textsf{ dB/dec} + \\end{array}` + } + + execute(x = 1) { + if(typeof x == "string") x = executeExpression(x) + if((this.pass === "high" && x < this.om_0.x) || (this.pass === "low" && x > this.om_0.x)) { + let dbfn = new Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`) + return dbfn.execute(x) + } else { + return this.om_0.y.execute() + } + } + + simplify(x = 1) { + let xval = x + if(typeof x == "string") xval = executeExpression(x) + if((this.pass === "high" && xval < this.om_0.x.execute()) || (this.pass === "low" && xval > this.om_0.x.execute())) { + let dbfn = new Expression(`${this.gain.execute()}*(ln(x)-ln(${this.om_0.x}))/ln(10)+${this.om_0.y}`) + return dbfn.simplify(x) + } else { + return this.om_0.y.toString() + } + } + + canExecute(x = 1) { + return true + } + + draw(canvas) { + let base = [canvas.x2px(this.om_0.x.execute()), canvas.y2px(this.om_0.y.execute())] + let dbfn = new Expression(`${this.gain.execute()}*(log10(x)-log10(${this.om_0.x}))+${this.om_0.y}`) + let inDrawDom + + if(this.pass === "high") { + // High pass, linear line from beginning, then constant to the end. + canvas.drawLine(base[0], base[1], canvas.width, base[1]) + inDrawDom = parseDomain(`]-inf;${this.om_0.x}[`) + } else { + // Low pass, constant from the beginning, linear line to the end. + canvas.drawLine(base[0], base[1], 0, base[1]) + inDrawDom = parseDomain(`]${this.om_0.x};+inf[`) + } + Function.drawFunction(canvas, dbfn, inDrawDom, Domain.R) + // Dashed line representing break in function + let xpos = canvas.x2px(this.om_0.x.execute()) + let dashPxSize = 10 + for(let i = 0; i < canvas.height && this.omGraduation; i += dashPxSize * 2) + canvas.drawLine(xpos, i, xpos, i + dashPxSize) + + // Label + this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) + } + + update() { + super.update() + /** @type {BodeMagnitudeSum[]} */ + let sumObjs = Objects.currentObjects["Somme gains Bode"] + if(sumObjs !== undefined && sumObjs.length > 0) { + sumObjs[0].recalculateCache() + } else { + Objects.createNewRegisteredObject("Somme gains Bode") + } + } + + delete() { + super.delete() + /** @type {BodeMagnitudeSum[]} */ + let sumObjs = Objects.currentObjects["Somme gains Bode"] + if(sumObjs !== undefined && sumObjs.length > 0) { + sumObjs[0].recalculateCache() + } + } +} diff --git a/common/src/objs/bodemagnitudesum.mjs b/common/src/objs/bodemagnitudesum.mjs new file mode 100644 index 0000000..701a4b9 --- /dev/null +++ b/common/src/objs/bodemagnitudesum.mjs @@ -0,0 +1,158 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Range, Expression, Domain } from "../math/index.mjs" +import * as P from "../parameters.mjs" +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" + +import { ExecutableObject } from "./common.mjs" +import Function from "./function.mjs" + + +export default class BodeMagnitudeSum extends ExecutableObject { + static type() { + return "Somme gains Bode" + } + + static displayType() { + return qsTranslate("bodemagnitudesum", "Bode Magnitudes Sum") + } + + static displayTypeMultiple() { + return qsTranslate("bodemagnitudesum", "Bode Magnitudes Sum") + } + + static createable() { + return false + } + + static properties() { + return { + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position, + [QT_TRANSLATE_NOOP("prop", "labelX")]: "number" + } + } + + constructor(name = null, visible = true, color = null, labelContent = "name + value", + labelPosition = "above", labelX = 1) { + if(name == null) name = "G" + super(name, visible, color, labelContent) + this.labelPosition = labelPosition + this.labelX = labelX + this.recalculateCache() + } + + getReadableString() { + return `${this.name} = ${Objects.getObjectsName("Gain Bode").join(" + ")}` + } + + getLatexString() { + return `${Latex.variable(this.name)} = ${Objects.getObjectsName("Gain Bode").map(name => Latex.variable(name)).join(" + ")}` + } + + execute(x = 0) { + for(let [limitedDrawFunction, inDrawDom] of this.cachedParts) { + if(inDrawDom.includes(x)) { + return limitedDrawFunction.execute(x) + } + } + return null + } + + canExecute(x = 1) { + return true // Should always be true + } + + simplify(x = 1) { + for(let [limitedDrawFunction, inDrawDom] of this.cachedParts) { + if(inDrawDom.includes(x)) { + return limitedDrawFunction.simplify(x) + } + } + return "" + } + + recalculateCache() { + this.cachedParts = [] + // Calculating this is fairly resource expansive so it's cached. + let magnitudeObjects = Objects.currentObjects["Gain Bode"] + if(magnitudeObjects === undefined || magnitudeObjects.length < 1) { + Objects.deleteObject(this.name) + } else { + console.log("Recalculating cache gain") + // Minimum to draw (can be expended if needed, just not infinite or it'll cause issues. + const MIN_DRAW = 1e-20 + // Format: [[x value of where the filter transitions, magnitude, high-pass (bool)]] + const magnitudes = [] + const XVALUE = 0 + const MAGNITUDE = 1 + const PASS = 2 + magnitudes.push([Number.MAX_VALUE, 0, true]) // Draw the ending section + // Collect data from current magnitude (or gain in French) objects. + let baseY = 0 + for(/** @type {BodeMagnitude} */ let magnitudeObj of magnitudeObjects) { // Sorting by their om_0 position. + const om0x = magnitudeObj.om_0.x.execute() + magnitudes.push([om0x, magnitudeObj.gain.execute(), magnitudeObj.pass === "high"]) + baseY += magnitudeObj.execute(MIN_DRAW) + } + // Sorting the data by their x transitions value + magnitudes.sort((a, b) => a[XVALUE] - b[XVALUE]) + // Adding the total gains. + let magnitudesBeforeTransition = [] + let magnitudesAfterTransition = [] + let totalMagnitudeAtStart = 0 // Magnitude at the lowest x value (sum of all high-pass magnitudes) + for(let [om0x, magnitude, highpass] of magnitudes) { + if(highpass) { + magnitudesBeforeTransition.push(magnitude) + magnitudesAfterTransition.push(0) + totalMagnitudeAtStart += magnitude + } else { + magnitudesBeforeTransition.push(0) + magnitudesAfterTransition.push(magnitude) + } + } + // Calculating parts + let previousTransitionX = MIN_DRAW + let currentMagnitude = totalMagnitudeAtStart + for(let transitionID = 0; transitionID < magnitudes.length; transitionID++) { + const transitionX = magnitudes[transitionID][XVALUE] + // Create draw function that will be used during drawing. + const limitedDrawFunction = new Expression(`${currentMagnitude}*(ln(x)-ln(${previousTransitionX}))/ln(10)+${baseY}`) + const drawDomain = new Range(previousTransitionX, transitionX, true, false) + this.cachedParts.push([limitedDrawFunction, drawDomain]) + // Prepare default values for next function. + previousTransitionX = transitionX + baseY = limitedDrawFunction.execute(transitionX) + currentMagnitude += magnitudesAfterTransition[transitionID] - magnitudesBeforeTransition[transitionID] + } + } + } + + draw(canvas) { + if(this.cachedParts.length > 0) { + for(let [limitedDrawFunction, drawDomain] of this.cachedParts) { + Function.drawFunction(canvas, limitedDrawFunction, drawDomain, Domain.R) + // Check if necessary to draw label + if(drawDomain.includes(this.labelX)) { + this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) + } + } + } + } +} diff --git a/common/src/objs/bodephase.mjs b/common/src/objs/bodephase.mjs new file mode 100644 index 0000000..75776b1 --- /dev/null +++ b/common/src/objs/bodephase.mjs @@ -0,0 +1,147 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { executeExpression, Expression } from "../math/index.mjs" +import { CreateNewObject } from "../history/index.mjs" +import * as P from "../parameters.mjs" +import Objects from "../module/objects.mjs" +import History from "../module/history.mjs" +import Latex from "../module/latex.mjs" + +import { ExecutableObject } from "./common.mjs" + + +export default class BodePhase extends ExecutableObject { + static type() { + return "Phase Bode" + } + + static displayType() { + return qsTranslate("bodephase", "Bode Phase") + } + + static displayTypeMultiple() { + return qsTranslate("bodephase", "Bode Phases") + } + + static properties() { + return { + [QT_TRANSLATE_NOOP("prop", "om_0")]: new P.ObjectType("Point"), + [QT_TRANSLATE_NOOP("prop", "phase")]: new P.Expression(), + [QT_TRANSLATE_NOOP("prop", "unit")]: new P.Enum("°", "deg", "rad"), + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position, + [QT_TRANSLATE_NOOP("prop", "labelX")]: "number" + } + } + + constructor(name = null, visible = true, color = null, labelContent = "name + value", + om_0 = "", phase = 90, unit = "°", labelPosition = "above", labelX = 1) { + if(name == null) name = Objects.getNewName("φ") + if(name === "φ") name = "φ₀" // φ is reserved for sum of Bode phases. + super(name, visible, color, labelContent) + if(typeof phase == "number" || typeof phase == "string") phase = new Expression(phase.toString()) + this.phase = phase + if(typeof om_0 == "string") { + // Point name or create one + om_0 = Objects.currentObjectsByName[om_0] + if(om_0 == null) { + // Create new point + om_0 = Objects.createNewRegisteredObject("Point", [Objects.getNewName("ω"), this.color, "name"]) + om_0.labelPosition = this.phase.execute() >= 0 ? "above" : "below" + History.addToHistory(new CreateNewObject(om_0.name, "Point", om_0.export())) + labelPosition = "below" + } + om_0.requiredBy.push(this) + } + /** @type {Point} */ + this.om_0 = om_0 + this.unit = unit + this.labelPosition = labelPosition + this.labelX = labelX + } + + getReadableString() { + return `${this.name}: ${this.phase.toString(true)}${this.unit} at ${this.om_0.name} = ${this.om_0.x}` + } + + getLatexString() { + return `${Latex.variable(this.name)}: ${this.phase.latexMarkup}\\textsf{${this.unit} at }${Latex.variable(this.om_0.name)} = ${this.om_0.x.latexMarkup}` + } + + execute(x = 1) { + if(typeof x == "string") x = executeExpression(x) + if(x < this.om_0.x) { + return this.om_0.y.execute() + } else { + return this.om_0.y.execute() + this.phase.execute() + } + } + + simplify(x = 1) { + let xval = x + if(typeof x == "string") xval = executeExpression(x) + if(xval < this.om_0.x) { + return this.om_0.y.toString() + } else { + let newExp = this.om_0.y.toEditableString() + " + " + this.phase.toEditableString() + return new Expression(newExp) + } + } + + canExecute(x = 1) { + return true + } + + draw(canvas) { + let baseX = canvas.x2px(this.om_0.x.execute()) + let omy = this.om_0.y.execute() + let augmt = this.phase.execute() + let baseY = canvas.y2px(omy) + let augmtY = canvas.y2px(omy + augmt) + // Before change line. + canvas.drawLine(0, baseY, Math.min(baseX, canvas.height), baseY) + // Transition line. + canvas.drawLine(baseX, baseY, baseX, augmtY) + // After change line + canvas.drawLine(Math.max(0, baseX), augmtY, canvas.width, augmtY) + + // Label + this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) + } + + update() { + super.update() + /** @type {BodePhaseSum[]} */ + let sumObjs = Objects.currentObjects["Somme phases Bode"] + if(sumObjs !== undefined && sumObjs.length > 0) { + sumObjs[0].recalculateCache() + } else { + Objects.createNewRegisteredObject("Somme phases Bode") + } + } + + delete() { + super.update() + /** @type {BodePhaseSum[]} */ + let sumObjs = Objects.currentObjects["Somme phases Bode"] + if(sumObjs !== undefined && sumObjs.length > 0) { + sumObjs[0].recalculateCache() + } + } +} + diff --git a/common/src/objs/bodephasesum.mjs b/common/src/objs/bodephasesum.mjs new file mode 100644 index 0000000..55b3b6c --- /dev/null +++ b/common/src/objs/bodephasesum.mjs @@ -0,0 +1,139 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { executeExpression, Expression } from "../math/index.mjs" +import * as P from "../parameters.mjs" +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" + +import { ExecutableObject } from "./common.mjs" + +export default class BodePhaseSum extends ExecutableObject { + static type() { + return "Somme phases Bode" + } + + static displayType() { + return qsTranslate("bodephasesum", "Bode Phases Sum") + } + + static displayTypeMultiple() { + return qsTranslate("bodephasesum", "Bode Phases Sum") + } + + static createable() { + return false + } + + static properties() { + return { + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position, + [QT_TRANSLATE_NOOP("prop", "labelX")]: "number" + } + } + + constructor(name = null, visible = true, color = null, labelContent = "name + value", + labelPosition = "above", labelX = 1) { + if(name == null) name = "φ" + super(name, visible, color, labelContent) + this.labelPosition = labelPosition + this.labelX = labelX + this.recalculateCache() + } + + getReadableString() { + return `${this.name} = ${Objects.getObjectsName("Phase Bode").join(" + ")}` + } + + getLatexString() { + return `${Latex.variable(this.name)} = ${Objects.getObjectsName("Phase Bode").map(name => Latex.variable(name)).join(" + ")}` + } + + execute(x = 1) { + if(typeof x == "string") x = executeExpression(x) + for(let i = 0; i < this.om0xList.length - 1; i++) { + if(x >= this.om0xList[i] && x < this.om0xList[i + 1]) return this.phasesList[i] + } + } + + simplify(x = 1) { + let xval = x + if(typeof x == "string") xval = executeExpression(x) + for(let i = 0; i < this.om0xList.length - 1; i++) { + if(xval >= this.om0xList[i] && xval < this.om0xList[i + 1]) { + return (new Expression(this.phasesExprList[i])).simplify() + } + } + return "0" + } + + canExecute(x = 1) { + return true + } + + recalculateCache() { + // Minimum to draw (can be expended if needed, just not infinite or it'll cause issues. + let drawMin = 1e-20 + let drawMax = 1e20 + this.om0xList = [drawMin, drawMax] + this.phasesList = [0] + this.phasesExprList = ["0"] + let phasesDict = new Map() + let phasesExprDict = new Map() + phasesDict.set(drawMax, 0) + + let phaseObjects = Objects.currentObjects["Phase Bode"] + if(phaseObjects === undefined || phaseObjects.length < 1) { + Objects.deleteObject(this.name) + } else { + console.log("Recalculating cache phase") + for(/** @type {BodePhase} */ let obj of phaseObjects) { + this.om0xList.push(obj.om_0.x.execute()) + if(!phasesDict.has(obj.om_0.x.execute())) { + phasesDict.set(obj.om_0.x.execute(), obj.phase.execute()) + phasesExprDict.set(obj.om_0.x.execute(), obj.phase.toEditableString()) + } else { + phasesDict.set(obj.om_0.x.execute(), obj.phase.execute() + phasesDict.get(obj.om_0.x.execute())) + phasesExprDict.set(obj.om_0.x.execute(), obj.phase.toEditableString() + "+" + phasesExprDict.get(obj.om_0.x.execute())) + } + this.phasesList[0] += obj.om_0.y.execute() + this.phasesExprList[0] += "+" + obj.om_0.y.toEditableString() + } + this.om0xList.sort((a, b) => a - b) + for(let i = 1; i < this.om0xList.length; i++) { + this.phasesList[i] = this.phasesList[i - 1] + phasesDict.get(this.om0xList[i]) + this.phasesExprList[i] = this.phasesExprList[i - 1] + "+" + phasesDict.get(this.om0xList[i]) + } + } + } + + draw(canvas) { + for(let i = 0; i < this.om0xList.length - 1; i++) { + let om0xBegin = canvas.x2px(this.om0xList[i]) + let om0xEnd = canvas.x2px(this.om0xList[i + 1]) + let baseY = canvas.y2px(this.phasesList[i]) + let nextY = canvas.y2px(this.phasesList[i + 1]) + canvas.drawLine(om0xBegin, baseY, om0xEnd, baseY) + canvas.drawLine(om0xEnd, baseY, om0xEnd, nextY) + } + + // Label + this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) + } +} + diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/common.js b/common/src/objs/common.mjs similarity index 51% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/common.js rename to common/src/objs/common.mjs index bc31bac..602856a 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/common.js +++ b/common/src/objs/common.mjs @@ -1,118 +1,120 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library - -.import "../utils.js" as Utils -.import "../objects.js" as Objects -.import "../math/latex.js" as Latex -.import "../parameters.js" as P -.import "../math/common.js" as C +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" +import { getRandomColor } from "../utils/index.mjs" +import { ensureTypeSafety, serializesByPropertyType } from "../parameters.mjs" // This file contains the default data to be imported from all other objects -/** - * Creates a new name for an object, based on the \c allowedLetters. - * If variables with each of the allowedLetters is created, a subscript - * number is added to the name. - * @param {string} prefix - Prefix to the name. - * @return {string} New unused name for a new object. - */ -function getNewName(allowedLetters, prefix='') { - // Allows to get a new name, based on the allowed letters, - // as well as adding a sub number when needs be. - var newid = 0 - var ret - do { - var letter = allowedLetters[newid % allowedLetters.length] - var num = Math.floor((newid - (newid % allowedLetters.length)) / allowedLetters.length) - ret = prefix + letter + (num > 0 ? Utils.textsub(num-1) : '') - newid += 1 - } while(ret in Objects.currentObjectsByName) - return ret -} - /** * Class to extend for every type of object that * can be drawn on the canvas. */ -class DrawableObject { +export class DrawableObject { /** * Base name of the object. Needs to be constant over time. * @return {string} Type of the object. */ - static type(){return 'Unknown'} - + static type() { + return "Unknown" + } + /** * Translated name of the object to be shown to the user. * @return {string} */ - static displayType(){return 'Unknown'} - + static displayType() { + return "Unknown" + } + /** * Translated name of the object in plural form to be shown to the user. * @return {string} */ - static displayTypeMultiple(){return 'Unknowns'} - + static displayTypeMultiple() { + return "Unknowns" + } + /** * True if this object can be created by the user, false if it can only * be instantiated by other objects - * @return {bool} + * @return {boolean} */ - static createable() {return true} - + static createable() { + return true + } + /** * List of properties used in the Object Editor. - * + * * Properties are set with key as property name and * value as it's type name (e.g 'numbers', 'string'...), * an Enum for enumerations, an ObjectType for DrawableObjects - * with a specific type, a List instance for lists, a + * with a specific type, a List instance for lists, a * Dictionary instance for dictionaries, an Expression for expressions... - * + * * If the property is to be translated, the key should be passed * through the QT_TRANSLATE_NOOP macro in that form: * [QT_TRANSLATE_NOOP('prop','key')] - * Enums that are translated should be indexed in parameters.js and + * Enums that are translated should be indexed in parameters.mjs and * then be linked directly here. - * - * @return {Dictionary} + * + * @return {Object.} */ - static properties() {return {}} - + static properties() { + return {} + } + /** * True if this object can be executed, so that an y value might be computed * for an x value. Only ExecutableObjects have that property set to true. - * @return {bool} + * @return {boolean} */ - static executable() {return false} - + static executable() { + return false + } + + /** + * Imports the object from its serialized form. + * @return {DrawableObject} + */ + static import(name, visible, color, labelContent, ...args) { + let importedArgs = [name.toString(), visible === true, color.toString(), labelContent] + console.log("Importing", this.type(), name, args) + for(let [name, propType] of Object.entries(this.properties())) + if(!name.startsWith("comment")) { + importedArgs.push(ensureTypeSafety(propType, args[importedArgs.length - 4])) + } + return new this(...importedArgs) + } + /** * Base constructor for the object. * @param {string} name - Name of the object - * @param {bool} visible - true if the object is visible, false otherwise. - * @param {color} color - Color of the object (can be string or QColor) - * @param {Enum} labelContent - One of 'null', 'name' or 'name + value' describing the content of the label. - * @constructor() + * @param {boolean} visible - true if the object is visible, false otherwise. + * @param {color|string} color - Color of the object (can be string or QColor) + * @param {"null"|"name"|"name + value"} labelContent - One of 'null', 'name' or 'name + value' describing the content of the label. + * @constructor */ - constructor(name, visible = true, color = null, labelContent = 'name + value') { - if(color == null) color = Utils.getRandomColor() + constructor(name, visible = true, color = null, labelContent = "name + value") { + if(color == null) color = getRandomColor() this.type = this.constructor.type() this.name = name this.visible = visible @@ -121,17 +123,20 @@ class DrawableObject { this.requiredBy = [] this.requires = [] } - + /** - * Serilizes the object in an array that can be JSON serialized. + * Serializes the object in an array that can be JSON serialized. * These parameters will be re-entered in the constructor when restored. * @return {array} */ export() { - // Should return what will be inputed as arguments when a file is loaded (serializable form) - return [this.name, this.visible, this.color.toString(), this.labelContent] + let exportList = [this.name, this.visible, this.color.toString(), this.labelContent] + for(let [name, propType] of Object.entries(this.constructor.properties())) + if(!name.startsWith("comment")) + exportList.push(serializesByPropertyType(propType, this[name])) + return exportList } - + /** * String representing the object that will be displayed to the user. * It allows for 2 lines and translated text, but it shouldn't be too long. @@ -140,53 +145,53 @@ class DrawableObject { getReadableString() { return `${this.name} = Unknown` } - + /** * Latex markuped version of the readable string. - * Every non latin character should be passed as latex symbols and formulas + * Every non latin character should be passed as latex symbols and formulas * should be in latex form. - * See ../latex.js for helper methods. + * See ../latex.mjs for helper methods. * @return {string} */ getLatexString() { return this.getReadableString() } - + /** - * Readable string content of the label depending on the value of the \c latexContent. + * Readable string content of the label depending on the value of the latexContent. * @return {string} */ getLabel() { switch(this.labelContent) { - case 'name': + case "name": return this.name - case 'name + value': + case "name + value": return this.getReadableString() - case 'null': - return '' - + case "null": + return "" + } } - + /** - * Latex markup string content of the label depending on the value of the \c latexContent. - * Every non latin character should be passed as latex symbols and formulas + * Latex markup string content of the label depending on the value of the latexContent. + * Every non latin character should be passed as latex symbols and formulas * should be in latex form. - * See ../latex.js for helper methods. + * See ../latex.mjs for helper methods. * @return {string} */ getLatexLabel() { switch(this.labelContent) { - case 'name': + case "name": return Latex.variable(this.name) - case 'name + value': + case "name + value": return this.getLatexString() - case 'null': - return '' - + case "null": + return "" + } } - + /** * Returns the recursive list of objects this one depends on. * @return {array} @@ -197,32 +202,33 @@ class DrawableObject { dependencies = dependencies.concat(obj.getDependenciesList()) return dependencies } - + /** * Callback method when one of the properties of the object is updated. */ update() { // Refreshing dependencies. for(let obj of this.requires) - obj.requiredBy = obj.requiredBy.filter(dep => dep != this) + obj.requiredBy = obj.requiredBy.filter(dep => dep !== this) // Checking objects this one depends on this.requires = [] + let currentObjectsByName = Objects.currentObjectsByName let properties = this.constructor.properties() for(let property in properties) - if(typeof properties[property] == 'object' && 'type' in properties[property]) - if(properties[property].type == 'Expression' && this[property] != null) { + if(typeof properties[property] == "object" && "type" in properties[property]) + if(properties[property].type === "Expression" && this[property] != null) { // Expressions with dependencies for(let objName of this[property].requiredObjects()) { - if(objName in C.currentObjectsByName && !this.requires.includes(C.currentObjectsByName[objName])) { - this.requires.push(C.currentObjectsByName[objName]) - C.currentObjectsByName[objName].requiredBy.push(this) + if(objName in currentObjectsByName && !this.requires.includes(currentObjectsByName[objName])) { + this.requires.push(currentObjectsByName[objName]) + currentObjectsByName[objName].requiredBy.push(this) } } - if(this[property].cached && this[property].requiredObjects().length > 0) + if(this[property].canBeCached && this[property].requiredObjects().length > 0) // Recalculate this[property].recache() - - } else if(properties[property].type == 'ObjectType' && this[property] != null) { + + } else if(properties[property].type === "ObjectType" && this[property] != null) { // Object dependency this.requires.push(this[property]) this[property].requiredBy.push(this) @@ -231,7 +237,7 @@ class DrawableObject { for(let req of this.requiredBy) req.update() } - + /** * Callback method when the object is about to get deleted. */ @@ -239,175 +245,174 @@ class DrawableObject { for(let toRemove of this.requiredBy) { // Normally, there should be none here, but better leave nothing just in case. Objects.deleteObject(toRemove.name) } + for(let toRemoveFrom of this.requires) { + toRemoveFrom.requiredBy = toRemoveFrom.requiredBy.filter(o => o !== this) + } } - + /** - * Abstract method. Draw the object onto the \c canvas with the 2D context \c ctx. - * @param {Canvas} canvas - * @param {Context2D} ctx + * Abstract method. Draw the object onto the canvas with the. + * @param {CanvasAPI} canvas */ - draw(canvas, ctx) {} - + draw(canvas) { + } + /** - * Applicates a \c drawFunction with two position arguments depending on - * both the \c posX and \c posY of where the label should be displayed, - * and the \c labelPosition which declares the label should be displayed + * Applicates a drawFunction with two position arguments depending on + * both the posX and posY of where the label should be displayed, + * and the labelPosition which declares the label should be displayed * relatively to that position. - * + * * @param {string|Enum} labelPosition - Position of the label relative to the marked position * @param {number} offset - Margin between the position and the object to be drawn - * @param {Dictionary} size - Size of the label item, containing two properties, "width" and "height" - * @param {number} posX - Component of the marked position on the x-axis - * @param {number} posY - Component of the marked position on the y-axis - * @param {function} drawFunction - Function with two arguments (x, y) that will be called to draw the label + * @param {{width: number, height: number}} size - Size of the label item, containing two properties, "width" and "height" + * @param {number} posX - Component of the marked position on the x-axis + * @param {number} posY - Component of the marked position on the y-axis + * @param {function} drawFunction - Function with two arguments (x, y) that will be called to draw the label */ drawPositionDivergence(labelPosition, offset, size, posX, posY, drawFunction) { switch(labelPosition) { - case 'center': - drawFunction(posX-size.width/2, posY-size.height/2) - break; - case 'top': - case 'above': - drawFunction(posX-size.width/2, posY-size.height-offset) - break; - case 'bottom': - case 'below': - drawFunction(posX-size.width/2, posY+offset) - break; - case 'left': - drawFunction(posX-size.width-offset, posY-size.height/2) - break; - case 'right': - drawFunction(posX+offset, posY-size.height/2) - break; - case 'top-left': - case 'above-left': - drawFunction(posX-size.width, posY-size.height-offset) - break; - case 'top-right': - case 'above-right': - drawFunction(posX+offset, posY-size.height-offset) - break; - case 'bottom-left': - case 'below-left': - drawFunction(posX-size.width-offset, posY+offset) - break; - case 'bottom-right': - case 'below-right': - drawFunction(posX+offset, posY+offset) - break; + case "center": + drawFunction(posX - size.width / 2, posY - size.height / 2) + break + case "top": + case "above": + drawFunction(posX - size.width / 2, posY - size.height - offset) + break + case "bottom": + case "below": + drawFunction(posX - size.width / 2, posY + offset) + break + case "left": + drawFunction(posX - size.width - offset, posY - size.height / 2) + break + case "right": + drawFunction(posX + offset, posY - size.height / 2) + break + case "top-left": + case "above-left": + drawFunction(posX - size.width, posY - size.height - offset) + break + case "top-right": + case "above-right": + drawFunction(posX + offset, posY - size.height - offset) + break + case "bottom-left": + case "below-left": + drawFunction(posX - size.width - offset, posY + offset) + break + case "bottom-right": + case "below-right": + drawFunction(posX + offset, posY + offset) + break } } - + /** - * Automatically draw text (by default the label of the object on the \c canvas with - * the 2D context \c ctx depending on user settings. - * This method takes into account both the \c posX and \c posY of where the label - * should be displayed, including the \c labelPosition relative to it. - * The text is get both through the \c getLatexFunction and \c getTextFunction + * Automatically draw text (by default the label of the object on the canvas + * depending on user settings. + * This method takes into account both the posX and posY of where the label + * should be displayed, including the labelPosition relative to it. + * The text is get both through the getLatexFunction and getTextFunction * depending on whether to use latex. - * Then, it's displayed using the \c drawFunctionLatex (x,y,imageData) and - * \c drawFunctionText (x,y,text) depending on whether to use latex. - * - * @param {Canvas} canvas - * @param {Context2D} ctx + * Then, it's displayed using the drawFunctionLatex (x,y,imageData) and + * drawFunctionText (x,y,text) depending on whether to use latex. + * + * @param {CanvasAPI} canvas * @param {string|Enum} labelPosition - Position of the label relative to the marked position - * @param {number} posX - Component of the marked position on the x-axis - * @param {number} posY - Component of the marked position on the y-axis - * @param {bool} forceText - Force the rendering of the label as text + * @param {number} posX - Component of the marked position on the x-axis + * @param {number} posY - Component of the marked position on the y-axis + * @param {boolean} forceText - Force the rendering of the label as text * @param {function|null} getLatexFunction - Function (no argument) to get the latex markup to be displayed * @param {function|null} getTextFunction - Function (no argument) to get the text to be displayed * @param {function|null} drawFunctionLatex - Function (x,y,imageData) to display the latex image * @param {function|null} drawFunctionText - Function (x,y,text,textSize) to display the text */ - drawLabel(canvas, ctx, labelPosition, posX, posY, forceText = false, - getLatexFunction = null, getTextFunction = null, drawFunctionLatex = null, drawFunctionText = null) { + drawLabel(canvas, labelPosition, posX, posY, forceText = false, + getLatexFunction = null, getTextFunction = null, drawFunctionLatex = null, drawFunctionText = null) { // Default functions if(getLatexFunction == null) getLatexFunction = this.getLatexLabel.bind(this) if(getTextFunction == null) getTextFunction = this.getLabel.bind(this) if(drawFunctionLatex == null) - drawFunctionLatex = (x,y,ltxImg) => canvas.drawVisibleImage(ctx, ltxImg.source, x, y, ltxImg.width, ltxImg.height) + drawFunctionLatex = (x, y, ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, y, ltxImg.width, ltxImg.height) if(drawFunctionText == null) - drawFunctionText = (x,y,text,textSize) => canvas.drawVisibleText(ctx, text, x, y+textSize.height) // Positioned from left bottom + drawFunctionText = (x, y, text, textSize) => canvas.drawVisibleText(text, x, y + textSize.height) // Positioned from left bottom // Drawing the label - let offset - if(!forceText && Latex.enabled) { // TODO: Check for user setting with Latex. + if(!forceText && Latex.enabled) { // With latex - let drawLblCb = function(canvas, ctx, ltxImg) { - this.drawPositionDivergence(labelPosition, 8+ctx.lineWidth/2, ltxImg, posX, posY, (x,y) => drawFunctionLatex(x,y,ltxImg)) - } - let ltxLabel = getLatexFunction(); - if(ltxLabel != "") - canvas.renderLatexImage(ltxLabel, this.color, drawLblCb.bind(this)) + let drawLblCb = ((ltxImg) => { + this.drawPositionDivergence(labelPosition, 8 + canvas.linewidth / 2, ltxImg, posX, posY, (x, y) => drawFunctionLatex(x, y, ltxImg)) + }) + let ltxLabel = getLatexFunction() + if(ltxLabel !== "") + canvas.renderLatexImage(ltxLabel, this.color, drawLblCb) } else { // Without latex let text = getTextFunction() - ctx.font = `${canvas.textsize}px sans-serif` - let textSize = canvas.measureText(ctx, text) - this.drawPositionDivergence(labelPosition, 8+ctx.lineWidth/2, textSize, posX, posY, (x,y) => drawFunctionText(x,y,text,textSize)) + canvas.font = `${canvas.textsize}px sans-serif` + let textSize = canvas.measureText(text) + this.drawPositionDivergence(labelPosition, 8 + canvas.linewidth / 2, textSize, posX, posY, (x, y) => drawFunctionText(x, y, text, textSize)) } } - + toString() { - return this.name; + return this.name } } + /** - * Class to be extended for every object on which + * Class to be extended for every object on which * an y for a x can be computed with the execute function. * If a value cannot be found during execute, it will * return null. However, theses same x values will * return false when passed to canExecute. */ -class ExecutableObject extends DrawableObject { +export class ExecutableObject extends DrawableObject { /** * Returns the corresponding y value for an x value. * If the object isn't defined on the given x, then * this function will return null. - * + * * @param {number} x * @returns {number|null} */ - execute(x = 1) {return 0} + execute(x = 1) { + return 0 + } + /** * Returns false if the object isn't defined on the given x, true otherwise. - * + * * @param {number} x - * @returns {bool} + * @returns {boolean} */ - canExecute(x = 1) {return true} + canExecute(x = 1) { + return true + } + /** * Returns the simplified expression string for a given x. - * + * * @param {number} x - * @returns {bool} + * @returns {string|Expression} */ - simplify(x = 1) {return '0'} - - + simplify(x = 1) { + return "0" + } + /** * True if this object can be executed, so that an y value might be computed * for an x value. Only ExecutableObjects have that property set to true. - * @return {bool} + * @return {boolean} */ - static executable() {return true} -} - - -/** - * Registers the object \c obj in the object list. - * @param {DrawableObject} obj - Object to be registered. - */ -function registerObject(obj) { - // Registers an object to be used in LogarithmPlotter. - // This function is called from autoload.js - if(obj.prototype instanceof DrawableObject) { - Objects.types[obj.type()] = obj - } else { - console.error("Could not register object " + (obj.type()) + ", as it isn't a DrawableObject.") + static executable() { + return true } } + + + diff --git a/common/src/objs/distribution.mjs b/common/src/objs/distribution.mjs new file mode 100644 index 0000000..b63717c --- /dev/null +++ b/common/src/objs/distribution.mjs @@ -0,0 +1,159 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import * as P from "../parameters.mjs" +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" + +import { ExecutableObject } from "./common.mjs" + + +export default class DistributionFunction extends ExecutableObject { + static type() { + return "Repartition" + } + + static displayType() { + return qsTranslate("distribution", "Repartition") + } + + static displayTypeMultiple() { + return qsTranslate("distribution", "Repartition functions") + } + + static properties() { + return { + "comment1": QT_TRANSLATE_NOOP( + "comment", + "Note: Specify the probability for each value." + ), + [QT_TRANSLATE_NOOP("prop", "probabilities")]: new P.Dictionary("string", "double", /^-?[\d.,]+$/, /^-?[\d.,]+$/, "P({name_} = ", ") = "), + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position, + [QT_TRANSLATE_NOOP("prop", "labelX")]: "number" + } + } + + static import(name, visible, color, labelContent, ...args) { + console.log(args, args.length) + if(args.length === 5) { + // Two legacy values no longer included. + args.shift() + args.shift() + } + return super.import(name, visible, color, labelContent, ...args) + } + + constructor(name = null, visible = true, color = null, labelContent = "name + value", + probabilities = { "0": "0" }, labelPosition = "above", labelX = 1) { + if(name == null) name = Objects.getNewName("XYZUVW", "F_") + super(name, visible, color, labelContent) + this.probabilities = probabilities + this.labelPosition = labelPosition + this.labelX = labelX + this.update() + } + + getReadableString() { + let keys = Object.keys(this.probabilities).sort((a, b) => a - b) + let varname = this.name.substring(this.name.indexOf("_") + 1) + return `${this.name}(x) = P(${varname} ≤ x)\n` + keys.map(idx => `P(${varname}=${idx})=${this.probabilities[idx]}`).join("; ") + } + + getLatexString() { + let keys = Object.keys(this.probabilities).sort((a, b) => a - b) + let funcName = Latex.variable(this.name) + let varName = Latex.variable(this.name.substring(this.name.indexOf("_") + 1)) + return `\\begin{array}{l}{${funcName}}(x) = P(${varName} \\le x)\\\\` + keys.map(idx => `P(${varName}=${idx})=${this.probabilities[idx]}`).join("; ") + "\\end{array}" + } + + execute(x = 1) { + let ret = 0 + Object.keys(this.probabilities).sort((a, b) => a - b).forEach(idx => { + if(x >= idx) ret += parseFloat(this.probabilities[idx].replace(/,/g, ".")) + }) + return ret + } + + canExecute(x = 1) { + return true + } + + // Simplify returns the simplified string of the expression. + simplify(x = 1) { + return this.execute(x).toString() + } + + getLabel() { + switch(this.labelContent) { + case "name": + return `${this.name}(x)` + case "name + value": + return this.getReadableString() + case "null": + return "" + } + } + + draw(canvas) { + let currentY = 0 + let keys = Object.keys(this.probabilities).map(idx => parseInt(idx)).sort((a, b) => a - b) + if(canvas.isVisible(keys[0], this.probabilities[keys[0]].replace(/,/g, "."))) { + canvas.drawLine(0, canvas.y2px(0), canvas.x2px(keys[0]), canvas.y2px(0)) + if(canvas.isVisible(keys[0], 0)) { + canvas.arc(canvas.x2px(keys[0]) + 4, canvas.y2px(0), 4, Math.PI / 2, 3 * Math.PI / 2) + } + } + for(let i = 0; i < keys.length - 1; i++) { + let idx = keys[i] + currentY += parseFloat(this.probabilities[idx].replace(/,/g, ".")) + if(canvas.isVisible(idx, currentY) || canvas.isVisible(keys[i + 1], currentY)) { + canvas.drawLine( + Math.max(0, canvas.x2px(idx)), + canvas.y2px(currentY), + Math.min(canvas.width, canvas.x2px(keys[i + 1])), + canvas.y2px(currentY) + ) + if(canvas.isVisible(idx, currentY)) { + canvas.disc(canvas.x2px(idx), canvas.y2px(currentY), 4) + } + if(canvas.isVisible(keys[i + 1], currentY)) { + canvas.arc(canvas.x2px(keys[i + 1]) + 4, canvas.y2px(currentY), 4, Math.PI / 2, 3 * Math.PI / 2) + } + } + } + if(canvas.isVisible(keys[keys.length - 1], currentY + parseFloat(this.probabilities[keys[keys.length - 1]]))) { + canvas.drawLine( + Math.max(0, canvas.x2px(keys[keys.length - 1])), + canvas.y2px(currentY + parseFloat(this.probabilities[keys[keys.length - 1]].replace(/,/g, "."))), + canvas.width, + canvas.y2px(currentY + parseFloat(this.probabilities[keys[keys.length - 1]].replace(/,/g, "."))) + ) + canvas.disc( + canvas.x2px(keys[keys.length - 1]), + canvas.y2px( + currentY + parseFloat(this.probabilities[keys[keys.length - 1]].replace(/,/g, ".")) + ), + 4 + ) + } + + // Label + this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) + } +} + diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/function.js b/common/src/objs/function.mjs similarity index 51% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/function.js rename to common/src/objs/function.mjs index aa43a08..fec90ac 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/function.js +++ b/common/src/objs/function.mjs @@ -1,64 +1,74 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library - -.import "common.js" as Common -.import "../utils.js" as Utils -.import "../mathlib.js" as MathLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex +import { textsub } from "../utils/index.mjs" +import Objects from "../module/objects.mjs" +import { ExecutableObject } from "./common.mjs" +import { parseDomain, Expression, SpecialDomain } from "../math/index.mjs" +import * as P from "../parameters.mjs" +import Latex from "../module/latex.mjs" -class Function extends Common.ExecutableObject { - static type(){return 'Function'} - static displayType(){return qsTr('Function')} - static displayTypeMultiple(){return qsTr('Functions')} - static properties() {return { - [QT_TRANSLATE_NOOP('prop','expression')]: new P.Expression('x'), - [QT_TRANSLATE_NOOP('prop','definitionDomain')]: 'Domain', - [QT_TRANSLATE_NOOP('prop','destinationDomain')]: 'Domain', - 'comment1': QT_TRANSLATE_NOOP( - 'comment', - 'Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5}' - ), - [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, - [QT_TRANSLATE_NOOP('prop','displayMode')]: P.Enum.FunctionDisplayType, - [QT_TRANSLATE_NOOP('prop','labelX')]: 'number', - 'comment2': QT_TRANSLATE_NOOP( - 'comment', - 'The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...)' - ), - [QT_TRANSLATE_NOOP('prop','drawPoints')]: 'boolean', - [QT_TRANSLATE_NOOP('prop','drawDashedLines')]: 'boolean' - }} - - constructor(name = null, visible = true, color = null, labelContent = 'name + value', - expression = 'x', definitionDomain = 'RPE', destinationDomain = 'R', - displayMode = 'application', labelPosition = 'above', labelX = 1, - drawPoints = true, drawDashedLines = true) { - if(name == null) name = Common.getNewName('fghjqlmnopqrstuvwabcde') +export default class Function extends ExecutableObject { + static type() { + return "Function" + } + + static displayType() { + return qsTranslate("function", "Function") + } + + static displayTypeMultiple() { + return qsTranslate("function", "Functions") + } + + static properties() { + return { + [QT_TRANSLATE_NOOP("prop", "expression")]: new P.Expression("x"), + [QT_TRANSLATE_NOOP("prop", "definitionDomain")]: "Domain", + [QT_TRANSLATE_NOOP("prop", "destinationDomain")]: "Domain", + "comment1": QT_TRANSLATE_NOOP( + "comment", + "Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5}" + ), + [QT_TRANSLATE_NOOP("prop", "displayMode")]: P.Enum.FunctionDisplayType, + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position, + [QT_TRANSLATE_NOOP("prop", "labelX")]: "number", + "comment2": QT_TRANSLATE_NOOP( + "comment", + "The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...)" + ), + [QT_TRANSLATE_NOOP("prop", "drawPoints")]: "boolean", + [QT_TRANSLATE_NOOP("prop", "drawDashedLines")]: "boolean" + } + } + + constructor(name = null, visible = true, color = null, labelContent = "name + value", + expression = "x", definitionDomain = "RPE", destinationDomain = "R", + displayMode = "application", labelPosition = "above", labelX = 1, + drawPoints = true, drawDashedLines = true) { + if(name == null) name = Objects.getNewName("fghjqlmnopqrstuvwabcde") super(name, visible, color, labelContent) - if(typeof expression == 'number' || typeof expression == 'string') expression = new MathLib.Expression(expression.toString()) + if(typeof expression == "number" || typeof expression == "string") expression = new Expression(expression.toString()) this.expression = expression - if(typeof definitionDomain == 'string') definitionDomain = MathLib.parseDomain(definitionDomain) + if(typeof definitionDomain == "string") definitionDomain = parseDomain(definitionDomain) this.definitionDomain = definitionDomain - if(typeof destinationDomain == 'string') destinationDomain = MathLib.parseDomain(destinationDomain) + if(typeof destinationDomain == "string") destinationDomain = parseDomain(destinationDomain) this.destinationDomain = destinationDomain this.displayMode = displayMode this.labelPosition = labelPosition @@ -66,76 +76,74 @@ class Function extends Common.ExecutableObject { this.drawPoints = drawPoints this.drawDashedLines = drawDashedLines } - + getReadableString() { - if(this.displayMode == 'application') { - return `${this.name}: ${this.definitionDomain} ⟶ ${this.destinationDomain}\n ${' '.repeat(this.name.length)}x ⟼ ${this.expression.toString()}` + if(this.displayMode === "application") { + return `${this.name}: ${this.definitionDomain} ⟶ ${this.destinationDomain}\n ${" ".repeat(this.name.length)}x ⟼ ${this.expression.toString()}` } else { - return `${this.name}(x) = ${this.expression.toString()}\nD${Utils.textsub(this.name)} = ${this.definitionDomain}` + return `${this.name}(x) = ${this.expression.toString()}\nD${textsub(this.name)} = ${this.definitionDomain}` } } - + getLatexString() { - if(this.displayMode == 'application') { + if(this.displayMode === "application") { return `${Latex.variable(this.name)}:\\begin{array}{llll}${this.definitionDomain.latexMarkup}\\textrm{ } & \\rightarrow & \\textrm{ }${this.destinationDomain.latexMarkup}\\\\ x\\textrm{ } & \\mapsto & \\textrm{ }${this.expression.latexMarkup}\\end{array}` } else { return `\\begin{array}{l}${Latex.variable(this.name)}(x) = ${this.expression.latexMarkup}\\\\ D_{${this.name}} = ${this.definitionDomain.latexMarkup}\\end{array}` } } - - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, - this.expression.toEditableString(), this.definitionDomain.toString(), this.destinationDomain.toString(), - this.displayMode, this.labelPosition, this.labelX, this.drawPoints, this.drawDashedLines] - } - + execute(x = 1) { if(this.definitionDomain.includes(x)) return this.expression.execute(x) return null } - + canExecute(x = 1) { return this.definitionDomain.includes(x) } - + simplify(x = 1) { if(this.definitionDomain.includes(x)) return this.expression.simplify(x) - return '' + return "" } - - draw(canvas, ctx) { - Function.drawFunction(canvas, ctx, this.expression, this.definitionDomain, this.destinationDomain, this.drawPoints, this.drawDashedLines) + + draw(canvas) { + Function.drawFunction(canvas, this.expression, this.definitionDomain, this.destinationDomain, this.drawPoints, this.drawDashedLines) // Label - this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) + this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) } - - static drawFunction(canvas, ctx, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true) { - // Reusable in other objects. - // Drawing small traits every 0.2px - var pxprecision = 10 - var previousX = canvas.px2x(0) - var previousY = null; - if(definitionDomain instanceof MathLib.SpecialDomain && definitionDomain.moveSupported) { + + /** + * Reusable in other objects. + * Drawing small traits every few pixels + */ + static drawFunction(canvas, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true) { + let pxprecision = 10 + const startDrawFrom = canvas.x2px(1)%pxprecision-pxprecision + let previousX = canvas.px2x(startDrawFrom) + // console.log("Starting draw from", previousX, startDrawFrom, canvas.x2px(1)) + let previousY = null + if(definitionDomain instanceof SpecialDomain && definitionDomain.moveSupported) { // Point based functions. previousX = definitionDomain.next(previousX) if(previousX === null) previousX = definitionDomain.next(canvas.px2x(0)) previousY = expr.execute(previousX) if(!drawPoints && !drawDash) return - while(previousX !== null && canvas.x2px(previousX) < canvas.canvasSize.width) { + while(previousX !== null && canvas.x2px(previousX) < canvas.width) { // Reconverted for canvas to fix for logarithmic scales. - var currentX = definitionDomain.next(canvas.px2x(canvas.x2px(previousX)+pxprecision)); - var currentY = expr.execute(currentX) - if(currentX === null) break; + let currentX = definitionDomain.next(canvas.px2x(canvas.x2px(previousX) + pxprecision)) + let currentY = expr.execute(currentX) + if(currentX === null) break if((definitionDomain.includes(currentX) || definitionDomain.includes(previousX)) && (destinationDomain.includes(currentY) || destinationDomain.includes(previousY))) { if(drawDash) - canvas.drawDashedLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY)) + canvas.drawDashedLine(canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY)) if(drawPoints) { - ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2) - ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10) + canvas.fillRect(canvas.x2px(previousX) - 5, canvas.y2px(previousY) - 1, 10, 2) + canvas.fillRect(canvas.x2px(previousX) - 1, canvas.y2px(previousY) - 5, 2, 10) } } previousX = currentX @@ -143,8 +151,8 @@ class Function extends Common.ExecutableObject { } if(drawPoints) { // Drawing the last cross - ctx.fillRect(canvas.x2px(previousX)-5, canvas.y2px(previousY)-1, 10, 2) - ctx.fillRect(canvas.x2px(previousX)-1, canvas.y2px(previousY)-5, 2, 10) + canvas.fillRect(canvas.x2px(previousX) - 5, canvas.y2px(previousY) - 1, 10, 2) + canvas.fillRect(canvas.x2px(previousX) - 1, canvas.y2px(previousY) - 5, 2, 10) } } else { // Use max precision if function is trigonometrical on log scale. @@ -154,16 +162,16 @@ class Function extends Common.ExecutableObject { // Calculate the previousY at the start of the canvas if(definitionDomain.includes(previousX)) previousY = expr.execute(previousX) - for(let px = pxprecision; px < canvas.canvasSize.width; px += pxprecision) { + for(let px = pxprecision; px-pxprecision < canvas.width; px += pxprecision) { let currentX = canvas.px2x(px) if(!definitionDomain.includes(previousX) && definitionDomain.includes(currentX)) { // Should draw up to currentX, but NOT at previousX. // Need to find the starting point. - let tmpPx = px-pxprecision + let tmpPx = px - pxprecision do { - tmpPx++; + tmpPx++ previousX = canvas.px2x(tmpPx) - } while(!definitionDomain.includes(previousX)) + } while(!definitionDomain.includes(previousX) && currentX > previousX) // Recaclulate previousY previousY = expr.execute(previousX) } else if(!definitionDomain.includes(currentX)) { @@ -171,17 +179,17 @@ class Function extends Common.ExecutableObject { // Augmenting the pixel precision until this condition is fulfilled. let tmpPx = px do { - tmpPx--; + tmpPx-- currentX = canvas.px2x(tmpPx) - } while(!definitionDomain.includes(currentX) && currentX != previousX) + } while(!definitionDomain.includes(currentX) && currentX > previousX) } // This max variation is needed for functions with asymptotical vertical lines (e.g. 1/x, tan x...) - let maxvariation = (canvas.px2y(0)-canvas.px2y(canvas.canvasSize.height)) + let maxvariation = (canvas.px2y(0) - canvas.px2y(canvas.height)) if(definitionDomain.includes(previousX) && definitionDomain.includes(currentX)) { let currentY = expr.execute(currentX) if(destinationDomain.includes(currentY)) { - if(previousY != null && destinationDomain.includes(previousY) && Math.abs(previousY-currentY) < maxvariation) { - canvas.drawLine(ctx, canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY)) + if(previousY != null && destinationDomain.includes(previousY) && Math.abs(previousY - currentY) < maxvariation) { + canvas.drawLine(canvas.x2px(previousX), canvas.y2px(previousY), canvas.x2px(currentX), canvas.y2px(currentY)) } } previousY = currentY diff --git a/common/src/objs/point.mjs b/common/src/objs/point.mjs new file mode 100644 index 0000000..841308e --- /dev/null +++ b/common/src/objs/point.mjs @@ -0,0 +1,87 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Expression } from "../math/index.mjs" +import * as P from "../parameters.mjs" +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" + +import { DrawableObject } from "./common.mjs" + + +export default class Point extends DrawableObject { + static type() { + return "Point" + } + + static displayType() { + return qsTranslate("point", "Point") + } + + static displayTypeMultiple() { + return qsTranslate("point", "Points") + } + + static properties() { + return { + [QT_TRANSLATE_NOOP("prop", "x")]: new P.Expression(), + [QT_TRANSLATE_NOOP("prop", "y")]: new P.Expression(), + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position, + [QT_TRANSLATE_NOOP("prop", "pointStyle")]: new P.Enum("●", "✕", "+") + } + } + + constructor(name = null, visible = true, color = null, labelContent = "name + value", + x = 1, y = 0, labelPosition = "above", pointStyle = "●") { + if(name == null) name = Objects.getNewName("ABCDEFJKLMNOPQRSTUVW") + super(name, visible, color, labelContent) + if(typeof x == "number" || typeof x == "string") x = new Expression(x.toString()) + this.x = x + if(typeof y == "number" || typeof y == "string") y = new Expression(y.toString()) + this.y = y + this.labelPosition = labelPosition + this.pointStyle = pointStyle + } + + getReadableString() { + return `${this.name} = (${this.x}, ${this.y})` + } + + getLatexString() { + return `${Latex.variable(this.name)} = \\left(${this.x.latexMarkup}, ${this.y.latexMarkup}\\right)` + } + + draw(canvas) { + let [canvasX, canvasY] = [canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute())] + let pointSize = 8 + (canvas.linewidth * 2) + switch(this.pointStyle) { + case "●": + canvas.disc(canvasX, canvasY, pointSize / 2) + break + case "✕": + canvas.drawLine(canvasX - pointSize / 2, canvasY - pointSize / 2, canvasX + pointSize / 2, canvasY + pointSize / 2) + canvas.drawLine(canvasX - pointSize / 2, canvasY + pointSize / 2, canvasX + pointSize / 2, canvasY - pointSize / 2) + break + case "+": + canvas.fillRect(canvasX - pointSize / 2, canvasY - 1, pointSize, 2) + canvas.fillRect(canvasX - 1, canvasY - pointSize / 2, 2, pointSize) + break + } + this.drawLabel(canvas, this.labelPosition, canvasX, canvasY) + } +} diff --git a/common/src/objs/sequence.mjs b/common/src/objs/sequence.mjs new file mode 100644 index 0000000..b424ac0 --- /dev/null +++ b/common/src/objs/sequence.mjs @@ -0,0 +1,140 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Sequence as MathSequence, Domain } from "../math/index.mjs" +import * as P from "../parameters.mjs" +import Latex from "../module/latex.mjs" +import Objects from "../module/objects.mjs" + +import { ExecutableObject } from "./common.mjs" +import Function from "./function.mjs" + + +export default class Sequence extends ExecutableObject { + static type() { + return "Sequence" + } + + static displayType() { + return qsTranslate("sequence", "Sequence") + } + + static displayTypeMultiple() { + return qsTranslate("sequence", "Sequences") + } + + static properties() { + return { + [QT_TRANSLATE_NOOP("prop", "drawPoints")]: "boolean", + [QT_TRANSLATE_NOOP("prop", "drawDashedLines")]: "boolean", + [QT_TRANSLATE_NOOP("prop", "defaultExpression")]: new P.Dictionary("string", "int", /^.+$/, /^\d+$/, "{name}[n+", "] = ", true), + "comment1": QT_TRANSLATE_NOOP( + "comment", + "Note: Use %1[n] to refer to %1ₙ, %1[n+1] for %1ₙ₊₁..." + ), + [QT_TRANSLATE_NOOP("prop", "baseValues")]: new P.Dictionary("string", "int", /^.+$/, /^\d+$/, "{name}[", "] = "), + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Position, + [QT_TRANSLATE_NOOP("prop", "labelX")]: "number" + } + } + + constructor(name = null, visible = true, color = null, labelContent = "name + value", + drawPoints = true, drawDashedLines = true, defaultExp = { 1: "n" }, + baseValues = { 0: 0 }, labelPosition = "above", labelX = 1) { + if(name == null) name = Objects.getNewName("uvwPSUVWabcde") + super(name, visible, color, labelContent) + this.drawPoints = drawPoints + this.drawDashedLines = drawDashedLines + this.defaultExpression = defaultExp + this.baseValues = baseValues + this.labelPosition = labelPosition + this.labelX = labelX + this.update() + } + + update() { + console.log("Updating sequence", this.sequence) + super.update() + if( + this.sequence == null || this.baseValues !== this.sequence.baseValues || + this.sequence.name !== this.name || + this.sequence.expr !== Object.values(this.defaultExpression)[0] || + this.sequence.valuePlus.toString() !== Object.keys(this.defaultExpression)[0] + ) + this.sequence = new MathSequence( + this.name, this.baseValues, + parseFloat(Object.keys(this.defaultExpression)[0]), + Object.values(this.defaultExpression)[0] + ) + } + + getReadableString() { + return this.sequence.toString() + } + + getLatexString() { + return this.sequence.toLatexString() + } + + execute(x = 1) { + if(x % 1 === 0) + return this.sequence.execute(x) + return null + } + + canExecute(x = 1) { + return x % 1 === 0 + } + + // Simplify returns the simplified string of the expression. + simplify(x = 1) { + if(x % 1 === 0) + return this.sequence.simplify(x) + return null + } + + getLabel() { + switch(this.labelContent) { + case "name": + return `(${this.name}ₙ)` + case "name + value": + return this.getReadableString() + case "null": + return "" + } + } + + getLatexLabel() { + switch(this.labelContent) { + case "name": + return `(${Latex.variable(this.name)}_n)` + case "name + value": + return this.getLatexString() + case "null": + return "" + } + } + + draw(canvas) { + Function.drawFunction(canvas, this.sequence, canvas.logscalex ? Domain.NE : Domain.N, Domain.R, this.drawPoints, this.drawDashedLines) + + // Label + this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX))) + } +} + diff --git a/common/src/objs/text.mjs b/common/src/objs/text.mjs new file mode 100644 index 0000000..f4dd993 --- /dev/null +++ b/common/src/objs/text.mjs @@ -0,0 +1,108 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Expression } from "../math/index.mjs" +import * as P from "../parameters.mjs" +import Objects from "../module/objects.mjs" +import Latex from "../module/latex.mjs" + +import { DrawableObject } from "./common.mjs" + + +export default class Text extends DrawableObject { + static type() { + return "Text" + } + + static displayType() { + return qsTranslate("text", "Text") + } + + static displayTypeMultiple() { + return qsTranslate("text", "Texts") + } + + static properties() { + return { + [QT_TRANSLATE_NOOP("prop", "x")]: new P.Expression(), + [QT_TRANSLATE_NOOP("prop", "y")]: new P.Expression(), + [QT_TRANSLATE_NOOP("prop", "labelPosition")]: P.Enum.Positioning, + [QT_TRANSLATE_NOOP("prop", "text")]: "string", + "comment1": QT_TRANSLATE_NOOP( + "comment", + "If you have latex enabled, you can use use latex markup in between $$ to create equations." + ), + [QT_TRANSLATE_NOOP("prop", "disableLatex")]: "boolean" + } + } + + constructor(name = null, visible = true, color = null, labelContent = "null", + x = 1, y = 0, labelPosition = "center", text = "New text", disableLatex = false) { + if(name == null) name = Objects.getNewName("t") + super(name, visible, color, labelContent) + if(typeof x == "number" || typeof x == "string") x = new Expression(x.toString()) + this.x = x + if(typeof y == "number" || typeof y == "string") y = new Expression(y.toString()) + this.y = y + this.labelPosition = labelPosition + this.text = text + this.disableLatex = disableLatex + } + + getReadableString() { + return `${this.name} = "${this.text}"` + } + + latexMarkupText() { + // Check whether the text contains latex escaped elements. + let txt = [] + this.text.split("$$").forEach(function(t) { + txt = txt.concat(Latex.variable(t, true).replace(/\$\$/g, "").split("$")) + }) + let newTxt = txt[0] + let i + // Split between normal text and latex escaped. + for(i = 0; i < txt.length - 1; i++) + if(i & 0x01) // Every odd number + newTxt += "\\textsf{" + Latex.variable(txt[i + 1]) + else + newTxt += "}" + txt[i + 1] + // Finished by a } + if(i & 0x01) + newTxt += "{" + return newTxt + } + + getLatexString() { + return `${Latex.variable(this.name)} = "\\textsf{${this.latexMarkupText()}}"` + } + + getLabel() { + return this.text + } + + getLatexLabel() { + return `\\textsf{${this.latexMarkupText()}}` + } + + draw(canvas) { + let yOffset = this.disableLatex ? canvas.textsize - 4 : 0 + this.drawLabel(canvas, this.labelPosition, canvas.x2px(this.x.execute()), canvas.y2px(this.y.execute()) + yOffset, this.disableLatex) + } +} + diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/xcursor.js b/common/src/objs/xcursor.mjs similarity index 65% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/xcursor.js rename to common/src/objs/xcursor.mjs index cea2864..4ad511c 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/xcursor.js +++ b/common/src/objs/xcursor.mjs @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,26 +16,24 @@ * along with this program. If not, see . */ -.pragma library +import { Expression } from "../math/index.mjs" +import * as P from "../parameters.mjs" +import Latex from "../module/latex.mjs" +import Objects from "../module/objects.mjs" -.import "common.js" as Common -.import "../objects.js" as Objects -.import "../mathlib.js" as MathLib -.import "../parameters.js" as P -.import "../math/latex.js" as Latex +import { DrawableObject } from "./common.mjs" - -class XCursor extends Common.DrawableObject { +export default class XCursor extends DrawableObject { static type(){return 'X Cursor'} - static displayType(){return qsTr('X Cursor')} - static displayTypeMultiple(){return qsTr('X Cursors')} + static displayType(){return qsTranslate("xcursor", 'X Cursor')} + static displayTypeMultiple(){return qsTranslate("xcursor", 'X Cursors')} static properties() {return { - [QT_TRANSLATE_NOOP('prop','x')]: 'Expression', - [QT_TRANSLATE_NOOP('prop','targetElement')]: new P.ObjectType('ExecutableObject'), + [QT_TRANSLATE_NOOP('prop','x')]: new P.Expression(), + [QT_TRANSLATE_NOOP('prop','targetElement')]: new P.ObjectType('ExecutableObject', true), [QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position, [QT_TRANSLATE_NOOP('prop','approximate')]: 'boolean', - [QT_TRANSLATE_NOOP('prop','rounding')]: 'number', + [QT_TRANSLATE_NOOP('prop','rounding')]: 'int', [QT_TRANSLATE_NOOP('prop','displayStyle')]: new P.Enum( '— — — — — — —', '⸺⸺⸺⸺⸺⸺', @@ -47,11 +45,11 @@ class XCursor extends Common.DrawableObject { constructor(name = null, visible = true, color = null, labelContent = 'name + value', x = 1, targetElement = null, labelPosition = 'left', approximate = true, rounding = 3, displayStyle = '— — — — — — —', targetValuePosition = 'Next to target') { - if(name == null) name = Common.getNewName('X') + if(name == null) name = Objects.getNewName('X') super(name, visible, color, labelContent) this.approximate = approximate this.rounding = rounding - if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString()) + if(typeof x == 'number' || typeof x == 'string') x = new Expression(x.toString()) this.x = x this.targetElement = targetElement if(typeof targetElement == "string") { @@ -62,12 +60,6 @@ class XCursor extends Common.DrawableObject { this.targetValuePosition = targetValuePosition } - export() { - return [this.name, this.visible, this.color.toString(), this.labelContent, - this.x.toEditableString(), this.targetElement == null ? null : this.targetElement.name, this.labelPosition, - this.approximate, this.rounding, this.displayStyle, this.targetValuePosition] - } - getReadableString() { if(this.targetElement == null) return `${this.name} = ${this.x.toString()}` return `${this.name} = ${this.x.toString()}\n${this.getTargetValueLabel()}` @@ -80,27 +72,30 @@ class XCursor extends Common.DrawableObject { ${this.getTargetValueLatexLabel()} \\end{array}` } + + getApprox() { + let approx = '' + if(this.approximate) { + approx = (this.targetElement.execute(this.x.execute())) + let intLength = Math.round(approx).toString().length + let rounding = Math.min(this.rounding, approx.toString().length - intLength - 1) + approx = approx.toPrecision(rounding + intLength) + } + return approx + } getTargetValueLabel() { - var t = this.targetElement - var approx = '' - if(this.approximate) { - approx = t.execute(this.x.execute()) - approx = approx.toPrecision(this.rounding + Math.round(approx).toString().length) - } + const t = this.targetElement + const approx = this.getApprox() return `${t.name}(${this.name}) = ${t.simplify(this.x.toEditableString())}` + (this.approximate ? ' ≈ ' + approx : '') } getTargetValueLatexLabel() { - var t = this.targetElement - var approx = '' - if(this.approximate) { - approx = t.execute(this.x.execute()) - approx = approx.toPrecision(this.rounding + Math.round(approx).toString().length) - } - let simpl = t.simplify(this.x.toEditableString()) - return `${Latex.variable(t.name)}(${Latex.variable(this.name)}) = ${simpl.tokens ? Latex.expression(simpl.tokens) : simpl}` + + const t = this.targetElement + const approx = this.getApprox() + const simpl = t.simplify(this.x.toEditableString()) + return `${Latex.variable(t.name)}(${Latex.variable(this.name)}) = ${simpl.latexMarkup ? simpl.latexMarkup : Latex.variable(simpl)}` + (this.approximate ? ' \\simeq ' + approx : '') } @@ -108,16 +103,13 @@ class XCursor extends Common.DrawableObject { switch(this.labelContent) { case 'name': return this.name - break; case 'name + value': switch(this.targetValuePosition) { case 'Next to target': case 'Hidden': return `${this.name} = ${this.x.toString()}` - break; case 'With label': return this.getReadableString() - break; } case 'null': return '' @@ -128,55 +120,48 @@ class XCursor extends Common.DrawableObject { switch(this.labelContent) { case 'name': return Latex.variable(this.name) - break; case 'name + value': switch(this.targetValuePosition) { case 'Next to target': case 'Hidden': return `${Latex.variable(this.name)} = ${this.x.latexMarkup}` - break; case 'With label': return this.getLatexString() - break; } case 'null': return '' } } - draw(canvas, ctx) { + draw(canvas) { let xpos = canvas.x2px(this.x.execute()) switch(this.displayStyle) { case '— — — — — — —': - var dashPxSize = 10 - for(var i = 0; i < canvas.canvasSize.height; i += dashPxSize*2) - canvas.drawLine(ctx, xpos, i, xpos, i+dashPxSize) + canvas.drawDashedLine(xpos, 0, xpos, canvas.height, 20) break; case '⸺⸺⸺⸺⸺⸺': - canvas.drawXLine(ctx, this.x.execute()) + canvas.drawXLine(this.x.execute()) break; case '• • • • • • • • • •': - var pointDistancePx = 10 - var pointSize = 2 - ctx.beginPath(); - for(var i = 0; i < canvas.canvasSize.height; i += pointDistancePx) - ctx.ellipse(xpos-pointSize/2, i-pointSize/2, pointSize, pointSize) - ctx.fill(); + let pointDistancePx = 10 + let pointSize = 2 + for(let i = 0; i < canvas.height; i += pointDistancePx) + canvas.disc(xpos, i, pointSize) break; } // Drawing label at the top of the canvas. - this.drawLabel(canvas, ctx, this.labelPosition, xpos, 0, false, null, null, - (x,y,ltxImg) => canvas.drawVisibleImage(ctx, ltxImg.source, x, 5, ltxImg.width, ltxImg.height), - (x,y,text,textSize) => canvas.drawVisibleText(ctx, text, x, textSize.height+5)) + this.drawLabel(canvas, this.labelPosition, xpos, 0, false, null, null, + (x,y,ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, 5, ltxImg.width, ltxImg.height), + (x,y,text,textSize) => canvas.drawVisibleText(text, x, textSize.height+5)) // Drawing label at the position of the target element. - if(this.targetValuePosition == 'Next to target' && this.targetElement != null) { + if(this.targetValuePosition === 'Next to target' && this.targetElement != null) { let ypos = canvas.y2px(this.targetElement.execute(this.x.execute())) - this.drawLabel(canvas, ctx, this.labelPosition, xpos, ypos, false, + this.drawLabel(canvas, this.labelPosition, xpos, ypos, false, this.getTargetValueLatexLabel.bind(this), this.getTargetValueLabel.bind(this), - (x,y,ltxImg) => canvas.drawVisibleImage(ctx, ltxImg.source, x, y, ltxImg.width, ltxImg.height), - (x,y,text,textSize) => canvas.drawVisibleText(ctx, text, x, y+textSize.height)) + (x,y,ltxImg) => canvas.drawVisibleImage(ltxImg.source, x, y, ltxImg.width, ltxImg.height), + (x,y,text,textSize) => canvas.drawVisibleText(text, x, y+textSize.height)) } } } diff --git a/common/src/parameters.mjs b/common/src/parameters.mjs new file mode 100644 index 0000000..0da11a8 --- /dev/null +++ b/common/src/parameters.mjs @@ -0,0 +1,380 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import { parseDomain, Expression as Expr, Domain } from "./math/index.mjs" +import Objects from "./module/objects.mjs" + +const NONE = class Empty { +} + +let stringValuesValidators = { + "int": [parseInt, (x) => !isNaN(x)], + "double": [parseFloat, (x) => !isNaN(x)], + "string": [(x) => x, () => true] +} +let stringValidatorTypes = Object.keys(stringValuesValidators) + + +class PropertyType { + /** + * Validates if a value corresponds to the current property type, and if so, returns it. + * @param value + * @returns {null|object} + */ + parse(value) { + throw new TypeError(`validate function of ${typeof this} has not been implemented.`) + } + + /** + * Exports the value of this property type. + * @param value + * @returns {string|number|bool|object} + */ + export(value) { + throw new TypeError(`export function of ${typeof this} has not been implemented.`) + } +} + + +export class Expression extends PropertyType { + constructor(...variables) { + super() + this.type = "Expression" + this.variables = variables + } + + toString() { + return this.variables.length === 0 ? "Number" : `Expression(${this.variables.join(", ")})` + } + + parse(value) { + let result = NONE + if(typeof value == "string") + try { + result = new Expr(value) + } catch(e) { + // Silently error and return null + console.trace() + console.log(`Error parsing expression ${value}:`) + console.error(e) + } + return result + } + + export(value) { + if(value instanceof Expr) + return value.toEditableString() + else + throw new TypeError(`Exportation error: ${value} is not an expression.`) + } +} + + +export class Enum extends PropertyType { + constructor(...values) { + super() + this.type = "Enum" + this.values = values + this.legacyValues = {} + this.translatedValues = values.map(x => qsTranslate("parameters", x)) + } + + toString() { + return `${this.type}(${this.values.join(", ")})` + } + + parse(value) { + let result = NONE + if(this.values.includes(value)) + result = value + else if(this.legacyValues[value]) + result = this.legacyValues[value] + return result + } + + export(value) { + if(this.values.includes(value)) + return value + else if(this.legacyValues[value]) + return this.legacyValues[value] + else + throw new TypeError(`Exportation error: ${value} not one of ${this.values.join(", ")}.`) + } +} + + +export class ObjectType extends PropertyType { + constructor(objType, allowNull = false) { + super() + this.type = "ObjectType" + this.objType = objType + this.allowNull = allowNull + } + + toString() { + return this.objType + } + + parse(name) { + let result = NONE + if(typeof name == "string" && name in Objects.currentObjectsByName) { + let obj = Objects.currentObjectsByName[name] + if(obj.type === this.objType || (this.objType === "ExecutableObject" && obj.execute)) { + result = obj + } else { + // Silently error and return null + console.trace() + console.error(new TypeError(`Object ${name} is of not of type ${this.objType}:`)) + } + } else if(this.allowNull && (name == null || name === "null")) + result = null + return result + } + + export(value) { + if(value == null && this.allowNull) + return null + else if(value.type === this.objType || (this.objType === "ExecutableObject" && value.execute)) + return value.name + else + throw new TypeError(`Exportation error: ${value} not a ${this.objType}.`) + } +} + + +export class List extends PropertyType { + constructor(type, format = /^.+$/, label = "", forbidAdding = false) { + super() + // type can be string, int and double. + this.type = "List" + this.valueType = type + if(!stringValidatorTypes.includes(this.valueType)) + throw new TypeError(`${this.valueType} must be one of ${stringValidatorTypes.join(", ")}.`) + this.format = format + this.label = label + this.forbidAdding = forbidAdding + } + + toString() { + return `${this.type}(${this.valueType}:${this.format})` + } + + parse(value) { + let result = NONE + if(typeof value == "object" && value.__proto__ === Array) { + let valid = 0 + for(let v of value) { + if(this.format.test(v)) { + v = stringValuesValidators[this.valueType][0](v) + if(stringValuesValidators[this.valueType][1](v)) + valid++ + } + } + if(valid === value.length) // Ensure every value is valid. + result = value + } + return result + } + + export(value) { + if(typeof value == "object" && value.__proto__ === Array) + return value + else + throw new TypeError(`Exportation error: ${value} not a list.`) + + } +} + + +export class Dictionary extends PropertyType { + constructor(type, keyType = "string", format = /^.+$/, keyFormat = /^.+$/, preKeyLabel = "", postKeyLabel = ": ", forbidAdding = false) { + super() + // type & keyType can be string, int and double. + this.type = "Dict" + this.valueType = type + this.keyType = keyType + this.format = format + this.keyFormat = keyFormat + this.preKeyLabel = preKeyLabel + this.postKeyLabel = postKeyLabel + this.forbidAdding = forbidAdding + } + + toString() { + return `${this.type}(${this.keyType}:${this.keyFormat}: ${this.valueType}:${this.format})` + } + + parse(value) { + let result = NONE + if(typeof value == "object" && value.__proto__ !== Array) { + let dict = [] + for(let [k, v] of Object.entries(value)) { + if(this.format.test(v) && this.keyFormat.test(k)) { + k = stringValuesValidators[this.keyType][0](k) + v = stringValuesValidators[this.valueType][0](v) + if(stringValuesValidators[this.keyType][1](k)) + if(stringValuesValidators[this.valueType][1](v)) + dict[k] = v + } + } + if(Object.keys(dict).length === Object.keys(value).length) + result = value + } + return result + } + + export(value) { + if(typeof value == "object" && value.__proto__ !== Array) + return value + else + throw new TypeError(`Exportation error: ${value} not a dictionary.`) + } +} + +// Common parameters for Enums + +Enum.Position = new Enum( + QT_TRANSLATE_NOOP("parameters", "above"), + QT_TRANSLATE_NOOP("parameters", "below"), + QT_TRANSLATE_NOOP("parameters", "left"), + QT_TRANSLATE_NOOP("parameters", "right"), + QT_TRANSLATE_NOOP("parameters", "above-left"), + QT_TRANSLATE_NOOP("parameters", "above-right"), + QT_TRANSLATE_NOOP("parameters", "below-left"), + QT_TRANSLATE_NOOP("parameters", "below-right") +) +Enum.Position.legacyValues = { + "top": "above", + "bottom": "below", + "top-left": "above-left", + "top-right": "above-right", + "bottom-left": "below-left", + "bottom-right": "below-right" +} + +Enum.Positioning = new Enum( + QT_TRANSLATE_NOOP("parameters", "center"), + QT_TRANSLATE_NOOP("parameters", "top"), + QT_TRANSLATE_NOOP("parameters", "bottom"), + QT_TRANSLATE_NOOP("parameters", "left"), + QT_TRANSLATE_NOOP("parameters", "right"), + QT_TRANSLATE_NOOP("parameters", "top-left"), + QT_TRANSLATE_NOOP("parameters", "top-right"), + QT_TRANSLATE_NOOP("parameters", "bottom-left"), + QT_TRANSLATE_NOOP("parameters", "bottom-right") +) + +Enum.FunctionDisplayType = new Enum( + QT_TRANSLATE_NOOP("parameters", "application"), + QT_TRANSLATE_NOOP("parameters", "function") +) + +Enum.BodePass = new Enum( + QT_TRANSLATE_NOOP("parameters", "high"), + QT_TRANSLATE_NOOP("parameters", "low") +) + + +Enum.XCursorValuePosition = new Enum( + QT_TRANSLATE_NOOP("parameters", "Next to target"), + QT_TRANSLATE_NOOP("parameters", "With label"), + QT_TRANSLATE_NOOP("parameters", "Hidden") +) + +/** + * Ensures whether a provided value is of the corresponding type. + * @param {string|PropertyType} propertyType + * @param {string|number|boolean|array|object}value + * @returns {Object} + */ +export function ensureTypeSafety(propertyType, value) { + let result + let error = false + if(typeof propertyType == "string") + switch(propertyType) { + case "string": + result = value + error = typeof value !== "string" + break + case "number": + result = parseFloat(value) + error = isNaN(result) + break + case "boolean": + result = value + error = value !== true && value !== false + break + case "Domain": + try { + error = typeof value !== "string" + if(!error) + result = parseDomain(value) + } catch(e) { + // Parse domain sometimes returns an empty set when it cannot parse a domain. + // It's okay though, it shouldn't be user parsed value, so returning an empty set + // is okay for a corrupted file, rather than erroring. + console.trace() + console.log(`Error parsing domain ${value}:`) + console.error(e) + error = true + } + break + } + else if(propertyType instanceof PropertyType) { + result = propertyType.parse(value) + error = result === NONE + } else + throw new TypeError(`Importation error: Unknown property type ${propertyType}.`) + if(error) { + console.trace() + throw new TypeError(`Importation error: Couldn't parse ${JSON.stringify(value)} as ${propertyType}.`) + } + return result +} + +/** + * Serializes a property by its type to export into JSON. + * @param {string|PropertyType} propertyType + * @param value + * @returns {Object} + */ +export function serializesByPropertyType(propertyType, value) { + let result + if(typeof propertyType == "string") + switch(propertyType) { + case "string": + result = value.toString() + break + case "number": + result = parseFloat(value) + break + case "boolean": + result = value === true + break + case "Domain": + if(value instanceof Domain) + result = value.toString() + else + throw new TypeError(`Exportation error: ${value} is not a domain.`) + break + } + else if(propertyType instanceof PropertyType) { + result = propertyType.export(value) + } else + throw new TypeError(`Exportation error: Unknown property type ${propertyType}.`) + return result +} \ No newline at end of file diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/README.md b/common/src/parsing/README.md similarity index 84% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/README.md rename to common/src/parsing/README.md index a0ff7e7..8fa8a93 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/README.md +++ b/common/src/parsing/README.md @@ -2,4 +2,4 @@ Here lies the potential new, abandoned, cleaner implementation of the parsing system that was supposed to replace expr-eval.js, but never came to be because it's unfinished. If somebody has the will to finish this, you're welcome to try, as I won't. -Currently, the tokenizer is complete in use to provide tokens for the syntax highlighting. +Currently, the tokenizer is complete in use to provide tokens for the syntax highlighting, and the reference to provide usage. diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js b/common/src/parsing/common.mjs similarity index 71% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js rename to common/src/parsing/common.mjs index 53a5941..998f038 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/common.js +++ b/common/src/parsing/common.mjs @@ -1,49 +1,48 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library -class InputExpression { +export default class InputExpression { constructor(expression) { - this.position = 0; - this.input = expression; + this.position = 0 + this.input = expression } - + next() { - return this.input[this.position++]; + return this.input[this.position++] } - + peek() { - return this.input[this.position]; + return this.input[this.position] } - + skip(char) { - if(!this.atEnd() && this.peek() == char) { - this.position++; + if(!this.atEnd() && this.peek() === char) { + this.position++ } else { - this.raise("Unexpected character " + this.peek() + ". Expected character " + char); + this.raise("Unexpected character " + this.peek() + ". Expected character " + char) } } - + atEnd() { - return this.position >= this.input.length; + return this.position >= this.input.length } - + raise(message) { throw new SyntaxError(message + " at " + this.position + ".") } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/mathlib.js b/common/src/parsing/index.mjs similarity index 55% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/mathlib.js rename to common/src/parsing/index.mjs index 9ecb377..f0fde37 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/mathlib.js +++ b/common/src/parsing/index.mjs @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,26 +16,17 @@ * along with this program. If not, see . */ -.pragma library +import * as Reference from "./reference.mjs" +import * as T from "./tokenizer.mjs" +import InputExpression from "./common.mjs" -.import "math/expression.js" as Expr -.import "math/sequence.js" as Seq +export const Input = InputExpression +export const TokenType = T.TokenType +export const Token = T.Token +export const Tokenizer = T.ExpressionTokenizer -.import "math/domain.js" as Dom - -var Expression = Expr.Expression -var executeExpression = Expr.executeExpression -var Sequence = Seq.Sequence - -// Domains -var Domain = Dom.Domain -var EmptySet = Dom.EmptySet -var Range = Dom.Range -var SpecialDomain = Dom.SpecialDomain -var DomainSet = Dom.DomainSet -var UnionDomain = Dom.UnionDomain -var IntersectionDomain = Dom.IntersectionDomain -var MinusDomain = Dom.MinusDomain - -var parseDomain = Dom.parseDomain -var parseDomainSimple = Dom.parseDomainSimple +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 diff --git a/common/src/parsing/reference.mjs b/common/src/parsing/reference.mjs new file mode 100644 index 0000000..80365aa --- /dev/null +++ b/common/src/parsing/reference.mjs @@ -0,0 +1,174 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import * as Polyfill from "../lib/expr-eval/polyfill.mjs" + +export const CONSTANTS = { + "π": Math.PI, + "pi": Math.PI, + "inf": Infinity, + "infinity": Infinity, + "∞": Infinity, + "e": Math.E +} +export const CONSTANTS_LIST = Object.keys(CONSTANTS) + +export const FUNCTIONS = { + // The functions commented are the one either not implemented + // in the parser, or not to be used for autocompletion. + // Unary operators + //'+': Number, + //'-': (x) => -x, + //'!' + // Other operations + "length": (s) => Array.isArray(s) ? s.length : String(s).length, + // Boolean functions + "not": (x) => !x, + // Math functions + "abs": Math.abs, + "acos": Math.acos, + "acosh": Math.acosh, + "asin": Math.asin, + "asinh": Math.asinh, + "atan": Math.atan, + "atan2": Math.atan2, + "atanh": Math.atanh, + "cbrt": Math.cbrt, + "ceil": Math.ceil, + //'clz32': Math.clz32, + "cos": Math.cos, + "cosh": Math.cosh, + "exp": Math.exp, + "expm1": Math.expm1, + "floor": Math.floor, + //'fround': Math.fround, + "hypot": Math.hypot, + //'imul': Math.imul, + "lg": Math.log10, + "ln": Math.log, + "log": Math.log, + "log10": Math.log10, + "log1p": Math.log1p, + "log2": Math.log2, + "max": Math.max, + "min": Math.min, + "pow": Math.log2, + "random": Math.random, + "round": Math.round, + "sign": Math.sign, + "sin": Math.sin, + "sinh": Math.sinh, + "sqrt": Math.sqrt, + "tan": Math.tan, + "tanh": Math.tanh, + "trunc": Math.trunc, + // Functions in expr-eval, ported here. + "fac": Polyfill.factorial, + "gamma": Polyfill.gamma, + "Γ": Polyfill.gamma, + "roundTo": (x, exp) => Number(x).toFixed(exp), + // 'map': Polyfill.arrayMap, + // 'fold': Polyfill.arrayFold, + // 'filter': Polyfill.arrayFilter, + // 'indexOf': Polyfill.indexOf, + // 'join': Polyfill.arrayJoin, + // Integral & derivative (only here for autocomplete). + "integral": () => 0, // TODO: Implement + "derivative": () => 0 +} +export const FUNCTIONS_LIST = Object.keys(FUNCTIONS) + +export class P { + // Parameter class. + constructor(type, name = "", optional = false, multipleAllowed = false) { + this.name = name + this.type = type + this.optional = optional + this.multipleAllowed = multipleAllowed + } + + toString() { + let base_string = this.type + if(this.name !== "") + base_string = `${this.name}: ${base_string}` + if(this.multipleAllowed) + base_string += "..." + if(!this.optional) + base_string = `<${base_string}>` + else + base_string = `[${base_string}]` + return base_string + } +} + +export let string = new P("string") +export let bool = new P("bool") +export let number = new P("number") +export let array = new P("array") + +export const FUNCTIONS_USAGE = { + "length": [string], + "not": [bool], + // Math functions + "abs": [number], + "acos": [number], + "acosh": [number], + "asin": [number], + "asinh": [number], + "atan": [number], + "atan2": [number], + "atanh": [number], + "cbrt": [number], + "ceil": [number], + //'clz32': [number], + "cos": [number], + "cosh": [number], + "exp": [number], + "expm1": [number], + "floor": [number], + //'fround': [number], + "hypot": [number], + //'imul': [number], + "lg": [number], + "ln": [number], + "log": [number], + "log10": [number], + "log1p": [number], + "log2": [number], + "max": [number, number, new P("numbers", "", true, true)], + "min": [number, number, new P("numbers", "", true, true)], + "pow": [number, new P("number", "exp")], + "random": [number, number], + "round": [number], + "sign": [number], + "sin": [number], + "sinh": [number], + "sqrt": [number], + "tan": [number], + "tanh": [number], + "trunc": [number], + // Functions in expr-eval, ported here. + "fac": [number], + "gamma": [number], + "Γ": [number], + "roundTo": [number, new P("number")], + // Function manipulation + "derivative": [new P("f"), new P("string", "var", true), number], + "integral": [new P("from"), new P("to"), new P("f"), new P("string", "var", true)] +} + diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js b/common/src/parsing/tokenizer.mjs similarity index 59% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js rename to common/src/parsing/tokenizer.mjs index 9a907fa..8725bf3 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/parsing/tokenizer.js +++ b/common/src/parsing/tokenizer.mjs @@ -1,33 +1,32 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library -.import "reference.js" as Reference +import * as Reference from "./reference.mjs" const WHITESPACES = " \t\n\r" -const STRING_LIMITORS = '"\'`'; -const OPERATORS = "+-*/^%?:=!><"; -const PUNCTUTATION = "()[]{},."; +const STRING_LIMITERS = "\"'`" +const OPERATORS = "+-*/^%?:=!><" +const PUNCTUATION = "()[]{},." const NUMBER_CHARS = "0123456789" const IDENTIFIER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789_₀₁₂₃₄₅₆₇₈₉αβγδεζηθκλμξρςστφχψωₐₑₒₓₔₕₖₗₘₙₚₛₜ" -var TokenType = { +export const TokenType = { // Expression type "WHITESPACE": "WHITESPACE", "VARIABLE": "VARIABLE", @@ -40,130 +39,136 @@ var TokenType = { "UNKNOWN": "UNKNOWN" } -class Token { +export class Token { constructor(type, value, startPosition) { - this.type = type; - this.value = value; + this.type = type + this.value = value this.startPosition = startPosition } } -class ExpressionTokenizer { +export class ExpressionTokenizer { + /** + * + * @param {InputExpression} input + * @param {boolean} tokenizeWhitespaces + * @param {boolean} errorOnUnknown + */ constructor(input, tokenizeWhitespaces = false, errorOnUnknown = true) { - this.input = input; - this.currentToken = null; + this.input = input + this.currentToken = null this.tokenizeWhitespaces = tokenizeWhitespaces this.errorOnUnknown = errorOnUnknown } - + skipWhitespaces() { while(!this.input.atEnd() && WHITESPACES.includes(this.input.peek())) - this.input.next(); + this.input.next() } - + readWhitespaces() { - let included = ""; + let included = "" while(!this.input.atEnd() && WHITESPACES.includes(this.input.peek())) { - included += this.input.next(); + included += this.input.next() } - return new Token(TokenType.WHITESPACE, included, this.input.position-included.length) + return new Token(TokenType.WHITESPACE, included, this.input.position - included.length) } - + readString() { - let delimitation = this.input.peek(); - if(STRING_LIMITORS.includes(delimitation)) { + let delimitation = this.input.peek() + if(STRING_LIMITERS.includes(delimitation)) { this.input.skip(delimitation) - let included = ""; - let justEscaped = false; - while(!this.input.atEnd() && (!STRING_LIMITORS.includes(this.input.peek()) || justEscaped)) { - justEscaped = this.input.peek() == "\\" + let included = "" + let justEscaped = false + while(!this.input.atEnd() && (!STRING_LIMITERS.includes(this.input.peek()) || justEscaped)) { + justEscaped = this.input.peek() === "\\" if(!justEscaped) - included += this.input.next(); + included += this.input.next() } this.input.skip(delimitation) - let token = new Token(TokenType.STRING, included, this.input.position-included.length) + let token = new Token(TokenType.STRING, included, this.input.position - included.length) token.limitator = delimitation return token } else { this.input.raise("Unexpected " + delimitation + ". Expected string delimitator") } } - + readNumber() { - let included = ""; - let hasDot = false; - while(!this.input.atEnd() && (NUMBER_CHARS.includes(this.input.peek()) || this.input.peek() == '.')) { - if(this.input.peek() == ".") { + let included = "" + let hasDot = false + while(!this.input.atEnd() && (NUMBER_CHARS.includes(this.input.peek()) || this.input.peek() === ".")) { + if(this.input.peek() === ".") { if(hasDot) this.input.raise("Unexpected '.'. Expected digit") - hasDot = true; + hasDot = true } - included += this.input.next(); + included += this.input.next() } - return new Token(TokenType.NUMBER, included, this.input.position-included.length) + return new Token(TokenType.NUMBER, included, this.input.position - included.length) } - + readOperator() { - let included = ""; + let included = "" while(!this.input.atEnd() && OPERATORS.includes(this.input.peek())) { - included += this.input.next(); + included += this.input.next() } - return new Token(TokenType.OPERATOR, included, this.input.position-included.length) + return new Token(TokenType.OPERATOR, included, this.input.position - included.length) } - + readIdentifier() { - let identifier = ""; + let identifier = "" while(!this.input.atEnd() && IDENTIFIER_CHARS.includes(this.input.peek().toLowerCase())) { - identifier += this.input.next(); + identifier += this.input.next() } if(Reference.CONSTANTS_LIST.includes(identifier.toLowerCase())) { - return new Token(TokenType.CONSTANT, identifier.toLowerCase(), this.input.position-identifier.length) + return new Token(TokenType.CONSTANT, identifier.toLowerCase(), this.input.position - identifier.length) } else if(Reference.FUNCTIONS_LIST.includes(identifier.toLowerCase())) { - return new Token(TokenType.FUNCTION, identifier.toLowerCase(), this.input.position-identifier.length) + return new Token(TokenType.FUNCTION, identifier.toLowerCase(), this.input.position - identifier.length) } else { - return new Token(TokenType.VARIABLE, identifier, this.input.position-identifier.length) + return new Token(TokenType.VARIABLE, identifier, this.input.position - identifier.length) } } - + readNextToken() { if(!this.tokenizeWhitespaces) this.skipWhitespaces() - if(this.input.atEnd()) return null; - let c = this.input.peek(); - if(this.tokenizeWhitespaces && WHITESPACES.includes(c)) return this.readWhitespaces(); - if(STRING_LIMITORS.includes(c)) return this.readString(); - if(NUMBER_CHARS.includes(c)) return this.readNumber(); - if(IDENTIFIER_CHARS.includes(c.toLowerCase())) return this.readIdentifier(); - if(OPERATORS.includes(c)) return this.readOperator(); - if(Reference.CONSTANTS_LIST.includes(c)) return new Token(TokenType.CONSTANT, this.input.next(), this.input.position-1); - if(PUNCTUTATION.includes(c)) return new Token(TokenType.PUNCT, this.input.next(), this.input.position-1); + if(this.input.atEnd()) return null + let c = this.input.peek() + if(this.tokenizeWhitespaces && WHITESPACES.includes(c)) return this.readWhitespaces() + if(STRING_LIMITERS.includes(c)) return this.readString() + if(NUMBER_CHARS.includes(c)) return this.readNumber() + if(IDENTIFIER_CHARS.includes(c.toLowerCase())) return this.readIdentifier() + if(OPERATORS.includes(c)) return this.readOperator() + if(Reference.CONSTANTS_LIST.includes(c)) return new Token(TokenType.CONSTANT, this.input.next(), this.input.position - 1) + if(PUNCTUATION.includes(c)) return new Token(TokenType.PUNCT, this.input.next(), this.input.position - 1) if(this.errorOnUnknown) - this.input.throw("Unknown token character " + c) + this.input.raise("Unknown token character " + c) else - return new Token(TokenType.UNKNOWN, this.input.next(), this.input.position-1); + return new Token(TokenType.UNKNOWN, this.input.next(), this.input.position - 1) } peek() { - if(this.currentToken == null) this.currentToken = this.readNextToken(); - return this.currentToken; + if(this.currentToken == null) this.currentToken = this.readNextToken() + return this.currentToken } next() { - let tmp; + let tmp if(this.currentToken == null) - tmp = this.readNextToken(); + tmp = this.readNextToken() else - tmp = this.currentToken; - this.currentToken = null; - return tmp; + tmp = this.currentToken + this.currentToken = null + return tmp } - + atEnd() { - return this.peek() == null; + return this.peek() == null } - + skip(type) { - let next = this.next(); - if(next.type != type) - input.raise("Unexpected token " + next.type.toLowerCase() + ' "' + next.value + '". Expected ' + type.toLowerCase()); + let next = this.next() + if(next.type !== type) + this.input.raise("Unexpected token " + next.type.toLowerCase() + " \"" + next.value + "\". Expected " + type.toLowerCase()) } } diff --git a/common/src/preferences/common.mjs b/common/src/preferences/common.mjs new file mode 100644 index 0000000..4e3ecc8 --- /dev/null +++ b/common/src/preferences/common.mjs @@ -0,0 +1,136 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { Expression } from "../math/index.mjs" + +class Setting { + constructor(type, name, nameInConfig, icon) { + this.type = type + this.name = name + this.nameInConfig = nameInConfig + this.icon = icon + } + + /** + * Returns the value of the setting. + * @returns {string|boolean|number} + */ + value() { + throw new TypeError(`value of ${this.constructor} not implemented.`) + } + + /** + * Sets the value of the setting + * @param {string|boolean|number|Expression} value + */ + set(value) { + throw new TypeError(`value of ${this.constructor} not implemented.`) + } + + toString() { + return `Setting<${this.type} ${this.name}>` + } +} + +export class BoolSetting extends Setting { + constructor(name, nameInConfig, icon) { + super("bool", name, nameInConfig, icon) + } + + value() { + return Helper.getSetting(this.nameInConfig) + } + + set(value) { + Helper.setSetting(this.nameInConfig, value === true) + } +} + +export class NumberSetting extends Setting { + constructor(name, nameInConfig, icon, min = -Infinity, max = +Infinity) { + super("number", name, nameInConfig, icon) + this.min = typeof min == "number" ? () => min : min + this.max = typeof max == "number" ? () => max : max + } + + value() { + return Helper.getSetting(this.nameInConfig) + } + + set(value) { + Helper.setSetting(this.nameInConfig, +value) + } +} + +export class EnumIntSetting extends Setting { + constructor(name, nameInConfig, icon, values = []) { + super("enum", name, nameInConfig, icon) + this.values = values + } + + value() { + return Helper.getSetting(this.nameInConfig) + } + + set(value) { + Helper.setSetting(this.nameInConfig, +value) + } +} + +export class ExpressionSetting extends Setting { + constructor(name, nameInConfig, icon, variables = []) { + super("expression", name, nameInConfig, icon) + this.variables = variables + } + + value() { + return Helper.getSetting(this.nameInConfig) + } + + /** + * + * @param {Expression} value + */ + set(value) { + let vars = value.variables() + if(vars.length === this.variables.length && vars.every(x => this.variables.includes(x))) + Helper.setSetting(this.nameInConfig, value) + else { + let undefinedVars = vars.filter(x => !this.variables.includes(x)) + let allowed = "" + if(this.variables.length > 0) + allowed = `Allowed variables: ${this.variables.join(", ")}.` + throw new TypeError(`Cannot use variable(s) ${undefinedVars.join(", or ")} to define ${this.displayName}. ${allowed}`) + } + } +} + +export class StringSetting extends Setting { + constructor(name, nameInConfig, icon, defaultValues = []) { + super("string", name, nameInConfig, icon) + this.defaultValues = defaultValues + } + + value() { + return Helper.getSetting(this.nameInConfig) + } + + set(value) { + Helper.setSetting(this.nameInConfig, ""+value) + } +} \ No newline at end of file diff --git a/common/src/preferences/default.mjs b/common/src/preferences/default.mjs new file mode 100644 index 0000000..c6864d1 --- /dev/null +++ b/common/src/preferences/default.mjs @@ -0,0 +1,120 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { BoolSetting, ExpressionSetting, NumberSetting, StringSetting } from "./common.mjs" + + +const XZOOM = new NumberSetting( + qsTranslate("Settings", "X Zoom"), + "default_graph.xzoom", + "xzoom", + 0.1 +) + +const YZOOM = new NumberSetting( + qsTranslate("Settings", "Y Zoom"), + "default_graph.yzoom", + "yzoom", + 0.1 +) + +const XMIN = new NumberSetting( + qsTranslate("Settings", "Min X"), + "default_graph.xmin", + "xmin", + () => Helper.getSetting("default_graph.logscalex") ? 1e-100 : -Infinity +) + +const YMAX = new NumberSetting( + qsTranslate("Settings", "Max Y"), + "default_graph.ymax", + "ymax" +) + +const XAXISSTEP = new ExpressionSetting( + qsTranslate("Settings", "X Axis Step"), + "default_graph.xaxisstep", + "xaxisstep" +) + +const YAXISSTEP = new ExpressionSetting( + qsTranslate("Settings", "Y Axis Step"), + "default_graph.yaxisstep", + "yaxisstep" +) + +const LINE_WIDTH = new NumberSetting( + qsTranslate("Settings", "Line width"), + "default_graph.linewidth", + "linewidth", + 1 +) + +const TEXT_SIZE = new NumberSetting( + qsTranslate("Settings", "Text size (px)"), + "default_graph.textsize", + "textsize" +) + +const X_LABEL = new StringSetting( + qsTranslate("Settings", "X Label"), + "default_graph.xlabel", + "xlabel", + ["", "x", "ω (rad/s)"] +) + +const Y_LABEL = new StringSetting( + qsTranslate("Settings", "Y Label"), + "default_graph.ylabel", + "xlabel", + ["", "y", "G (dB)", "φ (°)", "φ (deg)", "φ (rad)"] +) + +const LOG_SCALE_X = new BoolSetting( + qsTranslate("Settings", "X Log scale"), + "default_graph.logscalex", + "logscalex" +) + +const SHOW_X_GRAD = new BoolSetting( + qsTranslate("Settings", "Show X graduation"), + "default_graph.showxgrad", + "showxgrad" +) + +const SHOW_Y_GRAD = new BoolSetting( + qsTranslate("Settings", "Show Y graduation"), + "default_graph.showygrad", + "showygrad" +) + +export default [ + XZOOM, + YZOOM, + XMIN, + YMAX, + XAXISSTEP, + YAXISSTEP, + LINE_WIDTH, + TEXT_SIZE, + X_LABEL, + Y_LABEL, + LOG_SCALE_X, + SHOW_X_GRAD, + SHOW_Y_GRAD +] diff --git a/common/src/preferences/expression.mjs b/common/src/preferences/expression.mjs new file mode 100644 index 0000000..bda6899 --- /dev/null +++ b/common/src/preferences/expression.mjs @@ -0,0 +1,51 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { BoolSetting, EnumIntSetting } from "./common.mjs" + +const AUTOCLOSE_FORMULA = new BoolSetting( + qsTranslate("expression", "Automatically close parenthesises and brackets"), + "expression_editor.autoclose", + "text" +) + +const ENABLE_SYNTAX_HIGHLIGHTING = new BoolSetting( + qsTranslate("expression", "Enable syntax highlighting"), + "expression_editor.colorize", + "appearance" +) + +const ENABLE_AUTOCOMPLETE = new BoolSetting( + qsTranslate("expression", "Enable autocompletion"), + "autocompletion.enabled", + "label" +) + +const PICK_COLOR_SCHEME = new EnumIntSetting( + qsTranslate("expression", "Color Scheme"), + "expression_editor.color_scheme", + "color", + ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"] +) + +export default [ + AUTOCLOSE_FORMULA, + ENABLE_AUTOCOMPLETE, + ENABLE_SYNTAX_HIGHLIGHTING, + PICK_COLOR_SCHEME +] diff --git a/common/src/preferences/general.mjs b/common/src/preferences/general.mjs new file mode 100644 index 0000000..093afaf --- /dev/null +++ b/common/src/preferences/general.mjs @@ -0,0 +1,60 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { BoolSetting } from "./common.mjs" +import Canvas from "../module/canvas.mjs" +import LatexAPI from "../module/latex.mjs" + +const CHECK_FOR_UPDATES = new BoolSetting( + qsTranslate("general", "Check for updates on startup"), + "check_for_updates", + "update" +) + +const RESET_REDO_STACK = new BoolSetting( + qsTranslate("general", "Reset redo stack automaticly"), + "reset_redo_stack", + "timeline" +) + +class EnableLatex extends BoolSetting { + constructor() { + super(qsTranslate("general", "Enable LaTeX rendering"), "enable_latex", "Expression") + } + + set(value) { + if(!value || Latex.checkLatexInstallation()) { + super.set(value) + LatexAPI.enabled = value + Canvas.requestPaint() + } + } +} + +const ENABLE_LATEX_ASYNC = new BoolSetting( + qsTranslate("general", "Enable threaded LaTeX renderer (experimental)"), + "enable_latex_threaded", + "new" +) + +export default [ + CHECK_FOR_UPDATES, + RESET_REDO_STACK, + new EnableLatex(), + ENABLE_LATEX_ASYNC +] diff --git a/common/src/utils/expression.mjs b/common/src/utils/expression.mjs new file mode 100644 index 0000000..2caf5ea --- /dev/null +++ b/common/src/utils/expression.mjs @@ -0,0 +1,258 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { textsub, textsup } from "./subsup.mjs" + +/** + * Simplifies (mathematically) a mathematical expression. + * @deprecated + * @param {string} str - Expression to parse + * @returns {string} + */ +export function simplifyExpression(str) { + let replacements = [ + // Operations not done by parser. + // [// Decomposition way 2 + // /(^|[+-] |\()([-.\d\w]+) ([*/]) \((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\)($| [+-]|\))/g, + // "$1$2 $3 $4 $6 $2 $3 $7$9" + // ], + // [ // Decomposition way 2 + // /(^|[+-] |\()\((([-.\d\w] [*/] )?[-\d\w.]+) ([+\-]) (([-.\d\w] [*/] )?[\d\w.+]+)\) ([*/]) ([-.\d\w]+)($| [+-]|\))/g, + // "$1$2 $7 $8 $4 $5 $7 $8$9" + // ], + [ // Factorisation of π elements. + /(([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*) ([+-]) (([-\d\w.]+ [*/] )*)(pi|π)(( [/*] [-\d\w.]+)*)?/g, + function(match, m1, n1, pi1, m2, ope2, n2, opeM, m3, n3, pi2, m4, ope4, n4) { + // g1, g2, g3 , g4, g5, g6, g7, g8, g9, g10, g11,g12 , g13 + // We don't care about mx & pix, ope2 & ope4 are either / or * for n2 & n4. + // n1 & n3 are multiplied, opeM is the main operation (- or +). + // Putting all n in form of number + //n2 = n2 == undefined ? 1 : parseFloat(n) + n1 = m1 === undefined ? 1 : eval(m1 + "1") + n2 = m2 === undefined ? 1 : eval("1" + m2) + n3 = m3 === undefined ? 1 : eval(m3 + "1") + n4 = m4 === undefined ? 1 : eval("1" + m4) + //let [n1, n2, n3, n4] = [n1, n2, n3, n4].map(n => n == undefined ? 1 : parseFloat(n)) + // Falling back to * in case it does not exist (the corresponding n would be 1) + [ope2, ope4] = [ope2, ope4].map(ope => ope === "/" ? "/" : "*") + let coeff1 = n1 * n2 + let coeff2 = n3 * n4 + let coefficient = coeff1 + coeff2 - (opeM === "-" ? 2 * coeff2 : 0) + + return `${coefficient} * π` + } + ], + [ // Removing parenthesis when content is only added from both sides. + /(^|[+-] |\()\(([^)(]+)\)($| [+-]|\))/g, + function(match, b4, middle, after) { + return `${b4}${middle}${after}` + } + ], + [ // Removing parenthesis when content is only multiplied. + /(^|[*\/] |\()\(([^)(+-]+)\)($| [*\/+-]|\))/g, + function(match, b4, middle, after) { + return `${b4}${middle}${after}` + } + ], + [ // Removing parenthesis when content is only multiplied. + /(^|[*\/+-] |\()\(([^)(+-]+)\)($| [*\/]|\))/g, + function(match, b4, middle, after) { + return `${b4}${middle}${after}` + } + ], + [// Simplification additions/subtractions. + /(^|[^*\/] |\()([-.\d]+) [+-] (\([^)(]+\)|[^)(]+) [+-] ([-.\d]+)($| [^*\/]|\))/g, + function(match, b4, n1, op1, middle, op2, n2, after) { + let total + if(op2 === "+") { + total = parseFloat(n1) + parseFloat(n2) + } else { + total = parseFloat(n1) - parseFloat(n2) + } + return `${b4}${total} ${op1} ${middle}${after}` + } + ], + [// Simplification multiplications/divisions. + /([-.\d]+) [*\/] (\([^)(]+\)|[^)(+-]+) [*\/] ([-.\d]+)/g, + function(match, n1, op1, middle, op2, n2) { + if(parseInt(n1) === n1 && parseInt(n2) === n2 && op2 === "/" && + (parseInt(n1) / parseInt(n2)) % 1 !== 0) { + // Non int result for int division. + return `(${n1} / ${n2}) ${op1} ${middle}` + } else { + if(op2 === "*") { + return `${parseFloat(n1) * parseFloat(n2)} ${op1} ${middle}` + } else { + return `${parseFloat(n1) / parseFloat(n2)} ${op1} ${middle}` + } + } + } + ], + [// Starting & ending parenthesis if not needed. + /^\s*\((.*)\)\s*$/g, + function(match, middle) { + let str = middle + // Replace all groups + while(/\([^)(]+\)/g.test(str)) + str = str.replace(/\([^)(]+\)/g, "") + // There shouldn't be any more parenthesis + // If there is, that means the 2 parenthesis are needed. + if(!str.includes(")") && !str.includes("(")) { + return middle + } else { + return `(${middle})` + } + + } + ] + // Simple simplifications + // [/(\s|^|\()0(\.0+)? \* (\([^)(]+\))/g, '$10'], + // [/(\s|^|\()0(\.0+)? \* ([^)(+-]+)/g, '$10'], + // [/(\([^)(]\)) \* 0(\.0+)?(\s|$|\))/g, '0$3'], + // [/([^)(+-]) \* 0(\.0+)?(\s|$|\))/g, '0$3'], + // [/(\s|^|\()1(\.0+)? [\*\/] /g, '$1'], + // [/(\s|^|\()0(\.0+)? (\+|\-) /g, '$1'], + // [/ [\*\/] 1(\.0+)?(\s|$|\))/g, '$3'], + // [/ (\+|\-) 0(\.0+)?(\s|$|\))/g, '$3'], + // [/(^| |\() /g, '$1'], + // [/ ($|\))/g, '$1'], + ] + + // Replacements + let found + do { + found = false + for(let replacement of replacements) + while(replacement[0].test(str)) { + found = true + str = str.replace(replacement[0], replacement[1]) + } + } while(found) + return str +} + + +/** + * Transforms a mathematical expression to make it readable by humans. + * NOTE: Will break parsing of expression. + * @deprecated + * @param {string} str - Expression to parse. + * @returns {string} + */ +export function makeExpressionReadable(str) { + let replacements = [ + // letiables + [/pi/g, "π"], + [/Infinity/g, "∞"], + [/inf/g, "∞"], + // Other + [/ \* /g, "×"], + [/ \^ /g, "^"], + [/\^\(([\d\w+-]+)\)/g, function(match, p1) { + return textsup(p1) + }], + [/\^([\d\w+-]+)/g, function(match, p1) { + return textsup(p1) + }], + [/_\(([\d\w+-]+)\)/g, function(match, p1) { + return textsub(p1) + }], + [/_([\d\w+-]+)/g, function(match, p1) { + return textsub(p1) + }], + [/\[([^\[\]]+)\]/g, function(match, p1) { + return textsub(p1) + }], + [/(\d|\))×/g, "$1"], + [/integral\((.+),\s?(.+),\s?["'](.+)["'],\s?["'](.+)["']\)/g, function(match, a, b, p1, body, p2, p3, by, p4) { + if(a.length < b.length) { + return `∫${textsub(a)}${textsup(b)} ${body} d${by}` + } else { + return `∫${textsup(b)}${textsub(a)} ${body} d${by}` + } + }], + [/derivative\(?["'](.+)["'], ?["'](.+)["'], ?(.+)\)?/g, function(match, p1, body, p2, p3, by, p4, x) { + return `d(${body.replace(new RegExp(by, "g"), "x")})/dx` + }] + ] + + // str = simplifyExpression(str) + // Replacements + for(let replacement of replacements) + while(replacement[0].test(str)) + str = str.replace(replacement[0], replacement[1]) + return str +} + +/** @type {[RegExp, string][]} */ +const replacements = [ + // Greek letters + [/(\W|^)al(pha)?(\W|$)/g, "$1α$3"], + [/(\W|^)be(ta)?(\W|$)/g, "$1β$3"], + [/(\W|^)ga(mma)?(\W|$)/g, "$1γ$3"], + [/(\W|^)de(lta)?(\W|$)/g, "$1δ$3"], + [/(\W|^)ep(silon)?(\W|$)/g, "$1ε$3"], + [/(\W|^)ze(ta)?(\W|$)/g, "$1ζ$3"], + [/(\W|^)et(a)?(\W|$)/g, "$1η$3"], + [/(\W|^)th(eta)?(\W|$)/g, "$1θ$3"], + [/(\W|^)io(ta)?(\W|$)/g, "$1ι$3"], + [/(\W|^)ka(ppa)?(\W|$)/g, "$1κ$3"], + [/(\W|^)la(mbda)?(\W|$)/g, "$1λ$3"], + [/(\W|^)mu(\W|$)/g, "$1μ$2"], + [/(\W|^)nu(\W|$)/g, "$1ν$2"], + [/(\W|^)xi(\W|$)/g, "$1ξ$2"], + [/(\W|^)rh(o)?(\W|$)/g, "$1ρ$3"], + [/(\W|^)si(gma)?(\W|$)/g, "$1σ$3"], + [/(\W|^)ta(u)?(\W|$)/g, "$1τ$3"], + [/(\W|^)up(silon)?(\W|$)/g, "$1υ$3"], + [/(\W|^)ph(i)?(\W|$)/g, "$1φ$3"], + [/(\W|^)ch(i)?(\W|$)/g, "$1χ$3"], + [/(\W|^)ps(i)?(\W|$)/g, "$1ψ$3"], + [/(\W|^)om(ega)?(\W|$)/g, "$1ω$3"], + // Capital greek letters + [/(\W|^)gga(mma)?(\W|$)/g, "$1Γ$3"], + [/(\W|^)gde(lta)?(\W|$)/g, "$1Δ$3"], + [/(\W|^)gth(eta)?(\W|$)/g, "$1Θ$3"], + [/(\W|^)gla(mbda)?(\W|$)/g, "$1Λ$3"], + [/(\W|^)gxi(\W|$)/g, "$1Ξ$2"], + [/(\W|^)gpi(\W|$)/g, "$1Π$2"], + [/(\W|^)gsi(gma)?(\W|$)/g, "$1Σ$3"], + [/(\W|^)gph(i)?(\W|$)/g, "$1Φ$3"], + [/(\W|^)gps(i)?(\W|$)/g, "$1Ψ$3"], + [/(\W|^)gom(ega)?(\W|$)/g, "$1Ω$3"], + // Array elements + [/\[([^\]\[]+)\]/g, function(match, p1) { + return textsub(p1) + }] +] + +/** + * Parses a variable name to make it readable by humans. + * + * @param {string} str - Variable name to parse + * @param {boolean} removeUnallowed - Remove domain symbols disallowed in name. + * @returns {string} - The parsed name + */ +export function parseName(str, removeUnallowed = true) { + for(const replacement of replacements) + str = str.replace(replacement[0], replacement[1]) + if(removeUnallowed) + str = str.replace(/[xnπℝℕ\\∪∩\]\[ ()^/÷*×+=\d¹²³⁴⁵⁶⁷⁸⁹⁰-]/g, "") + + return str +} diff --git a/common/src/utils/index.mjs b/common/src/utils/index.mjs new file mode 100644 index 0000000..2c11c1b --- /dev/null +++ b/common/src/utils/index.mjs @@ -0,0 +1,22 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +export * from "./prototype.mjs" +export * from "./subsup.mjs" +export * from "./expression.mjs" +export * from "./other.mjs" diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/autoload.js b/common/src/utils/other.mjs similarity index 52% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/autoload.js rename to common/src/utils/other.mjs index 8ee04cf..50f84f4 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/js/objs/autoload.js +++ b/common/src/utils/other.mjs @@ -1,42 +1,41 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * + * Copyright (C) 2021-2025 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 . */ -.pragma library -.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 -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) +/** + * Creates a randomized color string. + * @returns {string} + */ +export function getRandomColor() { + let clrs = "0123456789ABCDEF" + let color = "#" + for(let i = 0; i < 6; i++) { + color += clrs[Math.floor(Math.random() * (16 - 5 * (i % 2 === 0)))] + } + return color +} + +/** + * Escapes text to html entities. + * @param {string} str + * @returns {string} + */ +export function escapeHTML(str) { + return str.replace(/&/g, "&").replace(//g, ">") +} diff --git a/common/src/utils/prototype.mjs b/common/src/utils/prototype.mjs new file mode 100644 index 0000000..b0fd27f --- /dev/null +++ b/common/src/utils/prototype.mjs @@ -0,0 +1,51 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Add string methods +/** + * Replaces latin characters with their uppercase versions. + * @return {string} + */ +String.prototype.toLatinUppercase = function() { + return this.replace(/[a-z]/g, function(match) { + return match.toUpperCase() + }) +} + +/** + * Removes the first and last character of a string + * Used to remove enclosing characters like quotes, parentheses, brackets... + * @note Does NOT check for their existence ahead of time. + * @return {string} + */ +String.prototype.removeEnclosure = function() { + return this.substring(1, this.length - 1) +} + +/** + * Rounds to a certain number of decimal places. + * From https://stackoverflow.com/a/48764436 + * + * @param {number} decimalPlaces + * @return {number} + */ +Number.prototype.toDecimalPrecision = function(decimalPlaces = 0) { + const p = Math.pow(10, decimalPlaces) + const n = (this * p) * (1 + Number.EPSILON) + return Math.round(n) / p +} diff --git a/common/src/utils/subsup.mjs b/common/src/utils/subsup.mjs new file mode 100644 index 0000000..6eb4280 --- /dev/null +++ b/common/src/utils/subsup.mjs @@ -0,0 +1,140 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +const CHARACTER_TO_POWER = new Map([ + ["-", "⁻"], + ["+", "⁺"], + ["=", "⁼"], + [" ", " "], + ["(", "⁽"], + [")", "⁾"], + ["0", "⁰"], + ["1", "¹"], + ["2", "²"], + ["3", "³"], + ["4", "⁴"], + ["5", "⁵"], + ["6", "⁶"], + ["7", "⁷"], + ["8", "⁸"], + ["9", "⁹"], + ["a", "ᵃ"], + ["b", "ᵇ"], + ["c", "ᶜ"], + ["d", "ᵈ"], + ["e", "ᵉ"], + ["f", "ᶠ"], + ["g", "ᵍ"], + ["h", "ʰ"], + ["i", "ⁱ"], + ["j", "ʲ"], + ["k", "ᵏ"], + ["l", "ˡ"], + ["m", "ᵐ"], + ["n", "ⁿ"], + ["o", "ᵒ"], + ["p", "ᵖ"], + ["r", "ʳ"], + ["s", "ˢ"], + ["t", "ᵗ"], + ["u", "ᵘ"], + ["v", "ᵛ"], + ["w", "ʷ"], + ["x", "ˣ"], + ["y", "ʸ"], + ["z", "ᶻ"] +]) + +const CHARACTER_TO_INDICE = new Map([ + ["-", "₋"], + ["+", "₊"], + ["=", "₌"], + ["(", "₍"], + [")", "₎"], + [" ", " "], + ["0", "₀"], + ["1", "₁"], + ["2", "₂"], + ["3", "₃"], + ["4", "₄"], + ["5", "₅"], + ["6", "₆"], + ["7", "₇"], + ["8", "₈"], + ["9", "₉"], + ["a", "ₐ"], + ["e", "ₑ"], + ["h", "ₕ"], + ["i", "ᵢ"], + ["j", "ⱼ"], + ["k", "ₖ"], + ["l", "ₗ"], + ["m", "ₘ"], + ["n", "ₙ"], + ["o", "ₒ"], + ["p", "ₚ"], + ["r", "ᵣ"], + ["s", "ₛ"], + ["t", "ₜ"], + ["u", "ᵤ"], + ["v", "ᵥ"], + ["x", "ₓ"] +]) + +const EXPONENTS = [ + "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" +] + +const EXPONENTS_REG = new RegExp("([" + EXPONENTS.join("") + "]+)", "g") + +/** + * Put a text in sup position + * @param {string} text + * @return {string} + */ +export function textsup(text) { + let ret = "" + text = text.toString() + for(let letter of text) + ret += CHARACTER_TO_POWER.has(letter) ? CHARACTER_TO_POWER.get(letter) : letter + return ret +} + +/** + * Put a text in sub position + * @param {string} text + * @return {string} + */ +export function textsub(text) { + let ret = "" + text = text.toString() + for(let letter of text) + ret += CHARACTER_TO_INDICE.has(letter) ? CHARACTER_TO_INDICE.get(letter) : letter + return ret +} + + +/** + * Parses exponents and replaces them with expression values + * @param {string} expression - The expression to replace in. + * @return {string} The parsed expression + */ +export function exponentsToExpression(expression) { + return expression.replace(EXPONENTS_REG, (m, exp) => "^" + exp.split("").map((x) => EXPONENTS.indexOf(x)).join("")) +} + diff --git a/common/test/basics/events.mjs b/common/test/basics/events.mjs new file mode 100644 index 0000000..7dac6e2 --- /dev/null +++ b/common/test/basics/events.mjs @@ -0,0 +1,118 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { describe, it } from "mocha" +import { expect } from "chai" + +const { spy } = chaiPlugins + +import { BaseEventEmitter, BaseEvent } from "../../src/events.mjs" + +class MockEmitter extends BaseEventEmitter { + static emits = ["example1", "example2"] +} + +class MockEvent1 extends BaseEvent { + constructor() { + super("example1") + } +} + +class MockEvent2 extends BaseEvent { + constructor(parameter) { + super("example2") + this.parameter = parameter + } +} + +describe("Lib/EventsEmitters", function() { + it("sends events with unique and readonly names", function() { + const event = new MockEvent1() + expect(event.name).to.equal("example1") + expect(() => event.name = "not").to.throw() + }) + + it("forwards events to all of its listeners", function() { + const emitter = new MockEmitter() + const listener1 = spy() + const listener2 = spy() + emitter.on("example1", listener1) + emitter.on("example1", listener2) + emitter.emit(new MockEvent1()) + expect(listener1).to.have.been.called.once + expect(listener2).to.have.been.called.once + }) + + it("forwards multiple events to a singular listener", function() { + const emitter = new MockEmitter() + const listener = spy() + const mockEvent1 = new MockEvent1() + const mockEvent2 = new MockEvent2(3) + emitter.on("example1 example2", listener) + emitter.emit(mockEvent1) + emitter.emit(mockEvent2) + expect(listener).to.have.been.called.twice + expect(listener).to.have.been.first.called.with.exactly(mockEvent1) + expect(listener).to.have.been.second.called.with.exactly(mockEvent2) + }) + + it("is able to have listeners removed", function() { + const emitter = new MockEmitter() + const listener = spy() + emitter.on("example1", listener) + const removedFromEventItDoesntListenTo = emitter.off("example2", listener) + const removedFromEventItListensTo = emitter.off("example1", listener) + const removedFromEventASecondTime = emitter.off("example1", listener) + expect(removedFromEventItDoesntListenTo).to.be.false + expect(removedFromEventItListensTo).to.be.true + expect(removedFromEventASecondTime).to.be.false + emitter.on("example1 example2", listener) + const removedFromBothEvents = emitter.off("example1 example2", listener) + expect(removedFromBothEvents).to.be.true + emitter.on("example1", listener) + const removedFromOneOfTheEvents = emitter.off("example1 example2", listener) + expect(removedFromOneOfTheEvents).to.be.true + }) + + it("is able to have one listener's listening to a single event removed when said listener listens to multiple", function() { + const emitter = new MockEmitter() + const listener = spy() + const mockEvent1 = new MockEvent1() + const mockEvent2 = new MockEvent2(3) + emitter.on("example1 example2", listener) + // Disable listener for example1 + emitter.off("example1", listener) + emitter.emit(mockEvent1) + emitter.emit(mockEvent2) + expect(listener).to.have.been.called.once + expect(listener).to.also.have.been.called.with.exactly(mockEvent2) + }) + + it("is not able to emit or add/remove listeners for inexistant events", function() { + const emitter = new MockEmitter() + const listener = spy() + expect(() => emitter.on("inexistant", listener)).to.throw(Error) + expect(() => emitter.off("inexistant", listener)).to.throw(Error) + expect(() => emitter.emit(new BaseEvent("inexistant"))).to.throw(Error) + }) + + it("isn't able to emit non-events", function() { + const emitter = new MockEmitter() + expect(() => emitter.emit("not-an-event")).to.throw(Error) + }) +}) \ No newline at end of file diff --git a/common/test/basics/interface.mjs b/common/test/basics/interface.mjs new file mode 100644 index 0000000..48af62e --- /dev/null +++ b/common/test/basics/interface.mjs @@ -0,0 +1,58 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { describe, it } from "mocha" +import { expect } from "chai" + +import { MockLatex } from "../mock/latex.mjs" +import { MockHelper } from "../mock/helper.mjs" +import { + CanvasInterface, + DialogInterface, + HelperInterface, + Interface, + LatexInterface, + RootInterface +} from "../../src/module/interface.mjs" +import { MockDialog } from "../mock/dialog.mjs" +import { MockRootElement } from "../mock/root.mjs" +import { MockCanvas } from "../mock/canvas.mjs" + +describe("Interfaces", function() { + describe("#interface methods", function() { + it("throws an error when called directly", function() { + const obj = new CanvasInterface() + expect(() => obj.markDirty()).to.throw(Error) + }) + }) + + describe("#checkImplementation", function() { + it("validates the implementation of mocks", function() { + const checkMockLatex = () => Interface.checkImplementation(LatexInterface, new MockLatex()) + const checkMockHelper = () => Interface.checkImplementation(HelperInterface, new MockHelper()) + const checkMockDialog = () => Interface.checkImplementation(DialogInterface, new MockDialog()) + const checkMockRoot = () => Interface.checkImplementation(RootInterface, new MockRootElement()) + const checkMockCanvas = () => Interface.checkImplementation(CanvasInterface, new MockCanvas()) + expect(checkMockLatex).to.not.throw() + expect(checkMockHelper).to.not.throw() + expect(checkMockDialog).to.not.throw() + expect(checkMockRoot).to.not.throw() + expect(checkMockCanvas).to.not.throw() + }) + }) +}) \ No newline at end of file diff --git a/common/test/basics/polyfill.mjs b/common/test/basics/polyfill.mjs new file mode 100644 index 0000000..1fc9599 --- /dev/null +++ b/common/test/basics/polyfill.mjs @@ -0,0 +1,232 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Load prior tests + +import { describe, it } from "mocha" +import { expect } from "chai" + +import * as Polyfill from "../../src/lib/expr-eval/polyfill.mjs" +import { + andOperator, + cbrt, + equal, + expm1, + hypot, + lessThan, + log1p, + log2, + notEqual +} from "../../src/lib/expr-eval/polyfill.mjs" + +describe("Math/Polyfill", () => { + describe("#add", function() { + it("adds two numbers", function() { + expect(Polyfill.add(2, 3)).to.equal(5) + expect(Polyfill.add("2", "3")).to.equal(5) + }) + }) + + describe("#sub", function() { + it("subtracts two numbers", function() { + expect(Polyfill.sub(2, 1)).to.equal(1) + expect(Polyfill.sub("2", "1")).to.equal(1) + }) + }) + + describe("#mul", function() { + it("multiplies two numbers", function() { + expect(Polyfill.mul(2, 3)).to.equal(6) + expect(Polyfill.mul("2", "3")).to.equal(6) + }) + }) + + describe("#div", function() { + it("divides two numbers", function() { + expect(Polyfill.div(10, 2)).to.equal(5) + expect(Polyfill.div("10", "2")).to.equal(5) + }) + }) + + describe("#mod", function() { + it("returns the modulo of two numbers", function() { + expect(Polyfill.mod(10, 3)).to.equal(1) + expect(Polyfill.mod("10", "3")).to.equal(1) + }) + }) + + describe("#concat", function() { + it("returns the concatenation of two strings", function() { + expect(Polyfill.concat(10, 3)).to.equal("103") + expect(Polyfill.concat("abc", "def")).to.equal("abcdef") + }) + }) + + describe("#equal", function() { + it("returns whether its two arguments are equal", function() { + expect(Polyfill.equal(10, 3)).to.be.false + expect(Polyfill.equal(10, 10)).to.be.true + expect(Polyfill.equal("abc", "def")).to.be.false + expect(Polyfill.equal("abc", "abc")).to.be.true + }) + }) + + describe("#notEqual", function() { + it("returns whether its two arguments are not equal", function() { + expect(Polyfill.notEqual(10, 3)).to.be.true + expect(Polyfill.notEqual(10, 10)).to.be.false + expect(Polyfill.notEqual("abc", "def")).to.be.true + expect(Polyfill.notEqual("abc", "abc")).to.be.false + }) + }) + + describe("#greaterThan", function() { + it("returns whether its first argument is strictly greater than its second", function() { + expect(Polyfill.greaterThan(10, 3)).to.be.true + expect(Polyfill.greaterThan(10, 10)).to.be.false + expect(Polyfill.greaterThan(10, 30)).to.be.false + }) + }) + + describe("#lessThan", function() { + it("returns whether its first argument is strictly less than its second", function() { + expect(Polyfill.lessThan(10, 3)).to.be.false + expect(Polyfill.lessThan(10, 10)).to.be.false + expect(Polyfill.lessThan(10, 30)).to.be.true + }) + }) + + describe("#greaterThanEqual", function() { + it("returns whether its first argument is greater or equal to its second", function() { + expect(Polyfill.greaterThanEqual(10, 3)).to.be.true + expect(Polyfill.greaterThanEqual(10, 10)).to.be.true + expect(Polyfill.greaterThanEqual(10, 30)).to.be.false + }) + }) + + describe("#lessThanEqual", function() { + it("returns whether its first argument is strictly less than its second", function() { + expect(Polyfill.lessThanEqual(10, 3)).to.be.false + expect(Polyfill.lessThanEqual(10, 10)).to.be.true + expect(Polyfill.lessThanEqual(10, 30)).to.be.true + }) + }) + + describe("#andOperator", function() { + it("returns whether its arguments are both true", function() { + expect(Polyfill.andOperator(true, true)).to.be.true + expect(Polyfill.andOperator(true, false)).to.be.false + expect(Polyfill.andOperator(false, true)).to.be.false + expect(Polyfill.andOperator(false, false)).to.be.false + expect(Polyfill.andOperator(10, 3)).to.be.true + expect(Polyfill.andOperator(10, 0)).to.be.false + expect(Polyfill.andOperator(0, 0)).to.be.false + }) + }) + + describe("#orOperator", function() { + it("returns whether one of its arguments is true", function() { + expect(Polyfill.orOperator(true, true)).to.be.true + expect(Polyfill.orOperator(true, false)).to.be.true + expect(Polyfill.orOperator(false, true)).to.be.true + expect(Polyfill.orOperator(false, false)).to.be.false + expect(Polyfill.orOperator(10, 3)).to.be.true + expect(Polyfill.orOperator(10, 0)).to.be.true + expect(Polyfill.orOperator(0, 0)).to.be.false + }) + }) + + describe("#inOperator", function() { + it("checks if second argument contains first", function() { + expect(Polyfill.inOperator("a", ["a", "b", "c"])).to.be.true + expect(Polyfill.inOperator(3, [0, 1, 2])).to.be.false + expect(Polyfill.inOperator(3, [0, 1, 3, 2])).to.be.true + expect(Polyfill.inOperator("a", "abcdef")).to.be.true + expect(Polyfill.inOperator("a", "bcdefg")).to.be.false + }) + }) + + describe("#sinh, #cosh, #tanh, #asinh, #acosh, #atanh", function() { + const EPSILON = 1e-12 + for(let x = -.9; x < 1; x += 0.1) { + expect(Polyfill.sinh(x)).to.be.approximately(Math.sinh(x), EPSILON) + expect(Polyfill.cosh(x)).to.be.approximately(Math.cosh(x), EPSILON) + expect(Polyfill.tanh(x)).to.be.approximately(Math.tanh(x), EPSILON) + expect(Polyfill.asinh(x)).to.be.approximately(Math.asinh(x), EPSILON) + expect(Polyfill.atanh(x)).to.be.approximately(Math.atanh(x), EPSILON) + } + for(let x = 1.1; x < 10; x += 0.1) { + expect(Polyfill.sinh(x)).to.be.approximately(Math.sinh(x), EPSILON) + expect(Polyfill.cosh(x)).to.be.approximately(Math.cosh(x), EPSILON) + expect(Polyfill.tanh(x)).to.be.approximately(Math.tanh(x), EPSILON) + expect(Polyfill.asinh(x)).to.be.approximately(Math.asinh(x), EPSILON) + expect(Polyfill.acosh(x)).to.be.approximately(Math.acosh(x), EPSILON) + expect(Polyfill.log10(x)).to.be.approximately(Math.log10(x), EPSILON) + } + }) + + describe("#trunc", function() { + it("returns the decimal part of floats", function() { + for(let x = -10; x < 10; x += 0.1) + expect(Polyfill.trunc(x)).to.equal(Math.trunc(x)) + }) + }) + + describe("#gamma", function() { + it("returns the product of factorial(x - 1)", function() { + expect(Polyfill.gamma(0)).to.equal(Infinity) + expect(Polyfill.gamma(1)).to.equal(1) + expect(Polyfill.gamma(2)).to.equal(1) + expect(Polyfill.gamma(3)).to.equal(2) + expect(Polyfill.gamma(4)).to.equal(6) + expect(Polyfill.gamma(5)).to.equal(24) + expect(Polyfill.gamma(172)).to.equal(Infinity) + expect(Polyfill.gamma(172.3)).to.equal(Infinity) + expect(Polyfill.gamma(.2)).to.approximately(4.590_843_712, 1e-8) + expect(Polyfill.gamma(5.5)).to.be.approximately(52.34277778, 1e-8) + expect(Polyfill.gamma(90.2)).to.equal(4.0565358202825355e+136) + }) + }) + + describe("#hypot", function() { + it("returns the hypothenus length of a triangle whose length are provided in arguments", function() { + for(let x = 0; x < 10; x += 0.3) { + expect(Polyfill.hypot(x)).to.be.approximately(Math.hypot(x), Number.EPSILON) + for(let y = 0; y < 10; y += 0.3) { + expect(Polyfill.hypot(x, y)).to.be.approximately(Math.hypot(x, y), Number.EPSILON) + } + } + }) + }) + + describe("#sign, #cbrt, #exmp1", function() { + for(let x = -10; x < 10; x += 0.3) { + expect(Polyfill.sign(x)).to.approximately(Math.sign(x), 1e-12) + expect(Polyfill.cbrt(x)).to.approximately(Math.cbrt(x), 1e-12) + expect(Polyfill.expm1(x)).to.approximately(Math.expm1(x), 1e-12) + } + }) + + describe("#log1p, #log2", function() { + for(let x = 1; x < 10; x += 0.3) { + expect(Polyfill.log1p(x)).to + .be.approximately(Math.log1p(x), 1e-12) + expect(Polyfill.log2(x)).to.be.approximately(Math.log2(x), 1e-12) + } + }) +}) \ No newline at end of file diff --git a/common/test/basics/utils.mjs b/common/test/basics/utils.mjs new file mode 100644 index 0000000..66ecd92 --- /dev/null +++ b/common/test/basics/utils.mjs @@ -0,0 +1,183 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { textsup, textsub, parseName, getRandomColor, escapeHTML, exponentsToExpression } from "../../src/utils/index.mjs" + + +import { describe, it } from "mocha" +import { expect } from "chai" + +describe("Lib/PrototypeExtensions", function() { + describe("#String.toLatinUppercase", function() { + it("transforms latin characters from strings to their uppercase version", function() { + expect("abc".toLatinUppercase()).to.equal("ABC") + expect("abCd".toLatinUppercase()).to.equal("ABCD") + expect("ab123cd456".toLatinUppercase()).to.equal("AB123CD456") + expect("ABC".toLatinUppercase()).to.equal("ABC") + }) + + it("does not transform non latin characters to their uppercase version", function() { + expect("abαπ".toLatinUppercase()).to.equal("ABαπ") + expect("abαπ".toLatinUppercase()).to.not.equal("abαπ".toUpperCase()) + }) + }) + + describe("#String.removeEnclosure", function() { + it("is able to remove the first and last characters", function() { + expect("[1+t]".removeEnclosure()).to.equal("1+t") + expect('"a+b+c*d"'.removeEnclosure()).to.equal("a+b+c*d") + expect("(pi/2)".removeEnclosure()).to.equal("pi/2") + }) + }) + + describe("#Number.toDecimalPrecision", function() { + it("rounds a number to a fixed decimal precision", function() { + expect(123.456789.toDecimalPrecision()).to.equal(123) + expect(123.456789.toDecimalPrecision(1)).to.equal(123.5) + expect(123.456789.toDecimalPrecision(2)).to.equal(123.46) + expect(123.456789.toDecimalPrecision(3)).to.equal(123.457) + expect(123.456789.toDecimalPrecision(4)).to.equal(123.4568) + expect(123.456789.toDecimalPrecision(5)).to.equal(123.45679) + expect(123.456789.toDecimalPrecision(6)).to.equal(123.456789) + expect(123.111111.toDecimalPrecision(5)).to.equal(123.11111) + }) + }) +}) + +describe("Lib/Utils", function() { + describe("#textsup", function() { + it("transforms characters which have a sup unicode equivalent", function() { + expect(textsup("-+=()")).to.equal("⁻⁺⁼⁽⁾") + expect(textsup("0123456789")).to.equal("⁰¹²³⁴⁵⁶⁷⁸⁹") + expect(textsup("abcdefghijklmnoprstuvwxyz")).to.equal("ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ") + }) + + it("does not transform characters without a sup equivalent", function() { + expect(textsup("ABCDEFGHIJKLMNOPQRSTUVWXYZq")).to.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZq") + }) + + it("partially transforms strings which only have a few characters with a sup equivalent", function() { + expect(textsup("ABCabcABC")).to.equal("ABCᵃᵇᶜABC") + }) + }) + + describe("#textsub", function() { + it("transforms characters which have a sub unicode equivalent", function() { + expect(textsub("-+=()")).to.equal("₋₊₌₍₎") + expect(textsub("0123456789")).to.equal("₀₁₂₃₄₅₆₇₈₉") + expect(textsub("aehijklmnoprstuvx")).to.equal("ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ") + }) + + it("does not transform characters without a sub equivalent", function() { + expect(textsub("ABCDEFGHIJKLMNOPQRSTUVWXYZ")).to.equal("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + expect(textsub("bcdfgqyz")).to.equal("bcdfgqyz") + }) + + it("partially transforms strings which only have a few characters with a sub equivalent", function() { + expect(textsub("ABC123ABC")).to.equal("ABC₁₂₃ABC") + }) + }) + + describe("#parseName", function() { + it("parses greek letter names", function() { + const shorthands = { + "α": ["al", "alpha"], + "β": ["be", "beta"], + "γ": ["ga", "gamma"], + "δ": ["de", "delta"], + "ε": ["ep", "epsilon"], + "ζ": ["ze", "zeta"], + "η": ["et", "eta"], + "θ": ["th", "theta"], + "κ": ["ka", "kappa"], + "λ": ["la", "lambda"], + "μ": ["mu"], + "ν": ["nu"], + "ξ": ["xi"], + "ρ": ["rh", "rho"], + "σ": ["si", "sigma"], + "τ": ["ta", "tau"], + "υ": ["up", "upsilon"], + "φ": ["ph", "phi"], + "χ": ["ch", "chi"], + "ψ": ["ps", "psi"], + "ω": ["om", "omega"], + "Γ": ["gga", "ggamma"], + "Δ": ["gde", "gdelta"], + "Θ": ["gth", "gtheta"], + "Λ": ["gla", "glambda"], + "Ξ": ["gxi"], + "Π": ["gpi"], + "Σ": ["gsi", "gsigma"], + "Φ": ["gph", "gphi"], + "Ψ": ["gps", "gpsi"], + "Ω": ["gom", "gomega"], + } + for(const [char, shorts] of Object.entries(shorthands)) { + expect(parseName(char)).to.equal(char) + for(const short of shorts) + expect(parseName(short)).to.equal(char) + } + }) + + it("parses array elements into sub", function() { + expect(parseName("u[n+1]")).to.equal("uₙ₊₁") + expect(parseName("u[(n+x)]")).to.equal("u₍ₙ₊ₓ₎") + expect(parseName("u[A]")).to.equal("uA") + }) + + it("removes disallowed characters when indicated", function() { + const disallowed = "xnπℝℕ\\∪∩[] ()^/^/÷*×+=1234567890¹²³⁴⁵⁶⁷⁸⁹⁰-" + expect(parseName(disallowed)).to.equal("") + expect(parseName("AA" + disallowed)).to.equal("AA") + expect(parseName(disallowed, false)).to.equal(disallowed) + }) + + it("is able to do all three at once", function() { + expect(parseName("al[n+1]+n")).to.equal("αₙ₊₁") + expect(parseName("al[n+1]+n", false)).to.equal("αₙ₊₁+n") + }) + }) + + describe("#getRandomColor", function() { + it("provides a valid color", function() { + const colorReg = /^#[A-F\d]{6}$/ + for(let i = 0; i < 50; i++) + expect(getRandomColor()).to.match(colorReg) + }) + }) + + describe("#escapeHTML", function() { + it("escapes ampersands", function() { + expect(escapeHTML("&")).to.equal("&") + expect(escapeHTML("High & Mighty")).to.equal("High & Mighty") + }) + + it("escapes injected HTML tags", function() { + expect(escapeHTML("")).to.equal("<script>alert('Injected!')</script>") + expect(escapeHTML('Link')).to.equal('<a href="javascript:alert()">Link</a>') + }) + }) + + describe("#exponentsToExpression", function() { + it("transforms exponents to power expression", function() { + expect(exponentsToExpression("x¹²³⁴⁵⁶⁷⁸⁹⁰")).to.equal("x^1234567890") + expect(exponentsToExpression("x¹²+y³⁴")).to.equal("x^12+y^34") + }) + }) +}) diff --git a/common/test/hooks.mjs b/common/test/hooks.mjs new file mode 100644 index 0000000..39a25d7 --- /dev/null +++ b/common/test/hooks.mjs @@ -0,0 +1,36 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import * as fs from "./mock/fs.mjs" +import Qt from "./mock/qt.mjs" +import { MockHelper } from "./mock/helper.mjs" +import { MockLatex } from "./mock/latex.mjs" + +import { use } from "chai" +import spies from "chai-spies" +import promised from "chai-as-promised" + +function setup() { + use(promised) + const { spy } = use(spies) + + globalThis.Helper = new MockHelper() + globalThis.Latex = new MockLatex() + globalThis.chaiPlugins = { spy } +} + +setup() diff --git a/common/test/math/domain.mjs b/common/test/math/domain.mjs new file mode 100644 index 0000000..b9161c8 --- /dev/null +++ b/common/test/math/domain.mjs @@ -0,0 +1,108 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { describe, it } from "mocha" +import { expect } from "chai" + +import { Domain, EmptySet, parseDomainSimple } from "../../src/math/domain.mjs" + +describe("math.domain", function() { + describe("#parseDomainSimple", function() { + it("returns empty sets when a domain cannot be parsed", function() { + expect(parseDomainSimple("∅")).to.be.an.instanceof(EmptySet) + expect(parseDomainSimple("O")).to.be.an.instanceof(EmptySet) + expect(parseDomainSimple("AAAAAAAAA")).to.be.an.instanceof(EmptySet) + expect(parseDomainSimple("???")).to.be.an.instanceof(EmptySet) + expect(parseDomainSimple("∅").latexMarkup).to.equal("\\emptyset") + expect(parseDomainSimple("???").toString()).to.equal("∅") + expect(parseDomainSimple("∅").includes(0)).to.be.false + expect(parseDomainSimple("∅").includes(Infinity)).to.be.false + expect(parseDomainSimple("∅").includes(-3)).to.be.false + + }) + + it("returns predefined domains", function() { + const predefinedToCheck = [ + // Real domains + { domain: Domain.R, shortcuts: ["R", "ℝ"] }, + // Zero exclusive real domains + { domain: Domain.RE, shortcuts: ["RE", "R*", "ℝ*"] }, + // Real positive domains + { domain: Domain.RP, shortcuts: ["RP", "R+", "ℝ⁺", "ℝ+"] }, + // Zero-exclusive real positive domains + { domain: Domain.RPE, shortcuts: ["RPE", "REP", "R+*", "R*+", "ℝ*⁺", "ℝ⁺*", "ℝ*+", "ℝ+*"] }, + // Real negative domain + { domain: Domain.RM, shortcuts: ["RM", "R-", "ℝ⁻", "ℝ-"] }, + // Zero-exclusive real negative domains + { domain: Domain.RME, shortcuts: ["RME", "REM", "R-*", "R*-", "ℝ⁻*", "ℝ*⁻", "ℝ-*", "ℝ*-"] }, + // Natural integers domain + { domain: Domain.N, shortcuts: ["ℕ", "N", "ZP", "Z+", "ℤ⁺", "ℤ+"] }, + // Zero-exclusive natural integers domain + { domain: Domain.NE, shortcuts: ["NE", "NP", "N*", "N+", "ℕ*", "ℕ⁺", "ℕ+", "ZPE", "ZEP", "Z+*", "Z*+", "ℤ⁺*", "ℤ*⁺", "ℤ+*", "ℤ*+"] }, + // Logarithmic natural domains + { domain: Domain.NLog, shortcuts: ["NLOG", "ℕˡᵒᵍ", "ℕLOG"] }, + // All integers domains + { domain: Domain.Z, shortcuts: ["Z", "ℤ"] }, + // Zero-exclusive all integers domain + { domain: Domain.ZE, shortcuts: ["ZE", "Z*", "ℤ*"] }, + // Negative integers domain + { domain: Domain.ZM, shortcuts: ["ZM", "Z-", "ℤ⁻", "ℤ-"] }, + // Zero-exclusive negative integers domain + { domain: Domain.ZME, shortcuts: ["ZME", "ZEM", "Z-*", "Z*-", "ℤ⁻*", "ℤ*⁻", "ℤ-*", "ℤ*-"] }, + ] + + // Real domains + for(const { domain, shortcuts } of predefinedToCheck) + for(const shortcut of shortcuts) + expect(parseDomainSimple(shortcut)).to.be.equal(domain) + }) + + it("returns parsed ranges", function() { + const parsedClosed = parseDomainSimple("[1;3]") + expect(parsedClosed.includes(1)).to.be.true + expect(parsedClosed.includes(2.4)).to.be.true + expect(parsedClosed.includes(3)).to.be.true + expect(parsedClosed.includes(3.01)).to.be.false + expect(parsedClosed.includes(0.99)).to.be.false + const parsedOpen = parseDomainSimple("]1;3[") + expect(parsedOpen.includes(1)).to.be.false + expect(parsedOpen.includes(3)).to.be.false + expect(parsedOpen.includes(2.4)).to.be.true + expect(parsedOpen.includes(1.01)).to.be.true + expect(parsedOpen.includes(2.99)).to.be.true + const parsedOpenBefore = parseDomainSimple("]1;3]") + expect(parsedOpenBefore.includes(1)).to.be.false + expect(parsedOpenBefore.includes(3)).to.be.true + expect(parsedOpenBefore.includes(2.4)).to.be.true + expect(parsedOpenBefore.includes(1.01)).to.be.true + expect(parsedOpenBefore.includes(3.01)).to.be.false + const parsedOpenAfter = parseDomainSimple("[1;3[") + expect(parsedOpenAfter.includes(1)).to.be.true + expect(parsedOpenAfter.includes(3)).to.be.false + expect(parsedOpenAfter.includes(2.4)).to.be.true + expect(parsedOpenAfter.includes(0.99)).to.be.false + expect(parsedOpenAfter.includes(2.99)).to.be.true + }) + + it("does not parse invalid ranges", function() { + expect(() => parseDomainSimple("]1;2;3[")).to.throw + expect(() => parseDomainSimple("]1,2;3[")).to.throw + expect(() => parseDomainSimple("](12);3[")).to.throw + }) + }) +}) diff --git a/common/test/math/expression.mjs b/common/test/math/expression.mjs new file mode 100644 index 0000000..588480c --- /dev/null +++ b/common/test/math/expression.mjs @@ -0,0 +1,181 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Load prior tests +import "../basics/utils.mjs" +import "../module/latex.mjs" +import "../module/expreval.mjs" +import "../module/objects.mjs" + +import { describe, it } from "mocha" +import { expect } from "chai" + +import { executeExpression, Expression } from "../../src/math/expression.mjs" +import ExprEval from "../../src/module/expreval.mjs" + + +describe("Math/Expression", function() { + describe("#constructor", function() { + it("accepts strings", function() { + expect(() => new Expression("2+3")).to.not.throw + expect(() => new Expression("x+2")).to.not.throw + }) + + it("accepts already parsed expressions", function() { + expect(() => new Expression(ExprEval.parse("2+3"))).to.not.throw + expect(() => new Expression(ExprEval.parse("x+2"))).to.not.throw + }) + + it("doesn't accept anything else", function() { + expect(() => new Expression()).to.throw("Cannot create an expression with undefined.") + expect(() => new Expression(12)).to.throw("Cannot create an expression with a Number.") + expect(() => new Expression({})).to.throw("Cannot create an expression with a Object.") + expect(() => new Expression(true)).to.throw("Cannot create an expression with a Boolean.") + }) + }) + + describe("#variables", function() { + it("returns a list of variables for non-constant expressions", function() { + expect(new Expression("x+1").variables()).to.deep.equal(["x"]) + expect(new Expression("x+n").variables()).to.deep.equal(["x", "n"]) + expect(new Expression("u[n] + A.x").variables()).to.deep.equal(["u", "n", "A"]) + }) + + it("returns an empty array if the expression is constant", function() { + expect(new Expression("2+1").variables()).to.deep.equal([]) + expect(new Expression("sin π").variables()).to.deep.equal([]) + expect(new Expression("e^3").variables()).to.deep.equal([]) + }) + }) + + describe("#isConstant", function() { + it("returns true if neither x nor n are included into the expression", function() { + expect(new Expression("2+1").isConstant()).to.be.true + expect(new Expression("e^3").isConstant()).to.be.true + expect(new Expression("2+f(3)").isConstant()).to.be.true + expect(new Expression("sin A.x").isConstant()).to.be.true + }) + + it("returns false if either x or n are included into the expression", function() { + expect(new Expression("2+x").isConstant()).to.be.false + expect(new Expression("e^n").isConstant()).to.be.false + expect(new Expression("2+f(x)").isConstant()).to.be.false + expect(new Expression("n + sin x").isConstant()).to.be.false + }) + }) + + describe("#requiredObjects", function() { + it("returns the list of objects that need to be registered for this expression", function() { + expect(new Expression("x^n").requiredObjects()).to.deep.equal([]) + expect(new Expression("2+f(3)").requiredObjects()).to.deep.equal(["f"]) + expect(new Expression("A.x+x").requiredObjects()).to.deep.equal(["A"]) + expect(new Expression("2+f(sin A.x)+n").requiredObjects()).to.deep.equal(["f", "A"]) + }) + }) + + describe.skip("#allRequirementsFulfilled", function() { + // TODO: Make tests for objects + }) + + describe.skip("#undefinedVariables", function() { + // TODO: Make tests for objects + }) + + describe("#toEditableString", function() { + it("should return a readable expression", function() { + expect(new Expression("2+1").toEditableString()).to.equal("3") + expect(new Expression("2+x").toEditableString()).to.equal("(2 + x)") + expect(new Expression("x*2+x/3").toEditableString()).to.equal("((x * 2) + (x / 3))") + }) + + it("should be able to be reparsed and equal the same expression", function() { + const exprs = ["5", "x/2", "4/2", "sin x"] + for(const expr of exprs) { + const exprObj = new Expression(expr) + expect(new Expression(exprObj.toEditableString()).calc).to.deep.equal(exprObj.calc) + } + }) + }) + + describe("#execute", function() { + it("returns the result of the computation of the expression", function() { + expect(new Expression("2+3").execute()).to.equal(5) + expect(new Expression("2+3").execute(10)).to.equal(5) + expect(new Expression("2+x").execute(10)).to.equal(12) + expect(new Expression("sin x").execute(Math.PI)).to.be.approximately(0, Number.EPSILON) + }) + + it("returns the cached value if the expression can be cached", function() { + const exprs = ["2+3", "x/2", "4/2", "sin x"] + for(const expr of exprs) { + const exprObj = new Expression(expr) + if(exprObj.canBeCached) + expect(exprObj.execute()).to.equal(exprObj.cachedValue) + else + expect(exprObj.execute()).to.not.equal(exprObj.cachedValue) + } + }) + + it("throws an error if some variables are undefined.", function() { + expect(() => new Expression("x+n").execute()).to.throw("Undefined variable n.") + expect(() => new Expression("sin A.t").execute()).to.throw("Undefined variable A.") + expect(() => new Expression("f(3)").execute()).to.throw("Undefined variable f.") + }) + }) + + describe("#simplify", function() { + it("returns an expression with just the result when no constant or object are used", function() { + expect(new Expression("2+2").simplify(Math.PI/2)).to.deep.equal(new Expression("4")) + expect(new Expression("x+3").simplify(5)).to.deep.equal(new Expression("8")) + expect(new Expression("sin x").simplify(Math.PI/2)).to.deep.equal(new Expression("1")) + expect(new Expression("0*e^x").simplify(Math.PI/2)).to.deep.equal(new Expression("0")) + }) + + it("returns a simplified version of the expression if constants are used", function() { + const original = new Expression("e^x").simplify(2) + const to = new Expression("e^2") + expect(original.toEditableString()).to.deep.equal(to.toEditableString()) + }) + }) + + describe("#toString", function() { + it("returns a human readable string of the expression", function() { + expect(new Expression("-2-3").toString()).to.equal("-5") + expect(new Expression("0.2+0.1").toString()).to.equal("0.3") + expect(new Expression("sin x").toString()).to.equal("sin x") + expect(new Expression("sin π").toString()).to.equal("sin π") + }) + + it("should add a sign if the option is passed", function() { + expect(new Expression("-2-3").toString(true)).to.equal("-5") + expect(new Expression("2+3").toString(true)).to.equal("+5") + }) + }) + + describe("#executeExpression", function() { + it("directly computes the result of the expression with no variable", function() { + expect(executeExpression("2+3")).to.equal(5) + expect(executeExpression("sin (π/2)")).to.equal(1) + expect(executeExpression("e^3")).to.be.approximately(Math.pow(Math.E, 3), Number.EPSILON) + }) + + it("throws an error if variables are employed", function() { + expect(() => executeExpression("x+n")).to.throw("Undefined variable n.") + }) + }) +}) diff --git a/common/test/mock/canvas.mjs b/common/test/mock/canvas.mjs new file mode 100644 index 0000000..82bcbff --- /dev/null +++ b/common/test/mock/canvas.mjs @@ -0,0 +1,59 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + + +export class MockCanvas { + constructor(mockLoading = false) { + this.mockLoading = mockLoading + } + + getContext(context) { + throw new Error("MockCanvas.getContext not implemented") + } + + markDirty(rect) { + this.requestPaint() + } + + loadImageAsync(image) { + return new Promise((resolve, reject) => { + resolve() + }) + } + + /** + * Image loading is instantaneous. + * @param {string} image + * @return {boolean} + */ + isImageLoading(image) { + return this.mockLoading + } + + /** + * Image loading is instantaneous. + * @param {string} image + * @return {boolean} + */ + isImageLoaded(image) { + return !this.mockLoading + } + + requestPaint() { + } +} \ No newline at end of file diff --git a/common/test/mock/dialog.mjs b/common/test/mock/dialog.mjs new file mode 100644 index 0000000..ec8628d --- /dev/null +++ b/common/test/mock/dialog.mjs @@ -0,0 +1,23 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +export class MockDialog { + constructor() {} + + show() {} +} \ No newline at end of file diff --git a/common/test/mock/fs.mjs b/common/test/mock/fs.mjs new file mode 100644 index 0000000..7cb00dd --- /dev/null +++ b/common/test/mock/fs.mjs @@ -0,0 +1,44 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { readFileSync as readNode } from "node:fs" +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +export const HOME = "/home/user" +export const TMP = "/tmp" + + +const filesystem = { + [`${HOME}/test1.lpf`]: readNode(__dirname + "/../../../ci/test1.lpf") +} + + +export function existsSync(file) { + return filesystem[file] !== undefined +} + +export function writeFileSync(file, data, encoding) { + filesystem[file] = Buffer.from(data, encoding) +} + +export function readFileSync(file, encoding) { + return filesystem[file].toString(encoding) +} \ No newline at end of file diff --git a/common/test/mock/helper.mjs b/common/test/mock/helper.mjs new file mode 100644 index 0000000..9186947 --- /dev/null +++ b/common/test/mock/helper.mjs @@ -0,0 +1,116 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { readFileSync, writeFileSync, existsSync } from "./fs.mjs" + +const DEFAULT_SETTINGS = { + "check_for_updates": true, + "reset_redo_stack": true, + "last_install_greet": "0", + "enable_latex": true, + "enable_latex_threaded": true, + "expression_editor": { + "autoclose": true, + "colorize": true, + "color_scheme": 0 + }, + "autocompletion": { + "enabled": true + }, + "default_graph": { + "xzoom": 100, + "yzoom": 10, + "xmin": 5 / 10, + "ymax": 25, + "xaxisstep": "4", + "yaxisstep": "4", + "xlabel": "", + "ylabel": "", + "linewidth": 1, + "textsize": 18, + "logscalex": true, + "showxgrad": true, + "showygrad": true + } +} + +export class MockHelper { + constructor() { + this.__settings = { ...DEFAULT_SETTINGS } + } + + + /** + * Gets a setting from the config + * @param {string} settingName - Setting (and its dot-separated namespace) to get (e.g. "default_graph.xmin") + * @returns {string|number|boolean} Value of the setting + */ + getSetting(settingName) { + const namespace = settingName.split(".") + let data = this.__settings + for(const name of namespace) + if(data.hasOwnProperty(name)) + data = data[name] + else + throw new Error(`Setting ${namespace} does not exist.`) + return data + } + + /** + * Sets a setting in the config + * @param {string} settingName - Setting (and its dot-separated namespace) to set (e.g. "default_graph.xmin") + * @param {string|number|boolean} value + */ + setSetting(settingName, value) { + const namespace = settingName.split(".") + const finalName = namespace.pop() + let data = this.__settings + for(const name of namespace) + if(data.hasOwnProperty(name)) + data = data[name] + else + throw new Error(`Setting ${namespace} does not exist.`) + data[finalName] = value + } + + /** + * Sends data to be written + * @param {string} file + * @param {string} dataToWrite - just JSON encoded, requires the "LPFv1" mime to be added before writing + */ + write(file, dataToWrite) { + writeFileSync(file, "LPFv1" + dataToWrite) + } + + /** + * Requests data to be read from a file + * @param {string} file + * @returns {string} the loaded data - just JSON encoded, requires the "LPFv1" mime to be stripped + */ + load(file) { + if(existsSync(file)) { + const data = readFileSync(file, "utf8") + if(data.startsWith("LPFv1")) + return data.substring(5) + else + throw new Error(`Invalid LogarithmPlotter file.`) + } else + throw new Error(`File not found.`) + } + +} diff --git a/common/test/mock/latex.mjs b/common/test/mock/latex.mjs new file mode 100644 index 0000000..54d427d --- /dev/null +++ b/common/test/mock/latex.mjs @@ -0,0 +1,101 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { TMP, existsSync, writeFileSync } from "./fs.mjs" + +const PIXEL = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR4AWNgAAAAAgABc3UBGAAAAABJRU5ErkJggg==" + +export class MockLatex { + constructor() { + this.supportsAsyncRender = true + } + + /** + * Creates a simple string hash. + * @param {string} string + * @return {number} + * @private + */ + __hash(string) { + let hash = 0 + let i, chr + if(string.length === 0) return hash + for(i = 0; i < string.length; i++) { + chr = string.charCodeAt(i) + hash = ((hash << 5) - hash) + chr + hash |= 0 // Convert to 32bit integer + } + return hash + } + + /** + * + * @param {string} markup + * @param {number} fontSize + * @param {string} color + * @return {string} + * @private + */ + __getFileName(markup, fontSize, color) { + const name = this.__hash(`${markup}_${fontSize}_${color}`) + return `${TMP}/${name}.png` + } + + /** + * @param {string} markup - LaTeX markup to render + * @param {number} fontSize - Font size (in pt) to render + * @param {string} color - Color of the text to render + * @returns {Promise} - Comma separated data of the image (source, width, height) + */ + async renderAsync(markup, fontSize, color) { + return this.renderSync(markup, fontSize, color) + } + + /** + * @param {string} markup - LaTeX markup to render + * @param {number} fontSize - Font size (in pt) to render + * @param {string} color - Color of the text to render + * @returns {string} - Comma separated data of the image (source, width, height) + */ + renderSync(markup, fontSize, color) { + const file = this.__getFileName(markup, fontSize, color) + writeFileSync(file, PIXEL, "base64") + return `${file},1,1` + } + + /** + * @param {string} markup - LaTeX markup to render + * @param {number} fontSize - Font size (in pt) to render + * @param {string} color - Color of the text to render + * @returns {string} - Comma separated data of the image (source, width, height) + */ + findPrerendered(markup, fontSize, color) { + const file = this.__getFileName(markup, fontSize, color) + if(existsSync(file)) + return `${file},1,1` + return "" + } + + /** + * Checks if the Latex installation is valid + * @returns {boolean} + */ + checkLatexInstallation() { + return true // We're not *actually* doing any latex. + } +} diff --git a/common/test/mock/qt.mjs b/common/test/mock/qt.mjs new file mode 100644 index 0000000..46f5b3d --- /dev/null +++ b/common/test/mock/qt.mjs @@ -0,0 +1,69 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Mock qt methods. + +/** + * Polyfill for Qt.rect. + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + * @returns {{x, width, y, height}} + */ +function rect(x, y, width, height) { + return { x, y, width, height } +} + +/** + * Mock for QT_TRANSLATE_NOOP and qsTranslate + * @param {string} category + * @param {string} string + * @return {string} + */ +function QT_TRANSLATE_NOOP(category, string) { + return string +} + +/** + * Polyfilling Qt arg function. + * @param {string} argument + */ +String.prototype.arg = function(argument) { + for(let i = 0; i < 10; i++) + if(this.includes("%"+i)) + return this.replaceAll("%" + i, argument) + throw new Error("Too many arguments used.") +} + +function setup() { + globalThis.Qt = { + rect + } + + globalThis.QT_TRANSLATE_NOOP = QT_TRANSLATE_NOOP + globalThis.qsTranslate = QT_TRANSLATE_NOOP +} + +setup() + +export default { + rect, + QT_TRANSLATE_NOOP, + qtTranslate: QT_TRANSLATE_NOOP, +} \ No newline at end of file diff --git a/common/test/mock/root.mjs b/common/test/mock/root.mjs new file mode 100644 index 0000000..0539342 --- /dev/null +++ b/common/test/mock/root.mjs @@ -0,0 +1,61 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +/** + * Mock for root element with width and height property. + * setWidth, setHeight, getWidth, and getHeight methods can be spied on to check + * when the accessor is called. + */ +export class MockRootElement { + #width = 0 + #height = 0 + + constructor() {} + + setWidth(width) { + this.#width = width; + } + + getWidth() { + return this.#width + } + + setHeight(height) { + this.#height = height; + } + + getHeight() { + return this.#height + } + + get width() { + return this.getWidth() + } + + set width(value) { + this.setWidth(value) + } + + get height() { + return this.getHeight() + } + + set height(value) { + this.setHeight(value) + } +} \ No newline at end of file diff --git a/common/test/module/base.mjs b/common/test/module/base.mjs new file mode 100644 index 0000000..0cc73ea --- /dev/null +++ b/common/test/module/base.mjs @@ -0,0 +1,89 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Load prior tests +import "../basics/events.mjs" +import "../basics/interface.mjs" + +import { describe, it } from "mocha" +import { expect } from "chai" +import { MockDialog } from "../mock/dialog.mjs" +import { BOOLEAN, DialogInterface, FUNCTION, NUMBER, STRING } from "../../src/module/interface.mjs" +import { Module } from "../../src/module/common.mjs" + +class MockModule extends Module { + constructor() { + super("mock", { + number: NUMBER, + bool: BOOLEAN, + str: STRING, + func: FUNCTION, + dialog: DialogInterface + }) + } +} + +describe("Module/Base", function() { + it("defined a Modules global", function() { + expect(globalThis.Modules).to.not.be.undefined + }) + + it("is not be initialized upon construction", function() { + const module = new MockModule() + expect(module.name).to.equal("mock") + expect(module.initialized).to.be.false + }) + + it("is only be initialized with the right arguments", function() { + const module = new MockModule() + const initializeWithNoArg = () => module.initialize({}) + const initializeWithSomeArg = () => module.initialize({ number: 0, str: "" }) + const initializeWithWrongType = () => module.initialize({ + number: () => {}, + str: 0, + bool: "", + func: false, + dialog: null + }) + const initializeProperly = () => module.initialize({ + number: 0, + str: "", + bool: true, + func: FUNCTION, + dialog: new MockDialog() + }) + expect(initializeWithNoArg).to.throw(Error) + expect(initializeWithSomeArg).to.throw(Error) + expect(initializeWithWrongType).to.throw(Error) + expect(initializeProperly).to.not.throw(Error) + expect(module.initialized).to.be.true + }) + + it("cannot be initialized twice", function() { + const module = new MockModule() + const initialize = () => module.initialize({ + number: 0, + str: "", + bool: true, + func: FUNCTION, + dialog: new MockDialog() + }) + expect(initialize).to.not.throw(Error) + expect(initialize).to.throw(Error) + }) +}) \ No newline at end of file diff --git a/common/test/module/expreval.mjs b/common/test/module/expreval.mjs new file mode 100644 index 0000000..c04290e --- /dev/null +++ b/common/test/module/expreval.mjs @@ -0,0 +1,389 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Load prior tests +import "./base.mjs" + +import { describe, it } from "mocha" +import { expect } from "chai" + +import ExprEval from "../../src/module/expreval.mjs" + +describe("Module/ExprEval", function() { + describe("#parse.evaluate", function() { + const evaluate = (expr, vals = {}) => ExprEval.parse(expr).evaluate(vals) + it("parses simple mathematical expressions", function() { + expect(evaluate(`"\\'\\"\\\\\\/\\b\\f\\n\\r\\t\\u3509"`)).to.equal(`'"\\/\b\f\n\r\t\u3509`) + expect(evaluate("1")).to.equal(1) + expect(evaluate(" 1 ")).to.equal(1) + expect(evaluate("0xFF")).to.equal(255) + expect(evaluate("0b11")).to.equal(3) + expect(evaluate("-1")).to.equal(-1) + expect(evaluate("-(-1)")).to.equal(1) + expect(evaluate("+(-1)")).to.equal(-1) + expect(evaluate("3!")).to.equal(6) + expect(evaluate("1+1")).to.equal(2) + expect(evaluate("4*3")).to.equal(12) + expect(evaluate("4•3")).to.equal(12) + expect(evaluate("64/4")).to.equal(16) + expect(evaluate("2^10")).to.equal(1024) + expect(evaluate("10%3")).to.equal(1) + expect(evaluate("10%3")).to.equal(1) + // Test priorities + expect(evaluate("10*10+10*10")).to.equal(200) + expect(evaluate("10/10+10/10")).to.equal(2) + expect(evaluate("10/10+10/10")).to.equal(2) + expect(evaluate("2^2-2^2")).to.equal(0) + expect(evaluate("(2^2-2)^2")).to.equal(4) + }) + + it("parses equality and test statements", function() { + expect(evaluate("10%3 == 1 ? 2 : 1")).to.equal(2) + expect(evaluate("not(10%3 == 1) ? 2 : 1")).to.equal(1) + expect(evaluate("10%3 != 1 ? 2 : 1")).to.equal(1) + expect(evaluate("10 < 3 ? 2 : 1")).to.equal(1) + expect(evaluate("10 > 3 ? (2+1) : 1")).to.equal(3) + expect(evaluate("10 <= 3 ? 4 : 1")).to.equal(1) + expect(evaluate("10 >= 3 ? 4 : 1")).to.equal(4) + // Check equality + expect(evaluate("10 < 10 ? 2 : 1")).to.equal(1) + expect(evaluate("10 > 10 ? 2 : 1")).to.equal(1) + expect(evaluate("10 <= 10 ? 4 : 1")).to.equal(4) + expect(evaluate("10 >= 10 ? 4 : 1")).to.equal(4) + // Check 'and' and 'or' + expect(evaluate("10 <= 3 and 10 < 10 ? 4 : 1")).to.equal(1) + expect(evaluate("10 <= 10 and 10 < 10 ? 4 : 1")).to.equal(1) + expect(evaluate("10 <= 10 and 10 < 20 ? 4 : 1")).to.equal(4) + expect(evaluate("10 <= 3 or 10 < 10 ? 4 : 1")).to.equal(1) + expect(evaluate("10 <= 10 or 10 < 10 ? 4 : 1")).to.equal(4) + expect(evaluate("10 <= 10 or 10 < 20 ? 4 : 1")).to.equal(4) + }) + + it("parses singular function operators (functions with one arguments and no parenthesis)", function() { + // Trigonometric functions + expect(evaluate("sin 0")).to.be.approximately(0, Number.EPSILON) + expect(evaluate("cos 0")).to.be.approximately(1, Number.EPSILON) + expect(evaluate("tan 0")).to.be.approximately(0, Number.EPSILON) + expect(evaluate("asin 1")).to.be.approximately(Math.PI / 2, Number.EPSILON) + expect(evaluate("acos 1")).to.be.approximately(0, Number.EPSILON) + expect(evaluate("atan 1")).to.be.approximately(Math.PI / 4, Number.EPSILON) + expect(evaluate("sinh 1")).to.be.approximately(Math.sinh(1), Number.EPSILON) + expect(evaluate("cosh 1")).to.be.approximately(Math.cosh(1), Number.EPSILON) + expect(evaluate("tanh 1")).to.be.approximately(Math.tanh(1), Number.EPSILON) + expect(evaluate("asinh 1")).to.be.approximately(Math.asinh(1), Number.EPSILON) + expect(evaluate("acosh 1")).to.be.approximately(Math.acosh(1), Number.EPSILON) + expect(evaluate("atanh 0.5")).to.be.approximately(Math.atanh(0.5), Number.EPSILON) + // Reverse trigonometric + expect(evaluate("asin sin 1")).to.be.approximately(1, Number.EPSILON) + expect(evaluate("acos cos 1")).to.be.approximately(1, Number.EPSILON) + expect(evaluate("atan tan 1")).to.be.approximately(1, Number.EPSILON) + expect(evaluate("asinh sinh 1")).to.be.approximately(1, Number.EPSILON) + expect(evaluate("acosh cosh 1")).to.be.approximately(1, Number.EPSILON) + expect(evaluate("atanh tanh 1")).to.be.approximately(1, Number.EPSILON) + // Other functions + expect(evaluate("sqrt 4")).to.be.approximately(2, Number.EPSILON) + expect(evaluate("sqrt 2")).to.be.approximately(Math.sqrt(2), Number.EPSILON) + expect(evaluate("cbrt 27")).to.be.approximately(3, Number.EPSILON) + expect(evaluate("cbrt 14")).to.be.approximately(Math.cbrt(14), Number.EPSILON) + expect(evaluate("log 1")).to.be.approximately(Math.log(1), Number.EPSILON) + expect(evaluate("ln 1")).to.be.approximately(Math.log(1), Number.EPSILON) + expect(evaluate("log2 8")).to.be.approximately(3, Number.EPSILON) + expect(evaluate("log10 100")).to.be.approximately(2, Number.EPSILON) + expect(evaluate("lg 100")).to.be.approximately(2, Number.EPSILON) + expect(evaluate("expm1 0")).to.be.approximately(0, Number.EPSILON) + expect(evaluate("expm1 10")).to.be.approximately(Math.expm1(10), Number.EPSILON) + expect(evaluate("log1p 0")).to.be.approximately(0, Number.EPSILON) + expect(evaluate("log1p 10")).to.be.approximately(Math.log1p(10), Number.EPSILON) + // Roundings/Sign transformations + expect(evaluate("abs -12.34")).to.equal(12.34) + expect(evaluate("abs 12.45")).to.equal(12.45) + expect(evaluate("ceil 12.45")).to.equal(13) + expect(evaluate("ceil 12.75")).to.equal(13) + expect(evaluate("ceil 12.0")).to.equal(12) + expect(evaluate("ceil -12.6")).to.equal(-12) + expect(evaluate("floor 12.45")).to.equal(12) + expect(evaluate("floor 12.75")).to.equal(12) + expect(evaluate("floor 12.0")).to.equal(12) + expect(evaluate("floor -12.2")).to.equal(-13) + expect(evaluate("round 12.45")).to.equal(12) + expect(evaluate("round 12.75")).to.equal(13) + expect(evaluate("round 12.0")).to.equal(12) + expect(evaluate("round -12.2")).to.equal(-12) + expect(evaluate("round -12.6")).to.equal(-13) + expect(evaluate("trunc 12.45")).to.equal(12) + expect(evaluate("trunc 12.75")).to.equal(12) + expect(evaluate("trunc 12.0")).to.equal(12) + expect(evaluate("trunc -12.2")).to.equal(-12) + expect(evaluate("exp 1")).to.be.approximately(Math.E, Number.EPSILON) + expect(evaluate("exp 10")).to.be.approximately(Math.pow(Math.E, 10), 1e-8) + expect(evaluate("length \"string\"")).to.equal(6) + expect(evaluate("sign 0")).to.equal(0) + expect(evaluate("sign -0")).to.equal(0) + expect(evaluate("sign -10")).to.equal(-1) + expect(evaluate("sign 80")).to.equal(1) + }) + + it("parses regular functions", function() { + for(let i = 0; i < 1000; i++) { + expect(evaluate("random()")).to.be.within(0, 1) + expect(evaluate("random(100)")).to.be.within(0, 100) + } + expect(evaluate("fac(3)")).to.equal(6) + expect(evaluate("fac(10)")).to.equal(3628800) + expect(evaluate("min(10, 20)")).to.equal(10) + expect(evaluate("min(-10, -20)")).to.equal(-20) + expect(evaluate("max(10, 20)")).to.equal(20) + expect(evaluate("max(-10, -20)")).to.equal(-10) + expect(evaluate("hypot(3, 4)")).to.equal(5) + expect(evaluate("pyt(30, 40)")).to.equal(50) + expect(evaluate("atan2(1, 1)")).to.be.approximately(Math.PI / 4, Number.EPSILON) + expect(evaluate("atan2(1, 0)")).to.be.approximately(Math.PI / 2, Number.EPSILON) + expect(evaluate("atan2(0, 1)")).to.be.approximately(0, Number.EPSILON) + expect(evaluate("if(10 == 10, 1, 0)")).to.be.approximately(1, Number.EPSILON) + expect(evaluate("if(10 != 10, 1, 0)")).to.be.approximately(0, Number.EPSILON) + expect(evaluate("gamma(10) == 9!")).to.be.true + expect(evaluate("Γ(30) == 29!")).to.be.true + expect(evaluate("Γ(25) == 23!")).to.be.false + expect(evaluate("roundTo(26.04)")).to.equal(26) + expect(evaluate("roundTo(26.04, 2)")).to.equal(26.04) + expect(evaluate("roundTo(26.04836432123, 5)")).to.equal(26.04836) + expect(evaluate("roundTo(26.04836432123, 5)")).to.equal(26.04836) + }) + + it("parses arrays and access their members", function() { + expect(evaluate("[6, 7, 9]")).to.have.lengthOf(3) + expect(evaluate("[6, 7, 9]")).to.deep.equal([6, 7, 9]) + expect(evaluate("[6, \"8\", 9]")).to.have.lengthOf(3) + expect(evaluate("[6, 7%2]")).to.deep.equal([6, 1]) + // Access array indices + expect(evaluate("[6, 7][1]")).to.equal(7) + expect(evaluate("[6, 7, 8, 9, 10][2*2-1]")).to.equal(9) + }) + + it("can apply functions to arrays", function() { + expect(evaluate("length [6, 7, 9]")).to.equal(3) + expect(evaluate("length [6, 7, 8, 9]")).to.equal(4) + expect(evaluate("[6, 7, 9]||[10,11,12]")).to.deep.equal([6, 7, 9, 10, 11, 12]) + expect(evaluate("6 in [6, 7, 9]")).to.be.true + expect(evaluate("2 in [6, 7, 9]")).to.be.false + expect(evaluate("min([10, 6, 7, 8, 9])")).to.equal(6) + expect(evaluate("max([6, 7, 8, 9, 2])")).to.equal(9) + }) + + it("throws errors when invalid function parameters are provided", function() { + expect(() => evaluate("max()")).to.throw() + expect(() => evaluate("min()")).to.throw() + }) + + it("parses constants", function() { + expect(evaluate("pi")).to.equal(Math.PI) + expect(evaluate("PI")).to.equal(Math.PI) + expect(evaluate("π")).to.equal(Math.PI) + expect(evaluate("e")).to.equal(Math.E) + expect(evaluate("E")).to.equal(Math.E) + expect(evaluate("true")).to.be.true + expect(evaluate("false")).to.be.false + // expect(evaluate("∞")).to.equal(Math.Infinity) + // expect(evaluate("infinity")).to.equal(Math.Infinity) + // expect(evaluate("Infinity")).to.equal(Math.Infinity) + }) + + it("can be provided variables", function() { + const u = [1, 2, 3, 4] + const x = 10 + const s_ = "string" + const f = (x) => x * 2 + expect(evaluate("u", { u })).to.deep.equal([...u]) + expect(evaluate("x", { x })).to.equal(x) + expect(evaluate("s_", { s_ })).to.equal(s_) + expect(evaluate("f", { f })).to.equal(f) + expect(evaluate("b", { b: true })).to.equal(true) + expect(evaluate("u[1]", { u })).to.equal(u[1]) + expect(evaluate("x/2", { x })).to.equal(x / 2) + expect(evaluate("f(2)", { f })).to.equal(f(2)) + expect(evaluate("if(x == f(2), u[0], s_)", { x, u, s_, f })).to.equal(s_) + }) + + it("can be provided objects", function() { + const obj = { execute: (x) => x * 3, x: 10, y: { cached: true, execute: () => 20 } } + expect(evaluate("O(3)+O(2)", { O: obj })).to.equal(9 + 6) + expect(evaluate("O.x+O.y", { O: obj })).to.equal(30) + }) + + it("throws errors when trying to use variables wrongly", function() { + const obj = { execute: (x) => x * 3 } + expect(() => evaluate("O()", { O: obj })).to.throw() + expect(() => evaluate("O.x", { O: obj })).to.throw() + expect(() => evaluate("x()", { x: 10 })).to.throw() + expect(() => evaluate("x")).to.throw() + expect(() => evaluate("n")).to.throw() + }) + + it("can do it all at once", function() { + const obj = { execute: (x) => x * 3, x: 20 } + const u = [1, 2, 3, 4] + const x = 10 + const s = "string" + const expr = "random(e) <= e ? fac(x)+u[2]+O(pi) : O.x+length s" + expect(evaluate(expr, { x, u, s, O: obj })).to.equal(3628803 + obj.execute(Math.PI)) + }) + + it("cannot parse invalid expressions", function() { + expect(() => evaluate("1+")).to.throw() + expect(() => evaluate("@")).to.throw() + expect(() => evaluate("]")).to.throw() + expect(() => evaluate("")).to.throw() + expect(() => evaluate(`"\\u35P2"`)).to.throw() + expect(() => evaluate(`"\\x"`)).to.throw() + }) + }) + + describe("#parse.toString", function() { + it("can be converted back into a string without changes", function() { + const expressions = ["pi+2*(e+2)^4", "sin(1+2!+pi+cos -3)^2", "[2,3,4][(2-1)*2]", "true ? false : true"] + for(const ogString of expressions) { + const expr = ExprEval.parse(ogString) + const convertedString = expr.toString() + expect(ExprEval.parse(convertedString)).to.deep.equal(expr) // Can be reparsed just the same + } + }) + }) + + describe("#parse.substitute", function() { + const parsed = ExprEval.parse("if(x == 0, 1, 2+x)") + it("can substitute a variable for a number", function() { + expect(parsed.substitute("x", 10).evaluate({})).to.equal(12) + expect(parsed.substitute("x", 0).evaluate({})).to.equal(1) + }) + + it("can substitute a variable for another", function() { + expect(parsed.substitute("x", "b").evaluate({ b: 10 })).to.equal(12) + expect(parsed.substitute("x", "b").evaluate({ b: 0 })).to.equal(1) + }) + + it("can substitute a variable for an expression", function() { + expect(parsed.substitute("x", "sin α").evaluate({ "α": Math.PI / 2 })).to.be.approximately(3, Number.EPSILON) + expect(parsed.substitute("x", "sin α").evaluate({ "α": 0 })).to.equal(1) + expect(parsed.substitute("x", "α == 1 ? 0 : 1").evaluate({ "α": 1 })).to.equal(1) + }) + }) + + describe("#parse.variables", function() { + it("can list all parsed undefined variables", function() { + expect(ExprEval.parse("a+b+x+pi+sin(b)").variables()).to.deep.equal(["a", "b", "x"]) + }) + }) + + describe("#parse.toJSFunction", function() { + const func = ExprEval.parse("not(false) ? a+b+x+1/x : x!+random()+A.x+[][0]").toJSFunction("x", { a: "10", b: "0" }) + expect(func(10)).to.equal(20.1) + expect(func(20)).to.equal(30.05) + }) + + + describe("#integral", function() { + it("returns the integral value between two integers", function() { + expect(ExprEval.integral(0, 1, "1", "t")).to.be.approximately(1, Number.EPSILON) + expect(ExprEval.integral(0, 1, "t", "t")).to.be.approximately(1 / 2, Number.EPSILON) + expect(ExprEval.integral(0, 1, "t^2", "t")).to.be.approximately(1 / 3, Number.EPSILON) + expect(ExprEval.integral(0, 1, "t^3", "t")).to.be.approximately(1 / 4, 0.01) + expect(ExprEval.integral(0, 1, "t^4", "t")).to.be.approximately(1 / 5, 0.01) + + expect(ExprEval.integral(10, 40, "1", "t")).to.equal(30) + expect(ExprEval.integral(20, 40, "1", "t")).to.equal(20) + + expect(ExprEval.integral(0, 10, { execute: (x) => 1 })).to.equal(10) + expect(ExprEval.integral(0, 10, { execute: (x) => x })).to.equal(50) + expect(ExprEval.integral(0, 1, { execute: (x) => Math.pow(x, 2) })).to.equal(1 / 3) + }) + + + it("throws error when provided with invalid arguments", function() { + const noArg1 = () => ExprEval.integral() + const noArg2 = () => ExprEval.integral(0) + const noFunction = () => ExprEval.integral(0, 1) + const invalidObjectProvided = () => ExprEval.integral(0, 1, { a: 2 }) + const notAnObjectProvided = () => ExprEval.integral(0, 1, "string") + const invalidFromProvided = () => ExprEval.integral("ze", 1, "t^2", "t") + const invalidToProvided = () => ExprEval.integral(0, "ze", "t^2", "t") + const notStringProvided1 = () => ExprEval.integral(0, 1, { a: 2 }, { b: 1 }) + const notStringProvided2 = () => ExprEval.integral(0, 1, { a: 2 }, "t") + const notStringProvided3 = () => ExprEval.integral(0, 1, "t^2", { b: 1 }) + const invalidVariableProvided = () => ExprEval.integral(0, 1, "t^2", "93IO74") + const invalidExpressionProvided = () => ExprEval.integral(0, 1, "t^2t", "t") + const invalidVariableInExpression = () => ExprEval.integral(0, 1, "t^2+x", "t") + expect(noArg1).to.throw() + expect(noArg2).to.throw() + expect(noFunction).to.throw() + expect(invalidObjectProvided).to.throw() + expect(invalidFromProvided).to.throw() + expect(invalidToProvided).to.throw() + expect(notAnObjectProvided).to.throw() + expect(notStringProvided1).to.throw() + expect(notStringProvided2).to.throw() + expect(notStringProvided3).to.throw() + expect(invalidVariableProvided).to.throw() + expect(invalidExpressionProvided).to.throw() + expect(invalidVariableInExpression).to.throw() + }) + }) + + describe("#derivative", function() { + const DELTA = 1e-5 + it("returns the derivative value of a function at a given number", function() { + expect(ExprEval.derivative("1", "t", 2)).to.be.approximately(0, DELTA) + expect(ExprEval.derivative("t", "t", 2)).to.be.approximately(1, DELTA) + expect(ExprEval.derivative("t^2", "t", 2)).to.be.approximately(4, DELTA) + expect(ExprEval.derivative("t^3", "t", 2)).to.be.approximately(12, DELTA) + expect(ExprEval.derivative("t^4", "t", 2)).to.be.approximately(32, DELTA) + + expect(ExprEval.derivative({ execute: (x) => 1 }, 10)).to.equal(0) + expect(ExprEval.derivative({ execute: (x) => x }, 10)).to.be.approximately(1, DELTA) + expect(ExprEval.derivative({ execute: (x) => Math.pow(x, 2) }, 10)).to.be.approximately(20, DELTA) + }) + + it("throws error when provided with invalid arguments", function() { + const noArg1 = () => ExprEval.derivative() + const noArg2 = () => ExprEval.derivative("1") + const noValue1 = () => ExprEval.derivative("0", "1") + const noValue2 = () => ExprEval.derivative({ execute: (x) => 1 }) + const invalidObjectProvided = () => ExprEval.derivative({ a: 2 }, 1) + const notAnObjectProvided = () => ExprEval.derivative("string", 1) + const invalidXProvided = () => ExprEval.derivative("t^2+x", "t", "ze") + const notStringProvided1 = () => ExprEval.derivative({ a: 2 }, { b: 1 }, 1) + const notStringProvided2 = () => ExprEval.derivative({ a: 2 }, "t", 1) + const notStringProvided3 = () => ExprEval.derivative("t^2", { b: 1 }, 1) + const invalidVariableProvided = () => ExprEval.derivative("t^2", "93IO74", 1) + const invalidExpressionProvided = () => ExprEval.derivative("t^2t", "t", 1) + const invalidVariableInExpression = () => ExprEval.derivative("t^2+x", "t", 1) + expect(noArg1).to.throw() + expect(noArg2).to.throw() + expect(noValue1).to.throw() + expect(noValue2).to.throw() + expect(invalidObjectProvided).to.throw() + expect(invalidXProvided).to.throw() + expect(notAnObjectProvided).to.throw() + expect(notStringProvided1).to.throw() + expect(notStringProvided2).to.throw() + expect(notStringProvided3).to.throw() + expect(invalidVariableProvided).to.throw() + expect(invalidExpressionProvided).to.throw() + expect(invalidVariableInExpression).to.throw() + }) + }) +}) \ No newline at end of file diff --git a/common/test/module/latex.mjs b/common/test/module/latex.mjs new file mode 100644 index 0000000..6581850 --- /dev/null +++ b/common/test/module/latex.mjs @@ -0,0 +1,231 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Load prior tests +import "../basics/utils.mjs" +import "./base.mjs" +import "./expreval.mjs" + +import { describe, it } from "mocha" +import { expect } from "chai" +import { existsSync } from "../mock/fs.mjs" + +const { spy } = chaiPlugins + +import ExprEval from "../../src/module/expreval.mjs" +import LatexAPI from "../../src/module/latex.mjs" + +describe("Module/Latex", function() { + it("is defined as a global", function() { + expect(globalThis.Modules.Latex).to.equal(LatexAPI) + }) + + describe("#initialize", function() { + it("isn't enabled before initialization", function() { + expect(LatexAPI.enabled).to.be.false + }) + + it("is enabled after initialization", function() { + LatexAPI.initialize({ latex: Latex, helper: Helper }) + expect(LatexAPI.enabled).to.equal(Helper.getSetting("enable_latex")) + expect(LatexAPI.initialized).to.be.true + }) + }) + + describe("#requestAsyncRender", function() { + it("returns a render result with a valid source, a width, and a height", async function() { + const data = await LatexAPI.requestAsyncRender("\\frac{x}{3}", 13, "#AA0033") + expect(data).to.be.an("object") + expect(data.source).to.be.a("string") + expect(data.source).to.satisfy(existsSync) + expect(data.height).to.be.a("number") + expect(data.width).to.be.a("number") + }) + + it("calls functions from the LaTeX module", async function() { + const renderSyncSpy = spy.on(Latex, "renderSync") + const renderAsyncSpy = spy.on(Latex, "renderAsync") + Latex.supportsAsyncRender = true + await LatexAPI.requestAsyncRender("\\frac{x}{3}", 13, "#AA0033") + expect(renderAsyncSpy).to.have.been.called.once + expect(renderSyncSpy).to.have.been.called.once // Called async + Latex.supportsAsyncRender = false + await LatexAPI.requestAsyncRender("\\frac{x}{3}", 13, "#AA0033") + expect(renderAsyncSpy).to.have.been.called.once // From the time before + expect(renderSyncSpy).to.have.been.called.twice + Latex.supportsAsyncRender = true + }) + + it("should not reply with the same source for different markup, font size, or color.", async function() { + const datas = [ + await LatexAPI.requestAsyncRender("\\frac{x}{3}", 13, "#AA0033"), + await LatexAPI.requestAsyncRender("\\frac{x}{4}", 13, "#AA0033"), + await LatexAPI.requestAsyncRender("\\frac{x}{3}", 14, "#AA0033"), + await LatexAPI.requestAsyncRender("\\frac{x}{3}", 13, "#0033AA") + ] + const sources = datas.map(x => x.source) + expect(new Set(sources)).to.have.a.lengthOf(4) + }) + }) + + describe("#findPrerendered", function() { + it("returns the same data as async render for the same markup, font size, and color", async function() { + const data = await LatexAPI.requestAsyncRender("\\frac{x}{3}", 13, "#AA0033") + const found = LatexAPI.findPrerendered("\\frac{x}{3}", 13, "#AA0033") + expect(found).to.not.be.null + expect(found.source).to.equal(data.source) + expect(found.width).to.equal(data.width) + }) + + it("returns null if the markup hasn't been prerendered with the same markup, font size, and color", async function() { + await LatexAPI.requestAsyncRender("\\frac{x}{3}", 13, "#AA0033") + expect(LatexAPI.findPrerendered("\\frac{y}{3}", 13, "#AA0033")).to.be.null + expect(LatexAPI.findPrerendered("\\frac{x}{3}", 12, "#AA0033")).to.be.null + expect(LatexAPI.findPrerendered("\\frac{x}{3}", 13, "#3300AA")).to.be.null + }) + }) + + describe("#par", function() { + it("adds parentheses to strings", function() { + expect(LatexAPI.par("string")).to.equal("(string)") + expect(LatexAPI.par("aaaa")).to.equal("(aaaa)") + expect(LatexAPI.par("")).to.equal("()") + expect(LatexAPI.par("(example)")).to.equal("((example))") + }) + }) + + describe("#parif", function() { + it("adds parentheses to strings that contain one of the ones in the list", function() { + expect(LatexAPI.parif("string", ["+"])).to.equal("string") + expect(LatexAPI.parif("string+assert", ["+"])).to.equal("(string+assert)") + expect(LatexAPI.parif("string+assert", ["+", "-"])).to.equal("(string+assert)") + expect(LatexAPI.parif("string-assert", ["+", "-"])).to.equal("(string-assert)") + }) + + it("doesn't add new parentheses to strings that contains one of the ones in the list if they already have one", function() { + expect(LatexAPI.parif("(string+assert", ["+"])).to.equal("((string+assert)") + expect(LatexAPI.parif("string+assert)", ["+"])).to.equal("(string+assert))") + expect(LatexAPI.parif("(string+assert)", ["+"])).to.equal("(string+assert)") + expect(LatexAPI.parif("(string+assert)", ["+", "-"])).to.equal("(string+assert)") + expect(LatexAPI.parif("(string-assert)", ["+", "-"])).to.equal("(string-assert)") + }) + + it("doesn't add parentheses to strings that does not contains one of the ones in the list", function() { + expect(LatexAPI.parif("string", ["+"])).to.equal("string") + expect(LatexAPI.parif("string+assert", ["-"])).to.equal("string+assert") + expect(LatexAPI.parif("(string*assert", ["+", "-"])).to.equal("(string*assert") + expect(LatexAPI.parif("string/assert)", ["+", "-"])).to.equal("string/assert)") + }) + + it("removes parentheses from strings that does not contains one of the ones in the list", function() { + expect(LatexAPI.parif("(string)", ["+"])).to.equal("string") + expect(LatexAPI.parif("(string+assert)", ["-"])).to.equal("string+assert") + expect(LatexAPI.parif("((string*assert)", ["+", "-"])).to.equal("(string*assert") + expect(LatexAPI.parif("(string/assert))", ["+", "-"])).to.equal("string/assert)") + }) + }) + + describe("#variable", function() { + const from = [ + "α", "β", "γ", "δ", "ε", "ζ", "η", + "π", "θ", "κ", "λ", "μ", "ξ", "ρ", + "ς", "σ", "τ", "φ", "χ", "ψ", "ω", + "Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ", + "Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ", + "ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ", + "ₜ", "¹", "²", "³", "⁴", "⁵", "⁶", + "⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃", + "₄", "₅", "₆", "₇", "₈", "₉", "₀", + "pi", "∞"] + const to = [ + "\\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"] + + it("converts unicode characters to their latex equivalent", function() { + for(let i = 0; i < from.length; i++) + expect(LatexAPI.variable(from[i])).to.include(to[i]) + }) + + it("wraps within dollar signs when the option is included", function() { + for(let i = 0; i < from.length; i++) { + expect(LatexAPI.variable(from[i], false)).to.equal(to[i]) + expect(LatexAPI.variable(from[i], true)).to.equal(`$${to[i]}$`) + } + }) + + it("can convert multiple of them", function() { + expect(LatexAPI.variable("α₂", false)).to.equal("\\alpha{}_{2}") + expect(LatexAPI.variable("∞piΠ", false)).to.equal("\\infty\\pi\\Pi") + }) + }) + + describe("#functionToLatex", function() { + it("transforms derivatives into latex fractions", function() { + const d1 = LatexAPI.functionToLatex("derivative", ["'3t'", "'t'", "x+2"]) + const d2 = LatexAPI.functionToLatex("derivative", ["f", "x+2"]) + expect(d1).to.equal("\\frac{d3x}{dx}") + expect(d2).to.equal("\\frac{df}{dx}(x+2)") + }) + + it("transforms integrals into latex limits", function() { + const i1 = LatexAPI.functionToLatex("integral", ["0", "x", "'3y'", "'y'"]) + const i2 = LatexAPI.functionToLatex("integral", ["1", "2", "f"]) + expect(i1).to.equal("\\int\\limits_{0}^{x}3y dy") + expect(i2).to.equal("\\int\\limits_{1}^{2}f(t) dt") + }) + + it("transforms sqrt functions to sqrt latex", function() { + const sqrt1 = LatexAPI.functionToLatex("sqrt", ["(x+2)"]) + const sqrt2 = LatexAPI.functionToLatex("sqrt", ["\\frac{x}{2}"]) + expect(sqrt1).to.equal("\\sqrt{x+2}") + expect(sqrt2).to.equal("\\sqrt{\\frac{x}{2}}") + }) + + it("transforms abs, floor and ceil", function() { + const abs = LatexAPI.functionToLatex("abs", ["x+3"]) + const floor = LatexAPI.functionToLatex("floor", ["x+3"]) + const ceil = LatexAPI.functionToLatex("ceil", ["x+3"]) + expect(abs).to.equal("\\left|x+3\\right|") + expect(floor).to.equal("\\left\\lfloor{x+3}\\right\\rfloor") + expect(ceil).to.equal("\\left\\lceil{x+3}\\right\\rceil") + }) + + it("transforms regular functions into latex", function() { + const f1 = LatexAPI.functionToLatex("f", ["x+3", true]) + const f2 = LatexAPI.functionToLatex("h_1", ["10"]) + expect(f1).to.equal("\\mathrm{f}\\left(x+3, true\\right)") + expect(f2).to.equal("\\mathrm{h_1}\\left(10\\right)") + }) + }) + + describe("#expression", function() { + it("transforms parsed expressions", function() { + const expr = ExprEval.parse("(+1! == 2/2 ? sin [-2.2][0] : f(t)^(1+1-1) + sqrt(A.t)) * 3 % 1") + const expected = "((((+1!))==(\\frac{2}{2}) ? (\\mathrm{sin}\\left(([(-2.2)][0])\\right)) : (\\mathrm{f}\\left(t\\right)^{1+1-1}+\\sqrt{A.t})) \\times 3) \\mathrm{mod} 1" + expect(LatexAPI.expression(expr.tokens)).to.equal(expected) + }) + }) +}) \ No newline at end of file diff --git a/common/test/module/objects.mjs b/common/test/module/objects.mjs new file mode 100644 index 0000000..73b9e25 --- /dev/null +++ b/common/test/module/objects.mjs @@ -0,0 +1,31 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Load prior tests +import "./base.mjs" +import "../basics/utils.mjs" + +import { describe, it } from "mocha" +import { expect } from "chai" + +// import Objects from "../../src/module/objects.mjs" +// +// describe("Module/Objects", function() { +// describe("#getNewName", function() { +// }) +// }) \ No newline at end of file diff --git a/common/test/module/settings.mjs b/common/test/module/settings.mjs new file mode 100644 index 0000000..b266bb2 --- /dev/null +++ b/common/test/module/settings.mjs @@ -0,0 +1,101 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . + */ + +// Load prior tests +import "./base.mjs" +import "../basics/utils.mjs" + +import { describe, it } from "mocha" +import { expect } from "chai" + +const { spy } = chaiPlugins + +import Settings from "../../src/module/settings.mjs" +import { BaseEvent } from "../../src/events.mjs" + +describe("Module/Settings", function() { + it("is defined as a global", function() { + expect(globalThis.Modules.Settings).to.equal(Settings) + }) + + it("has base defined properties even before initialization", function() { + expect(Settings.saveFilename).to.be.a("string") + expect(Settings.xzoom).to.be.a("number") + expect(Settings.yzoom).to.be.a("number") + expect(Settings.xmin).to.be.a("number") + expect(Settings.ymax).to.be.a("number") + expect(Settings.xaxisstep).to.be.a("string") + expect(Settings.yaxisstep).to.be.a("string") + expect(Settings.xlabel).to.be.a("string") + expect(Settings.ylabel).to.be.a("string") + expect(Settings.linewidth).to.be.a("number") + expect(Settings.textsize).to.be.a("number") + expect(Settings.logscalex).to.be.a("boolean") + expect(Settings.showxgrad).to.be.a("boolean") + expect(Settings.showygrad).to.be.a("boolean") + }) + + it("can be set values, but only of the right type", function() { + expect(() => Settings.set("xzoom", "", false)).to.throw() + expect(() => Settings.set("xlabel", true, false)).to.throw() + expect(() => Settings.set("showxgrad", 2, false)).to.throw() + + expect(() => Settings.set("xzoom", 200, false)).to.not.throw() + expect(() => Settings.set("xlabel", "x", false)).to.not.throw() + expect(() => Settings.set("showxgrad", false, false)).to.not.throw() + }) + + it("cannot be set unknown settings", function() { + expect(() => Settings.set("unknown", "", false)).to.throw() + }) + + it("sends an event when a value is set", function() { + const listener = spy((e) => { + expect(e).to.be.an.instanceof(BaseEvent) + expect(e.name).to.equal("changed") + expect(e.property).to.equal("xzoom") + expect(e.newValue).to.equal(300) + expect(e.byUser).to.be.true + }) + Settings.on("changed", listener) + Settings.set("xzoom", 300, true) + expect(listener).to.have.been.called.once + Settings.off("changed", listener) + }) + + it("requires a helper to set default values", function() { + spy.on(Settings, "set") + expect(() => Settings.initialize({})).to.throw() + expect(() => Settings.initialize({ helper: globalThis.Helper })).to.not.throw() + expect(Settings.set).to.have.been.called.exactly(13) + expect(Settings.set).to.not.have.been.called.with("saveFilename") + expect(Settings.set).to.have.been.called.with("xzoom") + expect(Settings.set).to.have.been.called.with("yzoom") + expect(Settings.set).to.have.been.called.with("xmin") + expect(Settings.set).to.have.been.called.with("ymax") + expect(Settings.set).to.have.been.called.with("xaxisstep") + expect(Settings.set).to.have.been.called.with("yaxisstep") + expect(Settings.set).to.have.been.called.with("xlabel") + expect(Settings.set).to.have.been.called.with("ylabel") + expect(Settings.set).to.have.been.called.with("linewidth") + expect(Settings.set).to.have.been.called.with("textsize") + expect(Settings.set).to.have.been.called.with("logscalex") + expect(Settings.set).to.have.been.called.with("showxgrad") + expect(Settings.set).to.have.been.called.with("showygrad") + }) +}) \ No newline at end of file diff --git a/linux/application-x-logarithm-plot.svg b/linux/application-x-logarithm-plot.svg deleted file mode 100644 index 580277f..0000000 --- a/linux/application-x-logarithm-plot.svg +++ /dev/null @@ -1,177 +0,0 @@ - - - LogarithmPlotter Icon - - - - - - - - - - - - - - - - - - - image/svg+xml - - LogarithmPlotter File Icon - 2021 - - - Ad5001 - - - - - (c) Copyright Ad5001 2021 - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/linux/debian/depends b/linux/debian/depends deleted file mode 100644 index fb79d69..0000000 --- a/linux/debian/depends +++ /dev/null @@ -1 +0,0 @@ -python3, python3-pip, python3-pyside6-essentials (>= 6.4.0), texlive-latex-base, dvipng diff --git a/linux/install_local.sh b/linux/install_local.sh deleted file mode 100644 index 714cc0b..0000000 --- a/linux/install_local.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -# -# AccountFree - Browse and use online services, free of account. -# 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 . -# -# This script installs the desktop file & mime type for development environment, linking it directly to run.py. - -APPROOT="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -echo "Adding desktop file..." -sed "s+ROOTFOLDER+$APPROOT/+g" "$APPROOT/linux/logplotter.desktop" > "$APPROOT/linux/logarithmplotter-local.desktop" -xdg-desktop-menu install "$APPROOT/linux/logarithmplotter-local.desktop" -echo "Installing mime-type..." -xdg-mime install "$APPROOT/linux/x-logarithm-plot.xml" -echo "Installing icons..." -mkdir -p ~/.local/share/icons/hicolor/scalable/mimetypes -cp "$APPROOT/linux/application-x-logarithm-plot.svg" ~/.local/share/icons/hicolor/scalable/mimetypes/application-x-logarithm-plot.svg -mkdir -p ~/.local/share/icons/hicolor/scalable/apps -cp "$APPROOT/logplotter.svg" ~/.local/share/icons/hicolor/scalable/apps/logarithmplotter.svg -# xdg-icon-resource does not work with SVG yet. See https://bugs.launchpad.net/ubuntu/+source/xdg-utils/+bug/790449. -#xdg-icon-resource install --context mimetypes --novendor "$APPROOT/linux/application-x-logarithm-plot.svg" "application-x-logarithm-plot" -#xdg-icon-resource install --context apps --novendor "$APPROOT/logplotter.svg" "logarithmplotter" -update-mime-database ~/.local/share/mime/ -update-icon-caches ~/.local/share/icons/hicolor diff --git a/linux/logplotter.desktop b/linux/logplotter.desktop deleted file mode 100644 index 1c46f8f..0000000 --- a/linux/logplotter.desktop +++ /dev/null @@ -1,20 +0,0 @@ -[Desktop Entry] -Version=1.0 -Type=Application -Name=LogarithmPlotter -GenericName=2D plotter software -GenericName[fr]=Logiciel de traçage 2D -GenericName[de]=2D-Grafiksoftware -GenericName[no]=2D-plotterprogramvare -GenericName[hu]=Síkbeli ábrázolásszoftver -Comment=Create BODE diagrams, sequences and distribution functions -Comment[fr]=Créer des diagrammes de BODE, des suites et des fonctions de répartition -Comment[de]=Erstellung von Bode-Diagramms, Folgen und Verteilungsfunktionen -Comment[hu]=Bode-ábrák, sorozatok és újraosztási függvények létrehozása - -Exec=/usr/bin/python3 ROOTFOLDER/run.py %f -Icon=logarithmplotter -MimeType=application/x-logarithm-plot; -Terminal=false -StartupNotify=false -Categories=Science diff --git a/logplotter.svg b/logplotter.svg deleted file mode 100644 index 77a2817..0000000 --- a/logplotter.svg +++ /dev/null @@ -1,64 +0,0 @@ - -LogarithmPlotter Icon v1.0image/svg+xmlLogarithmPlotter Icon v1.02021Ad5001(c) Ad5001 2021 - All rights reserved diff --git a/run.py b/run.py index 1481dba..058ca94 100644 --- a/run.py +++ b/run.py @@ -1,6 +1,6 @@ """ * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,21 +15,24 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . """ -def update_translations(): +from os import system, getcwd, path +from sys import path as sys_path, argv + +def build(): """ Updates all binary translations """ - from os import system, getcwd, chdir, path - pwd = getcwd() - chdir(path.join("LogarithmPlotter", "i18n")) - system("./release.sh") - chdir(pwd) + system("./scripts/build.sh") def run(): - update_translations() from LogarithmPlotter import logarithmplotter logarithmplotter.run() if __name__ == "__main__": + if '--test-build' not in argv: + build() + logplotter_path = path.realpath(path.join(getcwd(), "build", "runtime-pyside6")) + print(f"Appending {logplotter_path} to path...") + sys_path.append(logplotter_path) run() diff --git a/LogarithmPlotter/__init__.py b/runtime-pyside6/LogarithmPlotter/__init__.py similarity index 86% rename from LogarithmPlotter/__init__.py rename to runtime-pyside6/LogarithmPlotter/__init__.py index 4b060a5..08914d3 100644 --- a/LogarithmPlotter/__init__.py +++ b/runtime-pyside6/LogarithmPlotter/__init__.py @@ -1,6 +1,6 @@ """ * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,9 +17,8 @@ """ from shutil import which -__VERSION__ = "0.5.0" -is_release = True - +__VERSION__ = "0.6.0" +is_release = False # Check if development version, if so get the date of the latest git patch # and append it to the version string. @@ -27,9 +26,10 @@ if not is_release and which('git') is not None: from os.path import realpath, join, dirname, exists from subprocess import check_output from datetime import datetime + # Command to check date of latest git commit cmd = ['git', 'log', '--format=%ci', '-n 1'] - cwd = realpath(join(dirname(__file__), '..')) # Root AccountFree directory. + cwd = realpath(join(dirname(__file__), '..', '..', '..')) # Root LogarithmPlotter directory. if exists(join(cwd, '.git')): date_str = check_output(cmd, cwd=cwd).decode('utf-8').split(' ')[0] try: @@ -39,6 +39,3 @@ if not is_release and which('git') is not None: # Date cannot be parsed, not git root? pass -if __name__ == "__main__": - from .logarithmplotter import run - run() diff --git a/runtime-pyside6/LogarithmPlotter/logarithmplotter.py b/runtime-pyside6/LogarithmPlotter/logarithmplotter.py new file mode 100644 index 0000000..562231a --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/logarithmplotter.py @@ -0,0 +1,215 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +from os import getcwd, chdir, environ, path +from platform import system as os_name, release as OS_RELEASE +from sys import path as sys_path +from sys import argv, exit +from tempfile import TemporaryDirectory +from math import ceil +from time import time + +from PySide6.QtCore import QTranslator, QLocale, QThreadPool, QThread +from PySide6.QtGui import QIcon +from PySide6.QtQml import QQmlApplicationEngine +from PySide6.QtQuickControls2 import QQuickStyle +from PySide6.QtWidgets import QApplication + +start_time = time() + +# Create the temporary directory for saving copied screenshots and latex files +tempdir = TemporaryDirectory() +tmpfile = path.join(tempdir.name, 'graph.png') +pwd = getcwd() + +logarithmplotter_path = path.dirname(path.realpath(__file__)) +chdir(logarithmplotter_path) + +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, debug +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 + +OS_NAME = os_name() + + +CACHE_PATH = { + "Linux": path.join(environ["XDG_CONFIG_HOME"], "LogarithmPlotter") + if "XDG_CONFIG_HOME" in environ else + path.join(path.expanduser("~"), ".cache", "LogarithmPlotter"), + "Windows": path.join(path.expandvars('%APPDATA%'), "LogarithmPlotter", "cache"), + "Darwin": path.join(path.expanduser("~"), "Library", "Caches", "LogarithmPlotter"), +}[OS_NAME] + + +LINUX_THEMES = { # See https://specifications.freedesktop.org/menu-spec/latest/onlyshowin-registry.html + "COSMIC": "Basic", + "GNOME": "Basic", + "GNOME-Classic": "Basic", + "GNOME-Flashback": "Basic", + "KDE": "Fusion", + "LXDE": "Basic", + "LXQt": "Fusion", + "MATE": "Fusion", + "TDE": "Fusion", + "Unity": "Basic", + "XFCE": "Basic", + "Cinnamon": "Fusion", + "Pantheon": "Basic", + "DDE": "Basic", + "EDE": "Fusion", + "Endless": "Basic", + "Old": "Fusion", +} + + +def get_linux_theme() -> str: + if "XDG_SESSION_DESKTOP" in environ: + if environ["XDG_SESSION_DESKTOP"] in LINUX_THEMES: + return LINUX_THEMES[environ["XDG_SESSION_DESKTOP"]] + return "Fusion" + else: + # Android + return "Material" + + +def get_platform_qt_style(os) -> str: + return { + "Linux": get_linux_theme(), + "Windows": "Universal" if OS_RELEASE() in ["10", "11", "12", "13", "14"] else "Windows", + "Darwin": "macOS", + "Android": "Material" + }[os] + + +def register_icon_directories() -> None: + icon_fallbacks = QIcon.fallbackSearchPaths() + base_icon_path = path.join(logarithmplotter_path, "qml", "eu", "ad5001", "LogarithmPlotter", "icons") + paths = [["common"], ["objects"], ["history"], ["settings"], ["properties"]] + for p in paths: + icon_fallbacks.append(path.realpath(path.join(base_icon_path, *p))) + QIcon.setFallbackSearchPaths(icon_fallbacks) + + +def create_qapp() -> QApplication: + app = QApplication(argv) + app.setApplicationName("LogarithmPlotter") + app.setApplicationDisplayName("LogarithmPlotter") + app.setApplicationVersion(f"v{__VERSION__}") + app.setDesktopFileName("eu.ad5001.LogarithmPlotter") + app.setOrganizationName("Ad5001") + app.styleHints().setShowShortcutsInContextMenus(True) + app.setWindowIcon(QIcon(path.realpath(path.join(logarithmplotter_path, "logarithmplotter.svg")))) + return app + + +def install_translation(app: QApplication) -> QTranslator: + # Installing translators + translator = QTranslator() + # Check if lang is forced. + forcedlang = [p for p in argv if p[:7] == "--lang="] + i18n_path = path.realpath(path.join(logarithmplotter_path, "i18n")) + locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale() + if not translator.load(locale, "lp", "_", i18n_path): + # Load default translation + print("Loading default language en...") + translator.load(QLocale("en"), "lp", "_", i18n_path) + app.installTranslator(translator) + return translator + + +def create_engine(helper: Helper, latex: Latex, dep_time: float) -> tuple[QQmlApplicationEngine, PyJSValue]: + global tmpfile + engine = QQmlApplicationEngine() + js_globals = PyJSValue(engine.globalObject()) + js_globals.globalThis = engine.globalObject() + js_globals.Helper = engine.newQObject(helper) + js_globals.Latex = engine.newQObject(latex) + engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv) + engine.rootContext().setContextProperty("StartTime", dep_time) + + qml_path = path.realpath(path.join(logarithmplotter_path, "qml")) + engine.addImportPath(qml_path) + engine.load(path.join(qml_path, "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")) + + return engine, js_globals + + +def run(): + config.init() + + if not 'QT_QUICK_CONTROLS_STYLE' in environ: + QQuickStyle.setStyle(get_platform_qt_style(OS_NAME)) + + dep_time = time() + print("Loaded dependencies in " + str((dep_time - start_time) * 1000) + "ms.") + + # Maxing thread count to half the computer's thread count to avoid maxing CPU + # with too many threads (and also leaving some for rendering). + QThreadPool.globalInstance().setMaxThreadCount(int(ceil(QThread.idealThreadCount() / 2))) + + register_icon_directories() + app = create_qapp() + translator = install_translation(app) + debug.setup() + + # Installing macOS file handler. + macos_file_open_handler = None + if OS_NAME == "Darwin": + macos_file_open_handler = native.MacOSFileOpenHandler() + app.installEventFilter(macos_file_open_handler) + + helper = Helper(pwd, tmpfile) + latex = Latex(CACHE_PATH) + engine, js_globals = create_engine(helper, latex, dep_time) + + if len(engine.rootObjects()) == 0: # No root objects loaded + print("No root object", path.realpath(path.join(getcwd(), "qml"))) + exit(-1) + + # 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]) + chdir(path.dirname(path.realpath(__file__))) + + if OS_NAME == "Darwin": + macos_file_open_handler.init_io(js_globals.Modules.IO) + + # Check for LaTeX installation if LaTeX support is enabled + if config.getSetting("enable_latex"): + latex.checkLatexInstallation() + + # Check for updates + if config.getSetting("check_for_updates"): + check_for_updates(__VERSION__, engine.rootObjects()[0]) + + exit_code = app.exec() + + tempdir.cleanup() + config.save() + exit(exit_code) + + +if __name__ == "__main__": + run() diff --git a/LogarithmPlotter/logarithmplotter.svg b/runtime-pyside6/LogarithmPlotter/logarithmplotter.svg similarity index 100% rename from LogarithmPlotter/logarithmplotter.svg rename to runtime-pyside6/LogarithmPlotter/logarithmplotter.svg diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml similarity index 51% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml index 7a33bce..d271cb6 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/AppMenuBar.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -20,9 +20,7 @@ import QtQuick import Qt.labs.platform as Native //import QtQuick.Controls 2.15 import eu.ad5001.MixedMenu 1.1 -import "js/objects.js" as Objects -import "js/historylib.js" as HistoryLib -import "js/math/latex.js" as LatexJS +import eu.ad5001.LogarithmPlotter.Common /*! @@ -35,7 +33,6 @@ import "js/math/latex.js" as LatexJS \sa LogarithmPlotter */ MenuBar { - property var settingsMenu: settingsSubMenu Menu { title: qsTr("&File") @@ -44,6 +41,7 @@ MenuBar { shortcut: StandardKey.Open onTriggered: settings.load() icon.name: 'document-open' + icon.color: sysPalette.windowText } Action { @@ -51,13 +49,14 @@ MenuBar { shortcut: StandardKey.Save onTriggered: settings.save() icon.name: 'document-save' + icon.color: sysPalette.windowText } Action { text: qsTr("Save &As...") shortcut: StandardKey.SaveAs onTriggered: settings.saveAs() - icon.name: 'document-save-as' - + icon.color: sysPalette.windowText + icon.name: 'document-save-as' } MenuSeparator { } Action { @@ -71,6 +70,7 @@ MenuBar { } icon.name: 'application-exit' + icon.color: sysPalette.windowText } } @@ -79,25 +79,31 @@ MenuBar { Action { text: qsTr("&Undo") shortcut: StandardKey.Undo - onTriggered: history.undo() + onTriggered: Modules.History.undo() icon.name: 'edit-undo' icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText - enabled: history.undoCount > 0 } Action { text: qsTr("&Redo") shortcut: StandardKey.Redo - onTriggered: history.redo() + onTriggered: Modules.History.redo() icon.name: 'edit-redo' 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' + icon.color: sysPalette.windowText + } + MenuSeparator { } + Action { + text: qsTr("&Preferences") + shortcut: StandardKey.Copy + onTriggered: preferences.open() + icon.name: 'settings' + icon.color: sysPalette.windowText } } @@ -105,160 +111,68 @@ MenuBar { title: qsTr("&Create") // Services repeater Repeater { - model: Object.keys(Objects.types) + model: Object.keys(Modules.Objects.types) MenuItem { - text: Objects.types[modelData].displayType() - visible: Objects.types[modelData].createable() + text: Modules.Objects.types[modelData].displayType() + visible: Modules.Objects.types[modelData].createable() height: visible ? implicitHeight : 0 icon.name: modelData icon.source: './icons/objects/' + modelData + '.svg' icon.color: sysPalette.buttonText onTriggered: { - var newObj = Objects.createNewRegisteredObject(modelData) - history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export())) + var newObj = Modules.Objects.createNewRegisteredObject(modelData) + Modules.History.addToHistory(new JS.HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export())) objectLists.update() } } } } - Menu { - id: settingsSubMenu - title: qsTr("&Settings") - Action { - id: checkForUpdatesMenuSetting - text: qsTr("Check for updates on startup") - checkable: true - checked: Helper.getSettingBool("check_for_updates") - onTriggered: Helper.setSettingBool("check_for_updates", checked) - icon.name: 'update' - icon.color: sysPalette.buttonText - } - - Action { - id: resetRedoStackMenuSetting - text: qsTr("Reset redo stack automaticly") - checkable: true - checked: Helper.getSettingBool("reset_redo_stack") - onTriggered: Helper.setSettingBool("reset_redo_stack", checked) - icon.name: 'timeline' - icon.color: sysPalette.buttonText - } - - Action { - id: enableLatexJSSetting - text: qsTr("Enable LaTeX rendering") - checkable: true - checked: Helper.getSettingBool("enable_latex") - onTriggered: { - Helper.setSettingBool("enable_latex", checked) - LatexJS.enabled = checked - drawCanvas.requestPaint() - } - icon.name: 'Expression' - icon.color: sysPalette.buttonText - } - - Menu { - title: qsTr("Expression editor") - - Action { - id: autocloseFormulaSetting - text: qsTr("Automatically close parenthesises and brackets") - checkable: true - checked: Helper.getSettingBool("expression_editor.autoclose") - onTriggered: { - Helper.setSettingBool("expression_editor.autoclose", checked) - } - icon.name: 'Text' - icon.color: sysPalette.buttonText - } - - Action { - id: colorizeFormulaSetting - text: qsTr("Enable syntax highlighting") - checkable: true - checked: Helper.getSettingBool("expression_editor.colorize") - onTriggered: { - Helper.setSettingBool("expression_editor.colorize", checked) - } - icon.name: 'appearance' - icon.color: sysPalette.buttonText - } - - Action { - id: autocompleteFormulaSetting - text: qsTr("Enable autocompletion") - checkable: true - checked: Helper.getSettingBool("autocompletion.enabled") - onTriggered: { - Helper.setSettingBool("autocompletion.enabled", checked) - } - icon.name: 'label' - icon.color: sysPalette.buttonText - } - - Menu { - id: colorSchemeSetting - title: qsTr("Color Scheme") - property var schemes: ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"] - - Repeater { - model: colorSchemeSetting.schemes - - MenuItem { - text: modelData - checkable: true - checked: Helper.getSettingInt("expression_editor.color_scheme") == index - onTriggered: { - parent.children[Helper.getSettingInt("expression_editor.color_scheme")].checked = false - checked = true - Helper.setSettingInt("expression_editor.color_scheme", index) - } - } - } - } - } - } - Menu { title: qsTr("&Help") Action { text: qsTr("&Source code") icon.name: 'software-sources' + icon.color: sysPalette.windowText onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter") } Action { text: qsTr("&Report a bug") icon.name: 'tools-report-bug' + icon.color: sysPalette.windowText onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues") } Action { text: qsTr("&User manual") icon.name: 'documentation' + icon.color: sysPalette.windowText onTriggered: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar") } Action { text: qsTr("&Changelog") icon.name: 'state-information' + icon.color: sysPalette.windowText onTriggered: changelog.open() } Action { text: qsTr("&Help translating!") - icon.name: 'translator' + icon.name: 'translate' + icon.color: sysPalette.windowText onTriggered: Qt.openUrlExternally("https://hosted.weblate.org/engage/logarithmplotter/") } MenuSeparator { } Action { text: qsTr("&Thanks") - icon.name: 'about' + icon.name: 'help-about' + icon.color: sysPalette.windowText onTriggered: thanksTo.open() } Action { text: qsTr("&About") shortcut: StandardKey.HelpContents - icon.name: 'about' + icon.name: 'help-about' + icon.color: sysPalette.windowText onTriggered: about.open() } } diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Common/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Common/qmldir new file mode 100644 index 0000000..af9eb16 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Common/qmldir @@ -0,0 +1,3 @@ +module eu.ad5001.LogarithmPlotter.Common + +JS 1.0 index.mjs diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/HistoryBrowser.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/Browser.qml similarity index 66% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/HistoryBrowser.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/Browser.qml index 69ddfdb..09b6feb 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/HistoryBrowser.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/Browser.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,15 @@ * along with this program. If not, see . */ +pragma ComponentBehavior: Bound + import QtQuick.Controls import QtQuick import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting -import "../js/utils.js" as Utils /*! - \qmltype HistoryBrowser + \qmltype Browser \inqmlmodule eu.ad5001.LogarithmPlotter.History \brief Tab of the drawer that allows to navigate through the undo and redo history. @@ -47,12 +48,25 @@ Item { true when the system is running with a dark theme, false otherwise. */ property bool darkTheme: isDarkTheme() + + /*! + \qmlproperty int HistoryBrowser::undoCount + Number of actions in the undo stack. + */ + property int undoCount: 0 + + /*! + \qmlproperty int HistoryBrowser::redoCount + Number of actions in the redo stack. + */ + property int redoCount: 0 Setting.TextSetting { id: filterInput anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top + anchors.rightMargin: 5 placeholderText: qsTr("Filter...") category: "all" } @@ -76,19 +90,22 @@ Item { id: redoColumn anchors.right: parent.right anchors.top: parent.top - width: actionWidth + width: historyBrowser.actionWidth Repeater { - model: history.redoCount + model: historyBrowser.redoCount - HistoryItem { + SingleItem { id: redoButton - width: actionWidth + width: historyBrowser.actionWidth //height: actionHeight isRedo: true - idx: index darkTheme: historyBrowser.darkTheme hidden: !(filterInput.value == "" || content.includes(filterInput.value)) + onClicked: { + redoTimer.toRedoCount = Modules.History.redoStack.length-index + redoTimer.start() + } } } } @@ -101,14 +118,14 @@ Item { transform: Rotation { origin.x: 30; origin.y: 30; angle: 270} height: 70 width: 20 - visible: history.redoCount > 0 + visible: historyBrowser.redoCount > 0 } Rectangle { id: nowRect anchors.right: parent.right anchors.top: redoColumn.bottom - width: actionWidth + width: historyBrowser.actionWidth height: 40 color: sysPalette.highlight Text { @@ -124,20 +141,24 @@ Item { id: undoColumn anchors.right: parent.right anchors.top: nowRect.bottom - width: actionWidth + width: historyBrowser.actionWidth Repeater { - model: history.undoCount + model: historyBrowser.undoCount - HistoryItem { + SingleItem { id: undoButton - width: actionWidth + width: historyBrowser.actionWidth //height: actionHeight isRedo: false - idx: index darkTheme: historyBrowser.darkTheme hidden: !(filterInput.value == "" || content.includes(filterInput.value)) + + onClicked: { + undoTimer.toUndoCount = +index+1 + undoTimer.start() + } } } } @@ -150,7 +171,39 @@ Item { transform: Rotation { origin.x: 30; origin.y: 30; angle: 270} height: 60 width: 20 - visible: history.undoCount > 0 + visible: historyBrowser.undoCount > 0 + } + } + } + + Timer { + id: undoTimer + interval: 5; running: false; repeat: true + property int toUndoCount: 0 + onTriggered: { + if(toUndoCount > 0) { + Modules.History.undo() + if(toUndoCount % 3 === 1) + Modules.Canvas.requestPaint() + toUndoCount--; + } else { + running = false; + } + } + } + + Timer { + id: redoTimer + interval: 5; running: false; repeat: true + property int toRedoCount: 0 + onTriggered: { + if(toRedoCount > 0) { + Modules.History.redo() + if(toRedoCount % 3 === 1) + Modules.Canvas.requestPaint() + toRedoCount--; + } else { + running = false; } } } @@ -163,6 +216,18 @@ Item { let hex = sysPalette.windowText.toString() // We only check the first parameter, as on all normal OSes, text color is grayscale. return parseInt(hex.substr(1,2), 16) > 128 - + } + + Component.onCompleted: { + Modules.History.initialize({ + helper: Helper, + themeTextColor: sysPalette.windowText.toString(), + imageDepth: Screen.devicePixelRatio, + fontSize: 14 + }) + Modules.History.on("cleared loaded added undone redone", () => { + undoCount = Modules.History.undoStack.length + redoCount = Modules.History.redoStack.length + }) } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/HistoryItem.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/SingleItem.qml similarity index 79% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/HistoryItem.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/SingleItem.qml index 041ddfa..fac19e3 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/HistoryItem.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/SingleItem.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,13 @@ * along with this program. If not, see . */ -import QtQuick.Controls import QtQuick -import Qt5Compat.GraphicalEffects -import "../js/utils.js" as Utils +import QtQuick.Controls import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting /*! - \qmltype HistoryItem + \qmltype SingleItem \inqmlmodule eu.ad5001.LogarithmPlotter.History \brief Item representing an history action. @@ -42,17 +40,17 @@ Button { \qmlproperty bool HistoryItem::isRedo true if the action is in the redo stack, false othewise. */ - property bool isRedo + required property bool isRedo /*! - \qmlproperty int HistoryItem::idx + \qmlproperty int HistoryItem::index Index of the item within the HistoryBrowser list. */ - property int idx + required property int index /*! \qmlproperty bool HistoryItem::darkTheme true when the system is running with a dark theme, false otherwise. */ - property bool darkTheme + required property bool darkTheme /*! \qmlproperty bool HistoryItem::hidden true when the item is filtered out, false otherwise. @@ -62,7 +60,7 @@ Button { \qmlproperty int HistoryItem::historyAction Associated history action. */ - readonly property var historyAction: isRedo ? history.redoStack[idx] : history.undoStack[history.undoCount-idx-1] + readonly property var historyAction: isRedo ? Modules.History.redoStack.at(index) : Modules.History.undoStack.at(-index-1) /*! \qmlproperty int HistoryItem::actionHeight @@ -83,12 +81,11 @@ Button { height: hidden ? 8 : Math.max(actionHeight, label.height + 15) - LinearGradient { + Rectangle { anchors.fill: parent //opacity: hidden ? 0.6 : 1 - start: Qt.point(0, 0) - end: Qt.point(parent.width, 0) gradient: Gradient { + orientation: Gradient.Horizontal GradientStop { position: 0.1; color: "transparent" } GradientStop { position: 1.5; color: clr } } @@ -116,10 +113,23 @@ Button { anchors.verticalCenter: parent.verticalCenter visible: !hidden font.pixelSize: 14 - text: historyAction.getHTMLString().replace(/\$\{tag_color\}/g, clr) + text: "" textFormat: Text.RichText clip: true wrapMode: Text.WordWrap + + Component.onCompleted: function() { + // Render HTML, might be string, but could also be a promise + const html = historyAction.getHTMLString() + if(typeof html === "string") { + label.text = html.replace(/\$\{tag_color\}/g, clr) + } else { + // Promise! We need to way to wait for it to be completed. + html.then(rendered => { + label.text = rendered.replace(/\$\{tag_color\}/g, clr) + }) + } + } } Rectangle { @@ -135,13 +145,6 @@ Button { ToolTip.visible: hovered ToolTip.delay: 200 ToolTip.text: content - - onClicked: { - if(isRedo) - history.redoMultipleDefered(history.redoCount-idx) - else - history.undoMultipleDefered(+idx+1) - } } diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/qmldir new file mode 100644 index 0000000..66c4408 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/History/qmldir @@ -0,0 +1,4 @@ +module eu.ad5001.LogarithmPlotter.History + +Browser 1.0 Browser.qml +SingleItem 1.0 SingleItem.qml diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml new file mode 100644 index 0000000..30bf6ca --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogGraphCanvas.qml @@ -0,0 +1,86 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick +import Qt.labs.platform as Native + +/*! + \qmltype LogGraphCanvas + \inqmlmodule eu.ad5001.LogarithmPlotter + \brief Canvas used to display the diagram. + + Provides a customized canvas with several helper methods to be used by objects. + + \sa LogarithmPlotter, PickLocationOverlay +*/ +Canvas { + id: canvas + anchors.top: parent.top + anchors.left: parent.left + height: parent.height - 90 + width: parent.width + /*! + \qmlproperty var LogGraphCanvas::imageLoaders + Dictionary of format {image: callback} containing data for deferred image loading. + */ + property var imageLoaders: {} + + Component.onCompleted: { + imageLoaders = {} + Modules.Canvas.initialize({ canvas, drawingErrorDialog }) + } + + Native.MessageDialog { + id: drawingErrorDialog + title: qsTranslate("expression", "LogarithmPlotter - Drawing error") + text: "" + function show(objType, objName, error) { + text = qsTranslate("error", "Error while attempting to draw %1 %2:\n%3\n\nUndoing last change.").arg(objType).arg(objName).arg(error) + open() + } + } + + onPaint: function(rect) { + //console.log('Redrawing') + if(rect.width == canvas.width) { // Redraw full canvas + Modules.Canvas.redraw() + } + } + + onImageLoaded: { + Object.keys(imageLoaders).forEach((key) => { + if(isImageLoaded(key)) { + // Calling callback + imageLoaders[key]() + delete imageLoaders[key] + } + }) + } + + /*! + \qmlmethod void LogGraphCanvas::loadImageAsync(string imageSource) + Loads an image data onto the canvas asynchronously. + Returns a Promise that is resolved when the image is loaded. + */ + function loadImageAsync(imageSource) { + return new Promise((resolve) => { + this.loadImage(imageSource) + this.imageLoaders[imageSource] = resolve + }) + } +} diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml new file mode 100644 index 0000000..ddbd2bd --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/LogarithmPlotter.qml @@ -0,0 +1,272 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts 1.12 +import eu.ad5001.MixedMenu 1.1 + +// Auto loading all modules. +import eu.ad5001.LogarithmPlotter.Common + +import eu.ad5001.LogarithmPlotter.History 1.0 as History +import eu.ad5001.LogarithmPlotter.ObjectLists 1.0 +import eu.ad5001.LogarithmPlotter.Overlay 1.0 as Overlay +import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup + +/*! + \qmltype LogarithmPlotter + \inqmlmodule eu.ad5001.LogarithmPlotter + \brief Main window of LogarithmPlotter + + \sa AppMenuBar, History, GreetScreen, Changelog, Alert, ObjectLists, Settings, HistoryBrowser, LogGraphCanvas, PickLocationOverlay. +*/ +ApplicationWindow { + id: root + visible: true + width: 1000 + height: 500 + color: sysPalette.window + title: qsTr("untitled") + + SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active } + SystemPalette { id: sysPaletteIn; colorGroup: SystemPalette.Disabled } + + menuBar: appMenu.trueItem + + AppMenuBar {id: appMenu} + + 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 + anchors.bottomMargin: 5 + z: 3 + } + + Item { + id: sidebar + width: 300 + height: parent.height + //y: root.menuBar.height + readonly property bool inPortrait: root.width < root.height + /*modal: true// inPortrait + interactive: inPortrait + position: inPortrait ? 0 : 1 + */ + visible: !inPortrait + + + TabBar { + id: sidebarSelector + width: parent.width + anchors.top: parent.top + TabButton { + text: qsTr("Objects") + icon.name: 'polygon-add-nodes' + icon.color: sysPalette.windowText + //height: 24 + } + TabButton { + text: qsTr("Settings") + icon.name: 'preferences-system-symbolic' + icon.color: sysPalette.windowText + //height: 24 + } + TabButton { + text: qsTr("History") + icon.name: 'view-history' + icon.color: sysPalette.windowText + //height: 24 + } + } + + StackLayout { + id: sidebarContents + anchors.top: sidebarSelector.bottom + anchors.left: parent.left + anchors.topMargin: 5 + anchors.leftMargin: 5 + anchors.bottom: parent.bottom + //anchors.bottomMargin: sidebarSelector.height + width: parent.width - 5 + currentIndex: sidebarSelector.currentIndex + z: -1 + clip: true + + ObjectLists { + id: objectLists + onChanged: Modules.Canvas.requestPaint() + } + + Settings { + id: settings + canvas: drawCanvas + onChanged: Modules.Canvas.requestPaint() + } + + History.Browser { + id: historyBrowser + } + } + } + + LogGraphCanvas { + id: drawCanvas + anchors.top: parent.top + anchors.left: sidebar.inPortrait ? parent.left : sidebar.right + height: parent.height + width: sidebar.inPortrait ? parent.width : parent.width - sidebar.width//*sidebar.position + x: sidebar.width//*sidebar.position + + property bool firstDrawDone: false + + onPainted: if(!firstDrawDone) { + firstDrawDone = true; + console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms") + if(TestBuild == true) { + console.log("Plot drawn in canvas, terminating test of build in 100ms.") + testBuildTimer.start() + } + } + + Overlay.ViewPositionChange { + id: viewPositionChanger + anchors.fill: parent + } + + Overlay.PickLocation { + id: positionPicker + anchors.fill: parent + } + } + + Overlay.Loading { + id: loadingOverlay + anchors.fill: parent + } + + Timer { + id: delayRefreshTimer + repeat: false + interval: 1 + onTriggered: sidebarSelector.currentIndex = 0 + } + + Timer { + id: testBuildTimer + repeat: false + interval: 100 + onTriggered: Qt.quit() // Quit after paint on test build + } + + onClosing: function(close) { + if(!Modules.IO.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() + } + } + + /*! + \qmlmethod void LogarithmPlotter::copyDiagramToClipboard() + Copies the current diagram image to the clipboard. + */ + function copyDiagramToClipboard() { + var file = Helper.gettmpfile() + drawCanvas.save(file) + Helper.copyImageToClipboard() + alert.show(qsTr("Copied plot screenshot to clipboard!")) + } + + /*! + \qmlmethod void LogarithmPlotter::showAlert(string alertText) + Shows an alert on the diagram. + */ + function showAlert(alertText) { + // This function is called from the backend and is used to show alerts from there. + alert.show(alertText) + } + + + Menu { + id: updateMenu + title: qsTr("&Update") + Action { + text: qsTr("&Update LogarithmPlotter") + icon.name: 'update' + onTriggered: Qt.openUrlExternally("https://apps.ad5001.eu/logarithmplotter/") + } + } + + /*! + \qmlmethod void LogarithmPlotter::showUpdateMenu() + Shows the update menu in the AppMenuBar. + */ + function showUpdateMenu() { + appMenu.addMenu(updateMenu) + } + + // Initializing modules + Component.onCompleted: { + Modules.IO.initialize({ root, settings, alert }) + Modules.Latex.initialize({ latex: Latex, helper: Helper }) + Modules.Settings.on("changed", (evt) => { + if(evt.property === "saveFilename") { + const fileName = evt.newValue.split('/').pop().split('\\').pop() + if(fileName !== "") + title = fileName + } + }) + Modules.IO.on("saved loaded", (evt) => { + // Refreshing sidebar + updateObjectsLists() + if(title.endsWith("*")) + title = title.substring(0, title.length-1) + }) + Modules.IO.on("modified", () => { + if(!title.endsWith("*")) + title = title+"*" + }) + } +} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/CustomPropertyList.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/CustomPropertyList.qml similarity index 82% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/CustomPropertyList.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/CustomPropertyList.qml index 3f2f5ab..6e33b71 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/CustomPropertyList.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/CustomPropertyList.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -20,10 +20,7 @@ import QtQuick import QtQuick.Controls import Qt.labs.platform as Native import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting -import "../../js/objects.js" as Objects -import "../../js/historylib.js" as HistoryLib -import "../../js/utils.js" as Utils -import "../../js/mathlib.js" as MathLib +import eu.ad5001.LogarithmPlotter.Common /*! \qmltype CustomPropertyList @@ -50,7 +47,7 @@ Repeater { */ property var positionPicker - readonly property var textTypes: ['Domain', 'string', 'number'] + readonly property var textTypes: ['Domain', 'string', 'number', 'int'] readonly property var comboBoxTypes: ['ObjectType', 'Enum'] readonly property var listTypes: ['List', 'Dict'] @@ -77,13 +74,13 @@ Repeater { Setting.ExpressionEditor { height: 30 label: propertyLabel - icon: `settings/custom/${propertyIcon}.svg` - defValue: Utils.simplifyExpression(obj[propertyName].toEditableString()) + icon: `properties/${propertyIcon}.svg` + defValue: JS.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( + Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( obj.name, objType, propertyName, obj[propertyName], newExpr )) @@ -91,7 +88,7 @@ Repeater { root.changed() } } - } + } } @@ -102,27 +99,31 @@ Repeater { Setting.TextSetting { height: 30 label: propertyLabel - icon: `settings/custom/${propertyIcon}.svg` + icon: `properties/${propertyIcon}.svg` + min: propertyType == "int" ? 0 : -Infinity + isInt: propertyType == "int" isDouble: propertyType == "number" defValue: obj[propertyName] == null ? '' : obj[propertyName].toString() category: { return { "Domain": "domain", "string": "all", - "number": "all" + "number": "all", + "int": "all", }[propertyType] } onChanged: function(newValue) { try { var newValueParsed = { - "Domain": () => MathLib.parseDomain(newValue), + "Domain": () => JS.MathLib.parseDomain(newValue), "string": () => newValue, - "number": () => parseFloat(newValue) + "number": () => newValue, + "int": () => newValue }[propertyType]() // Ensuring old and new values are different to prevent useless adding to history. if(obj[propertyName] != newValueParsed) { - history.addToHistory(new HistoryLib.EditedProperty( + Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( obj.name, objType, propertyName, obj[propertyName], newValueParsed )) @@ -131,6 +132,7 @@ Repeater { } } catch(e) { // Error in expression or domain + console.trace() parsingErrorDialog.showDialog(propertyName, newValue, e.message) } } @@ -141,7 +143,9 @@ Repeater { 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) + text = qsTranslate("error", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3") + .arg(qsTranslate('prop', propName)) + .arg(error).arg(propValue) open() } } @@ -155,7 +159,7 @@ Repeater { CheckBox { height: 20 text: propertyLabel - //icon: `settings/custom/${propertyIcon}.svg` + //icon: `properties/${propertyIcon}.svg` checked: { //if(obj[propertyName] == null) { @@ -164,7 +168,7 @@ Repeater { return obj[propertyName] } onClicked: { - history.addToHistory(new HistoryLib.EditedProperty( + Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( obj.name, objType, propertyName, obj[propertyName], this.checked )) @@ -181,15 +185,15 @@ Repeater { Setting.ComboBoxSetting { height: 30 label: propertyLabel - icon: `settings/custom/${propertyIcon}.svg` + icon: `properties/${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 ? - Objects.getObjectsName(propertyType.objType).concat( - isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] : []) + 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 @@ -199,30 +203,32 @@ Repeater { if(selectObjMode) { // This is only done when what we're selecting are Objects. // Setting object property. - var selectedObj = Objects.currentObjectsByName[baseModel[newIndex]] + 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 = Objects.createNewRegisteredObject(propertyType.objType) - history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export())) - baseModel = Objects.getObjectsName(propertyType.objType).concat( - isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] : + selectedObj = Modules.Objects.createNewRegisteredObject(propertyType.objType) + Modules.History.addToHistory( + new JS.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(Objects.currentObjects[objType][objIndex]) - //Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.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( + Modules.History.addToHistory(new JS.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( + Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( obj.name, objType, propertyName, obj[propertyName], baseModel[newIndex] )) @@ -240,7 +246,7 @@ Repeater { Setting.ListSetting { label: propertyLabel - //icon: `settings/custom/${propertyIcon}.svg` + //icon: `properties/${propertyIcon}.svg` dictionaryMode: paramTypeIn(propertyType, ['Dict']) keyType: dictionaryMode ? propertyType.keyType : 'string' valueType: propertyType.valueType @@ -252,11 +258,10 @@ Repeater { onChanged: { var exported = exportModel() - history.addToHistory(new HistoryLib.EditedProperty( + Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( obj.name, objType, propertyName, obj[propertyName], exported )) - //Objects.currentObjects[objType][objIndex][propertyName] = exported obj[propertyName] = exported root.changed() } @@ -278,7 +283,7 @@ Repeater { property string propertyName: modelData[0] property var propertyType: modelData[1] property string propertyLabel: qsTranslate('prop',propertyName) - property string propertyIcon: Utils.camelCase2readable(propertyName) + property string propertyIcon: propertyName sourceComponent: { if(propertyName.startsWith('comment')) diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/Dialog.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/Dialog.qml similarity index 83% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/Dialog.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/Dialog.qml index 78efdae..d762da2 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/Dialog.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/Dialog.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,15 +18,10 @@ 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/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 +import eu.ad5001.LogarithmPlotter.Common /*! \qmltype Dialog @@ -54,7 +49,7 @@ Popup.BaseDialog { \qmlproperty var EditorDialog::obj Instance of the object being edited. */ - property var obj: Objects.currentObjects[objType][objIndex] + property var obj: Modules.Objects.currentObjects[objType][objIndex] /*! \qmlproperty var EditorDialog::posPicker Reference to the global PositionPicker QML object. @@ -87,7 +82,7 @@ Popup.BaseDialog { Label { id: dlgTitle verticalAlignment: TextInput.AlignVCenter - text: qsTr("Edit properties of %1 %2").arg(Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name) + text: qsTr("Edit properties of %1 %2").arg(Modules.Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name) font.pixelSize: 20 color: sysPalette.windowText } @@ -111,16 +106,16 @@ Popup.BaseDialog { width: dlgProperties.width value: objEditor.obj.name onChanged: function(newValue) { - let newName = Utils.parseName(newValue) + let newName = JS.Utils.parseName(newValue) if(newName != '' && objEditor.obj.name != newName) { - if(newName in Objects.currentObjectsByName) { + if(newName in Modules.Objects.currentObjectsByName) { invalidNameDialog.showDialog(newName) } else { - history.addToHistory(new HistoryLib.NameChanged( + Modules.History.addToHistory(new JS.HistoryLib.NameChanged( objEditor.obj.name, objEditor.objType, newName )) - Objects.renameObject(obj.name, newName) - objEditor.obj = Objects.currentObjects[objEditor.objType][objEditor.objIndex] + Modules.Objects.renameObject(obj.name, newName) + objEditor.obj = Modules.Objects.currentObjects[objEditor.objType][objEditor.objIndex] objectListList.update() } } @@ -131,13 +126,17 @@ Popup.BaseDialog { id: labelContentProperty height: 30 width: dlgProperties.width - label: qsTr("Label content") + label: qsTranslate("prop", "labelContent") 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) { + Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( + obj.name, objType, "labelContent", + objEditor.obj.labelContent, idModel[newIndex] + )) objEditor.obj.labelContent = idModel[newIndex] objEditor.obj.update() objectListList.update() @@ -165,7 +164,7 @@ Popup.BaseDialog { */ function open() { dlgCustomProperties.model = [] // Reset - let objProps = Objects.types[objEditor.objType].properties() + 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() } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/qmldir similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/qmldir rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/Editor/qmldir diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectCreationGrid.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectCreationGrid.qml similarity index 87% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectCreationGrid.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectCreationGrid.qml index 1fd7364..21559a9 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectCreationGrid.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectCreationGrid.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,9 +18,8 @@ import QtQuick import QtQuick.Controls -import "../js/objects.js" as Objects -import "../js/historylib.js" as HistoryLib import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting +import eu.ad5001.LogarithmPlotter.Common /*! @@ -44,7 +43,7 @@ Column { // Open editor objectEditor.obj = obj objectEditor.objType = obj.type - objectEditor.objIndex = Objects.currentObjects[obj.type].indexOf(obj) + objectEditor.objIndex = Modules.Objects.currentObjects[obj.type].indexOf(obj) objectEditor.open() // Disconnect potential link posPicker.picked.disconnect(openEditorDialog) @@ -61,12 +60,12 @@ Column { width: parent.width columns: 3 Repeater { - model: Object.keys(Objects.types) + model: Object.keys(Modules.Objects.types) Button { id: createBtn width: 96 - visible: Objects.types[modelData].createable() + visible: Modules.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 @@ -94,7 +93,7 @@ Column { anchors.rightMargin: 4 horizontalAlignment: Text.AlignHCenter font.pixelSize: 14 - text: Objects.types[modelData].displayType() + text: Modules.Objects.types[modelData].displayType() wrapMode: Text.WordWrap clip: true } @@ -104,8 +103,10 @@ Column { ToolTip.text: label.text onClicked: { - let newObj = Objects.createNewRegisteredObject(modelData) - history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export())) + let newObj = Modules.Objects.createNewRegisteredObject(modelData) + Modules.History.addToHistory(new JS.HistoryLib.CreateNewObject( + newObj.name, modelData, newObj.export() + )) objectLists.update() let hasXProp = newObj.constructor.properties().hasOwnProperty('x') diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml similarity index 79% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml index e7d7a6f..d37a015 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectLists.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -21,7 +21,6 @@ import QtQuick import QtQuick.Controls 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 /*! \qmltype ObjectLists @@ -47,7 +46,7 @@ ScrollView { ListView { id: objectsListView - model: Object.keys(Objects.types) + model: Object.keys(Modules.Objects.types) //width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0) implicitHeight: contentItem.childrenRect.height + footerItem.height + 10 @@ -55,9 +54,9 @@ ScrollView { id: objTypeList property string objType: objectsListView.model[index] property var editingRows: [] - model: Objects.currentObjects[objType] + model: Modules.Objects.currentObjects[objType] width: objectsListView.width - implicitHeight: contentItem.childrenRect.height + height: contentItem.childrenRect.height + (visible ? 10 : 0) visible: model != undefined && model.length > 0 interactive: false @@ -70,21 +69,23 @@ ScrollView { CheckBox { id: typeVisibilityCheckBox - checked: Objects.currentObjects[objType] != undefined ? Objects.currentObjects[objType].every(obj => obj.visible) : true + checked: Modules.Objects.currentObjects[objType] != undefined ? Modules.Objects.currentObjects[objType].every(obj => obj.visible) : true onClicked: { - for(var obj of Objects.currentObjects[objType]) obj.visible = this.checked - for(var obj of objTypeList.editingRows) obj.objVisible = this.checked + for(const obj of Modules.Objects.currentObjects[objType]) obj.visible = this.checked + for(const obj of objTypeList.editingRows) obj.objVisible = this.checked objectListList.changed() } ToolTip.visible: hovered - ToolTip.text: checked ? qsTr("Hide all %1").arg(Objects.types[objType].displayTypeMultiple()) : qsTr("Show all %1").arg(Objects.types[objType].displayTypeMultiple()) + ToolTip.text: checked ? + qsTr("Hide all %1").arg(Modules.Objects.types[objType].displayTypeMultiple()) : + qsTr("Show all %1").arg(Modules.Objects.types[objType].displayTypeMultiple()) } Label { id: typeHeaderText verticalAlignment: TextInput.AlignVCenter - text: qsTranslate("control", "%1: ").arg(Objects.types[objType].displayTypeMultiple()) + text: qsTranslate("control", "%1: ").arg(Modules.Objects.types[objType].displayTypeMultiple()) font.pixelSize: 20 } } @@ -92,11 +93,11 @@ ScrollView { delegate: ObjectRow { id: controlRow width: objTypeList.width - obj: Objects.currentObjects[objType][index] + obj: Modules.Objects.currentObjects[objType][index] posPicker: positionPicker onChanged: { - obj = Objects.currentObjects[objType][index] + obj = Modules.Objects.currentObjects[objType][index] objectListList.update() } @@ -128,7 +129,7 @@ ScrollView { function update() { objectListList.changed() for(var objType in objectListList.listViews) { - objectListList.listViews[objType].model = Objects.currentObjects[objType] + objectListList.listViews[objType].model = Modules.Objects.currentObjects[objType] } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectRow.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectRow.qml similarity index 76% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectRow.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectRow.qml index ab3f2f7..0f7003f 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectRow.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/ObjectRow.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -21,9 +21,7 @@ import QtQuick.Dialogs import QtQuick.Controls import QtQuick.Window import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting -import "../js/objects.js" as Objects -import "../js/historylib.js" as HistoryLib -import "../js/math/latex.js" as LatexJS +import eu.ad5001.LogarithmPlotter.Common /*! @@ -74,7 +72,7 @@ Item { anchors.left: parent.left anchors.leftMargin: 5 onClicked: { - history.addToHistory(new HistoryLib.EditedVisibility( + Modules.History.addToHistory(new JS.HistoryLib.EditedVisibility( obj.name, obj.type, this.checked )) obj.visible = this.checked @@ -91,27 +89,43 @@ Item { id: objDescription anchors.left: objVisibilityCheckBox.right anchors.right: deleteButton.left - height: LatexJS.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight + height: Modules.Latex.enabled ? Math.max(parent.minHeight, latexDescription.height+4) : parent.minHeight verticalAlignment: TextInput.AlignVCenter - text: LatexJS.enabled ? "" : obj.getReadableString() + text: Modules.Latex.enabled ? "" : obj.getReadableString() font.pixelSize: 14 Image { id: latexDescription anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - visible: LatexJS.enabled + 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 + source: "" + width: 0/depth + height: 0/depth + + Component.onCompleted: function() { + if(Modules.Latex.enabled) { + const args = [obj.getLatexString(), depth*(parent.font.pixelSize+2), parent.color] + const prerendered = Modules.Latex.findPrerendered(...args) + if(prerendered !== null) { + source = prerendered.source + width = prerendered.width/depth + height = prerendered.height/depth + } else + Modules.Latex.requestAsyncRender(...args).then(info => { + source = info.source + width = info.width/depth + height = info.height/depth + }) + } + } } MouseArea { anchors.fill: parent onClicked: { - objEditor.obj = Objects.currentObjects[obj.type][index] + objEditor.obj = Modules.Objects.currentObjects[obj.type][index] objEditor.objType = obj.type objEditor.objIndex = index //objEditor.editingRow = objectRow @@ -198,7 +212,7 @@ Item { selectedColor: obj.color title: qsTr("Pick new color for %1 %2").arg(obj.constructor.displayType()).arg(obj.name) onAccepted: { - history.addToHistory(new HistoryLib.ColorChanged( + Modules.History.addToHistory(new JS.HistoryLib.ColorChanged( obj.name, obj.type, obj.color, selectedColor.toString() )) obj.color = selectedColor.toString() @@ -213,10 +227,14 @@ Item { function deleteRecursively(object) { for(let toRemove of object.requiredBy) deleteRecursively(toRemove) - object.requiredBy = [] - history.addToHistory(new HistoryLib.DeleteObject( - object.name, object.type, object.export() - )) - Objects.deleteObject(object.name) + if(Modules.Objects.currentObjectsByName[object.name] !== undefined) { + // Object still exists + // Temporary fix for objects require not being propertly updated. + object.requiredBy = [] + Modules.History.addToHistory(new JS.HistoryLib.DeleteObject( + object.name, object.type, object.export() + )) + Modules.Objects.deleteObject(object.name) + } } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/qmldir similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/qmldir rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ObjectLists/qmldir diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/Loading.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/Loading.qml new file mode 100644 index 0000000..10f3665 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/Loading.qml @@ -0,0 +1,130 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick +import QtQuick.Controls + + +/*! + \qmltype Loading + \inqmlmodule eu.ad5001.LogarithmPlotter.Overlay + \brief Overlay notifiying the user when a file is loading. + + Provides an overlay over the canvas that is shown when the user loads a new file, both to lock the ViewPositionChange + overlay and inform the user of what is loading and how much remains. + + \sa Common, ViewPositionChange +*/ +Item { + id: loadingRoot + opacity: 0 + visible: opacity !== 0 + clip: true + + property int currentlyLoading: 0 + property int maxCurrentLoadingSteps: 0 + + Behavior on opacity { PropertyAnimation {} } + + Rectangle { + anchors.fill: parent + color: sysPalette.window + opacity: 0.85 + } + + Column { + spacing: 5 + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + right: parent.right + } + + Text { + id: loadingTitle + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: 20 + color: sysPalette.windowText + } + + ProgressBar { + id: progress + anchors.horizontalCenter: parent.horizontalCenter + width: 300 + from: 0 + value: loadingRoot.maxCurrentLoadingSteps - loadingRoot.currentlyLoading + to: loadingRoot.maxCurrentLoadingSteps + } + + Text { + id: lastFinishedStep + anchors.horizontalCenter: parent.horizontalCenter + color: sysPalette.windowText + } + } + + MouseArea { + id: picker + anchors.fill: parent + hoverEnabled: parent.visible + cursorShape: Qt.ArrowCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + } + + + + /*! + \qmlmethod void Loading::addedLoadingStep() + Registers one new loading step that will eventually call \c finishedLoadingStep. + */ + function addedLoadingStep() { + if(loadingRoot.maxCurrentLoadingSteps === 1) { + // Only when several ones need to be loaded. + const fileName = Modules.Settings.saveFilename.split('/').pop().split('\\').pop() + loadingTitle.text = qsTr("Loading...") + loadingRoot.opacity = 1 + } + loadingRoot.currentlyLoading++ + loadingRoot.maxCurrentLoadingSteps++ + } + + /*! + \qmlmethod void Loading::finishedLoadingStep() + Marks a loading step as finished and displays the message to the user. + */ + function finishedLoadingStep(message) { + loadingRoot.currentlyLoading-- + const current = loadingRoot.maxCurrentLoadingSteps - loadingRoot.currentlyLoading + lastFinishedStep.text = `${message} (${current}/${loadingRoot.maxCurrentLoadingSteps})` + if(loadingRoot.currentlyLoading === 0) { + loadingRoot.maxCurrentLoadingSteps = 0 + loadingRoot.opacity = 0 + } + } + + + Component.onCompleted: function() { + Modules.Latex.on("async-render-started", (e) => { + addedLoadingStep() + }) + Modules.Latex.on("async-render-finished", (e) => { + const markup = e.markup.length > 20 ? e.markup.substring(0, 15)+"..." : e.markup + finishedLoadingStep(qsTr("Finished rendering of %1").arg(markup)) + }) + } +} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/PickLocationOverlay.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/PickLocation.qml similarity index 89% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/PickLocationOverlay.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/PickLocation.qml index afc8d61..6ba8bb6 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/PickLocationOverlay.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/PickLocation.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,14 +18,12 @@ import QtQuick import QtQuick.Controls -import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting -import "js/objects.js" as Objects -import "js/mathlib.js" as MathLib -import "js/historylib.js" as HistoryLib +import eu.ad5001.LogarithmPlotter.Setting as Setting +import eu.ad5001.LogarithmPlotter.Common /*! - \qmltype PickLocationOverlay - \inqmlmodule eu.ad5001.LogarithmPlotter + \qmltype PickLocation + \inqmlmodule eu.ad5001.LogarithmPlotter.Overlay \brief Overlay used to pick a new location for an object. Provides an overlay over the canvas that can be shown when the user clicks the "Set position" button @@ -38,7 +36,7 @@ Item { id: pickerRoot visible: false clip: true - + /*! \qmlsignal PickLocationOverlay::picked(var obj) @@ -99,9 +97,9 @@ Item { readonly property bool userPickY: pickY && pickYCheckbox.checked Rectangle { + anchors.fill: parent color: sysPalette.window opacity: 0.35 - anchors.fill: parent } MouseArea { @@ -114,10 +112,10 @@ Item { 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 = Objects.currentObjectsByName[objName] + let obj = Modules.Objects.currentObjectsByName[objName] // Set values if(parent.userPickX && parent.userPickY) { - history.addToHistory(new HistoryLib.EditedPosition( + Modules.History.addToHistory(new JS.HistoryLib.EditedPosition( objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY )) obj[propertyX] = newValueX @@ -126,7 +124,7 @@ Item { objectLists.update() pickerRoot.picked(obj) } else if(parent.userPickX) { - history.addToHistory(new HistoryLib.EditedProperty( + Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( objName, objType, propertyX, obj[propertyX], newValueX )) obj[propertyX] = newValueX @@ -134,7 +132,7 @@ Item { objectLists.update() pickerRoot.picked(obj) } else if(parent.userPickY) { - history.addToHistory(new HistoryLib.EditedProperty( + Modules.History.addToHistory(new JS.HistoryLib.EditedProperty( objName, objType, propertyY, obj[propertyY], newValueY )) obj[propertyY] = newValueY @@ -263,7 +261,7 @@ Item { color: 'black' anchors.top: parent.top anchors.left: parent.left - anchors.leftMargin: canvas.x2px(picked.mouseX) + anchors.leftMargin: Modules.Canvas.x2px(picked.mouseX) visible: parent.userPickX } @@ -274,7 +272,7 @@ Item { color: 'black' anchors.top: parent.top anchors.left: parent.left - anchors.topMargin: canvas.y2px(picked.mouseY) + anchors.topMargin: Modules.Canvas.y2px(picked.mouseY) visible: parent.userPickY } @@ -283,25 +281,26 @@ Item { x: picker.mouseX - width - 5 y: picker.mouseY - height - 5 color: 'black' - property double axisX: canvas.xaxisstep1 property double mouseX: { - let xpos = canvas.px2x(picker.mouseX) + const axisX = Modules.Canvas.axesSteps.x.value + const xpos = Modules.Canvas.px2x(picker.mouseX) if(snapToGridCheckbox.checked) { - if(canvas.logscalex) { + if(Modules.Settings.logscalex) { // Calculate the logged power let pow = Math.pow(10, Math.floor(Math.log10(xpos))) return pow*Math.round(xpos/pow) } else { - return canvas.xaxisstep1*Math.round(xpos/canvas.xaxisstep1) + return axisX*Math.round(xpos/axisX) } } else { return xpos.toFixed(parent.precision) } } property double mouseY: { - let ypos = canvas.px2y(picker.mouseY) + const axisY = Modules.Canvas.axesSteps.y.value + const ypos = Modules.Canvas.px2y(picker.mouseY) if(snapToGridCheckbox.checked) { - return canvas.yaxisstep1*Math.round(ypos/canvas.yaxisstep1) + return axisY*Math.round(ypos/axisY) } else { return ypos.toFixed(parent.precision) } @@ -324,9 +323,9 @@ Item { 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(Objects.types[objType].properties()[propertyName] == 'number') + if(Modules.Objects.types[objType].properties()[propertyName] == 'number') return parseFloat(value) else - return new MathLib.Expression(value) + return new JS.MathLib.Expression(value) } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ViewPositionChangeOverlay.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/ViewPositionChange.qml similarity index 64% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ViewPositionChangeOverlay.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/ViewPositionChange.qml index 0d80da4..78329a7 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/ViewPositionChangeOverlay.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/ViewPositionChange.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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,14 +17,9 @@ */ import QtQuick -import QtQuick.Controls -import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting -import "js/objects.js" as Objects -import "js/mathlib.js" as MathLib -import "js/historylib.js" as HistoryLib /*! - \qmltype ViewPositionChangeOverlay + \qmltype ViewPositionChange.Overlay \inqmlmodule eu.ad5001.LogarithmPlotter \brief Overlay used allow the user to drag the canvas' position and change the zoom level. @@ -62,16 +57,6 @@ Item { */ 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. @@ -84,7 +69,7 @@ Item { 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). + How much should the zoom be multiplied/scrolled by for one scroll step (120° on the mouse wheel). */ property double baseZoomMultiplier: 0.1 @@ -94,11 +79,15 @@ Item { cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor property int positionChangeTimer: 0 - function updatePosition(deltaX, deltaY) { - settingsInstance.xmin = (canvas.px2x(canvas.x2px(settingsInstance.xmin)-deltaX)) - settingsInstance.ymax += deltaY/canvas.yzoom - settingsInstance.ymax = settingsInstance.ymax.toFixed(4) - settingsInstance.changed() + function updatePosition(deltaX, deltaY, isEnd) { + const unauthorized = [NaN, Infinity, -Infinity] + const xmin = (Modules.Canvas.px2x(Modules.Canvas.x2px(Modules.Settings.xmin)-deltaX)) + const ymax = Modules.Settings.ymax + deltaY/Modules.Settings.yzoom + if(!unauthorized.includes(xmin)) + Modules.Settings.set("xmin", xmin, isEnd) + if(!unauthorized.includes(ymax)) + Modules.Settings.set("ymax", ymax.toDecimalPrecision(6), isEnd) + Modules.Canvas.requestPaint() parent.positionChanged(deltaX, deltaY) } @@ -108,43 +97,49 @@ Item { 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) + let deltaX = mouse.x - parent.prevX + let deltaY = mouse.y - parent.prevY + updatePosition(deltaX, deltaY, false) prevX = mouse.x prevY = mouse.y positionChangeTimer = 0 } } + onReleased: function(mouse) { - let deltaX = mouse.x - prevX - let deltaY = mouse.y - prevY - updatePosition(deltaX, deltaY) + let deltaX = mouse.x - parent.prevX + let deltaY = mouse.y - parent.prevY + updatePosition(deltaX, deltaY, true) 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)) + let zoomMultiplier = Math.pow(1+parent.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) + let xZoomDelta = (Modules.Settings.xzoom*zoomMultiplier - Modules.Settings.xzoom) + let yZoomDelta = (Modules.Settings.yzoom*zoomMultiplier - Modules.Settings.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() + let newXZoom = (Modules.Settings.xzoom+xZoomDelta).toDecimalPrecision(0) + let newYZoom = (Modules.Settings.yzoom+yZoomDelta).toDecimalPrecision(0) + // Check if we need to have more precision + if(newXZoom < 10) + newXZoom = (Modules.Settings.xzoom+xZoomDelta).toDecimalPrecision(4) + if(newYZoom < 10) + newYZoom = (Modules.Settings.yzoom+yZoomDelta).toDecimalPrecision(4) + if(newXZoom > 0.5) + Modules.Settings.set("xzoom", newXZoom) + if(newYZoom > 0.5) + Modules.Settings.set("yzoom", newYZoom) + Modules.Canvas.requestPaint() } } } diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/qmldir new file mode 100644 index 0000000..0288c9e --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Overlay/qmldir @@ -0,0 +1,5 @@ +module eu.ad5001.LogarithmPlotter.Overlay + +Loading 1.0 Loading.qml +PickLocation 1.0 PickLocation.qml +ViewPositionChange 1.0 ViewPositionChange.qml diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/About.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/About.qml similarity index 97% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/About.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/About.qml index d74c101..e82f39c 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/About.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/About.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -102,7 +102,7 @@ BaseDialog { wrapMode: Text.WordWrap textFormat: Text.RichText font.pixelSize: 13 - text: "Copyright © 2021-2024 Ad5001 <mail@ad5001.eu>
                        + text: "Copyright © 2021-2025 Ad5001 <mail@ad5001.eu>

                        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.

                        diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Alert.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Alert.qml similarity index 98% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Alert.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Alert.qml index 0220956..98e37d4 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Alert.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Alert.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/BaseDialog.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/BaseDialog.qml similarity index 97% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/BaseDialog.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/BaseDialog.qml index 10fe87e..a9c73a6 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/BaseDialog.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/BaseDialog.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Changelog.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Changelog.qml similarity index 85% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Changelog.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Changelog.qml index f86af07..1797b9d 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Changelog.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Changelog.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -45,17 +45,17 @@ Popup { property bool changelogNeedsFetching: true onAboutToShow: if(changelogNeedsFetching) { - Helper.fetchChangelog() - } - - Connections { - target: Helper - function onChangelogFetched(chl) { - changelogNeedsFetching = false; - changelog.text = chl + Helper.fetchChangelog().then((fetchedText) => { + changelogNeedsFetching = false + changelog.text = fetchedText changelogView.contentItem.implicitHeight = changelog.height - // console.log(changelog.height, changelogView.contentItem.implicitHeight) - } + }, (error) => { + const e = qsTranslate("changelog", "Could not fetch update: {}.").replace('{}', error) + console.error(e) + changelogNeedsFetching = false + changelog.text = e + changelogView.contentItem.implicitHeight = changelog.height + }) } ScrollView { @@ -96,7 +96,7 @@ Popup { Button { id: doneBtn - text: qsTr("Done") + text: qsTr("Close") font.pixelSize: 18 anchors.bottom: parent.bottom anchors.bottomMargin: 7 diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/FileDialog.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/FileDialog.qml similarity index 97% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/FileDialog.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/FileDialog.qml index 4b7ff8a..00dbdae 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/FileDialog.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/FileDialog.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/GreetScreen.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/GreetScreen.qml new file mode 100644 index 0000000..a083d64 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/GreetScreen.qml @@ -0,0 +1,156 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick +import QtQuick.Controls +import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting + +/*! + \qmltype GreetScreen + \inqmlmodule eu.ad5001.LogarithmPlotter.Popup + \brief Overlay displayed when LogarithmPlotter is launched for the first time or when it was just updated. + + It contains several settings as well as an easy access to the changelog + + \sa LogarithmPlotter, Settings, AppMenuBar, Changelog +*/ +Popup { + id: greetingPopup + x: (parent.width-width)/2 + y: Math.max(20, (parent.height-height)/2) + width: greetingLayout.width+20 + height: Math.min(parent.height-40, 700) + 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 { + 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.Wrap + clip: true + } + } + } + } + + Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()+1) { + greetingPopup.open() + } + + onClosed: Helper.setSetting("last_install_greet", Helper.getVersion()) +} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml similarity index 72% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml index 7002539..8111f85 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/InsertCharacter.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 . */ -import QtQuick.Controls import QtQuick +import QtQuick.Controls +import QtQml.Models /*! \qmltype InsertCharacter @@ -42,15 +43,18 @@ Popup { */ property string category: 'all' - width: 280 - height: Math.ceil(insertGrid.insertChars.length/insertGrid.columns)*(width/insertGrid.columns)+5 + width: insertGrid.width + 10 + height: insertGrid.height + 10 modal: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent - Grid { + GridView { id: insertGrid - width: parent.width - columns: 7 + width: 280 + height: Math.ceil(model.count/columns)*cellHeight + property int columns: 7 + cellWidth: width/columns + cellHeight: cellWidth property var insertCharsExpression: [ "∞","π","¹","²","³","⁴","⁵", @@ -86,21 +90,34 @@ Popup { }[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) - } + model: ListModel {} + + delegate: Button { + id: insertBtn + width: insertGrid.cellWidth + height: insertGrid.cellHeight + text: chr + flat: text == " " + font.pixelSize: 18 + + onClicked: { + insertPopup.selected(text) + insertPopup.close() } } + + Component.onCompleted: function() { + for(const chr of insertChars) { + model.append({ 'chr': chr }) + } + } + + Keys.onEscapePressed: parent.close() } + + function setFocus() { + insertGrid.currentIndex = 0 + insertGrid.forceActiveFocus() + } + } diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Preferences.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Preferences.qml new file mode 100644 index 0000000..9ed40c8 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/Preferences.qml @@ -0,0 +1,255 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting +import eu.ad5001.LogarithmPlotter.Common + +/*! + \qmltype Preferences + \inqmlmodule eu.ad5001.LogarithmPlotter.Popup + \brief Popup to change global application preferences. + + \sa LogarithmPlotter, GreetScreen +*/ +Popup { + id: preferencesPopup + x: (parent.width-width)/2 + y: Math.max(20, (parent.height-height)/2) + width: settingPopupRow.width + 30 + height: settingPopupRow.height + 20 + modal: true + focus: true + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + // Components for the preferences + Component { + id: boolSettingComponent + + CheckBox { + height: 20 + text: setting.name + checked: setting.value() + onClicked: setting.set(this.checked) + } + } + + Component { + id: enumIntSettingComponent + + // Setting when selecting data from an enum, or an object of a certain type. + Setting.ComboBoxSetting { + height: 30 + label: setting.name + icon: `settings/${setting.icon}.svg` + currentIndex: setting.value() + model: setting.values + onActivated: function(newIndex) { setting.set(newIndex) } + } + } + + Component { + id: stringSettingComponent + + Setting.ComboBoxSetting { + height: 30 + label: setting.name + icon: `settings/${setting.icon}.svg` + editable: true + currentIndex: find(setting.value()) + model: setting.defaultValues + onAccepted: function() { + editText = JS.Utils.parseName(editText, false) + if(find(editText) === -1) model.append(editText) + setting.set(editText) + } + onActivated: function(selectedId) { + setting.set(model[selectedId]) + } + Component.onCompleted: editText = setting.value() + } + } + + Component { + id: numberSettingComponent + + Setting.TextSetting { + height: 30 + isDouble: true + label: setting.name + min: setting.min() + icon: `settings/${setting.icon}.svg` + value: setting.value() + onChanged: function(newValue) { + if(newValue < setting.max()) + setting.set(newValue) + else { + value = setting.max() + setting.set(setting.max()) + } + } + } + } + + Component { + id: expressionSettingComponent + + Setting.ExpressionEditor { + height: 30 + label: setting.name + icon: `settings/${setting.icon}.svg` + defValue: JS.Utils.simplifyExpression(setting.value()) + variables: setting.variables + allowGraphObjects: false + property string propertyName: setting.name + onChanged: function(newExpr) { + try { + setting.set(newExpr) + } catch(e) { + errorDialog.showDialog(propertyName, newExpr, e.message) + } + } + } + } + + Row { + id: settingPopupRow + height: 300 + width: categories.width + categorySeparator.width + settingView.width + 70 + spacing: 15 + + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + right: parent.right + topMargin: 10 + bottomMargin: 10 + rightMargin: 15 + leftMargin: 15 + } + + ColumnLayout { + id: categories + width: 150 + height: parent.height + spacing: 0 + clip: true + + Repeater { + model: Object.keys(Modules.Preferences.categories) + + Button { + // width: 150 + Layout.fillWidth: true + text: qsTranslate('settingCategory', modelData) + + onClicked: { + settingView.modelName = modelData + } + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + Button { + id: closeButton + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + text: qsTr('Close') + onClicked: preferencesPopup.close() + } + } + } + + Rectangle { + id: categorySeparator + anchors { + top: parent.top + topMargin: 5 + } + opacity: 0.3 + color: sysPalette.windowText + height: parent.height - 10 + width: 1 + } + + ListView { + id: settingView + clip: true + width: 500 + spacing: 10 + model: Modules.Preferences.categories[modelName] + anchors { + top: parent.top + bottom: parent.bottom + } + ScrollBar.vertical: ScrollBar { } + property string modelName: 'general' + + + header: Text { + id: settingCategoryName + font.pixelSize: 32 + height: 48 + color: sysPalette.windowText + text: qsTranslate('settingCategory', settingView.modelName) + + Rectangle { + id: bottomSeparator + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + opacity: 0.3 + color: sysPalette.windowText + width: settingView.width + height: 1 + } + } + + delegate: Component { + Loader { + width: settingView.width - 20 + property var setting: Modules.Preferences.categories[settingView.modelName][index] + sourceComponent: { + if(setting.type === "bool") + return boolSettingComponent + else if(setting.type === "enum") + return enumIntSettingComponent + else if(setting.type === "number") + return numberSettingComponent + else if(setting.type === "expression") + return expressionSettingComponent + else if(setting.type === "string") + return stringSettingComponent + else + console.log('Unknown setting type!', setting.constructor.nameInConfig, setting.constructor) + } + } + } + } + } + + // Component.onCompleted: open() +} diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/ThanksTo.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/ThanksTo.qml new file mode 100644 index 0000000..85e4a64 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/ThanksTo.qml @@ -0,0 +1,367 @@ +/** + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick +import QtQuick.Dialogs +import QtQuick.Controls + +/*! + \qmltype ThanksTo + \inqmlmodule eu.ad5001.LogarithmPlotter.Popup + \brief Thanks to popup of LogarithmPlotter. + + \sa LogarithmPlotter +*/ +BaseDialog { + id: thanks + title: qsTr("Thanks and Contributions - LogarithmPlotter") + width: 450 + minimumHeight: 600 + + ScrollView { + + anchors { + top: parent.top; + left: parent.left; + bottom: parent.bottom; + right: parent.right; + topMargin: margin; + leftMargin: margin; + bottomMargin: margin+30; + } + + Column { + + anchors { + left: parent.left; + } + + width: thanks.width - 2*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: { + const authors = { + Ad5001: { + authorLine: 'Ad5001', + email: 'mail@ad5001.eu', + website: 'https://ad5001.eu', + websiteName: qsTr('Website') + }, + Ovari: { + authorLine: 'Óvári', + website: 'https://github.com/ovari', + websiteName: qsTr('Github') + }, + comradekingu: { + authorLine: 'Allan Nordhøy', + website: 'https://github.com/comradekingu', + websiteName: qsTr('Github') + }, + IngrownMink4: { + authorLine: 'IngrownMink4', + website: 'https://github.com/IngrownMink4', + websiteName: qsTr('Github') + }, + gallegonovato: { + authorLine: 'gallegonovato', + website: '', + websiteName: '' + }, + TamilNeram: { + authorLine: 'தமிழ் நேரம்', + website: 'https://github.com/TamilNeram', + websiteName: qsTr('Github') + } + } + + append({ + tranName: '🇬🇧 ' + qsTr('English'), + link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/', + authors: [authors.Ad5001] + }) + append({ + tranName: '🇫🇷 ' + qsTr('French'), + link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/', + authors: [authors.Ad5001] + }) + append({ + tranName: '🇩🇪 ' + qsTr('German'), + link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/', + authors: [authors.Ad5001] + }) + append({ + tranName: '🇭🇺 ' + qsTr('Hungarian'), + link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/', + authors: [authors.Ovari] + }) + append({ + tranName: '🇳🇴 ' + qsTr('Norwegian'), + link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/no/', + authors: [authors.comradekingu, authors.Ad5001] + }) + append({ + tranName: '🇪🇸 ' + qsTr('Spanish'), + link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/', + authors: [authors.IngrownMink4, authors.gallegonovato] + }) + append({ + tranName: '🇱🇰 ' + qsTr('Tamil'), + link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/ta/', + authors: [authors.TamilNeram] + }) + } + } + + 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.left: tranAuthorName.right + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + height: 30 + spacing: 10 + + Button { + text: websiteName + visible: websiteName !== "" + icon.name: 'web-browser' + height: parent.height + onClicked: Qt.openUrlExternally(website) + } + } + } + } + } + } + } + } +} diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/qmldir similarity index 89% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/qmldir rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/qmldir index 9306fae..3cf03fa 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/qmldir +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Popup/qmldir @@ -1,10 +1,11 @@ module eu.ad5001.LogarithmPlotter.Popup -BaseDialog 1.0 BaseDialog.qml -About 1.0 About.qml Alert 1.0 Alert.qml +About 1.0 About.qml +BaseDialog 1.0 BaseDialog.qml +Changelog 1.0 Changelog.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 +ThanksTo 1.0 ThanksTo.qml diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml similarity index 99% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml index 304b704..897af70 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/AutocompletionCategory.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ComboBoxSetting.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ComboBoxSetting.qml similarity index 98% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ComboBoxSetting.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ComboBoxSetting.qml index d701661..a4dc93b 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ComboBoxSetting.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ComboBoxSetting.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -114,6 +114,7 @@ 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) diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml similarity index 82% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml index fa3dece..35bba0a 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ExpressionEditor.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -20,10 +20,7 @@ import QtQuick.Controls import QtQuick import Qt.labs.platform as Native import eu.ad5001.LogarithmPlotter.Popup 1.0 as P -import "../js/mathlib.js" as MathLib -import "../js/utils.js" as Utils -import "../js/objects.js" as Objects -import "../js/parsing/parsing.js" as Parsing +import eu.ad5001.LogarithmPlotter.Common /*! @@ -81,6 +78,17 @@ Item { 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 @@ -167,19 +175,20 @@ Item { Icon { id: iconLabel anchors.top: parent.top - anchors.topMargin: icon == "" ? 0 : 3 - source: control.visible && icon != "" ? "../icons/" + control.icon : "" + anchors.topMargin: parent.icon == "" ? 0 : 3 + source: control.visible && parent.icon != "" ? "../icons/" + control.icon : "" width: height - height: icon == "" || !visible ? 0 : 24 + height: parent.icon == "" || !visible ? 0 : 24 color: sysPalette.windowText } Label { id: labelItem anchors.left: iconLabel.right - anchors.leftMargin: icon == "" ? 0 : 5 - height: parent.height + anchors.leftMargin: parent.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) : "" @@ -191,7 +200,9 @@ Item { 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) + text = qsTranslate("expression", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3") + .arg(qsTranslate('prop', propName)) + .arg(error).arg(propValue) open() } } @@ -210,9 +221,9 @@ Item { 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 bool autocompleteEnabled: Helper.getSetting("autocompletion.enabled") + property bool syntaxHighlightingEnabled: Helper.getSetting("expression_editor.colorize") + property bool autoClosing: Helper.getSetting("expression_editor.autoclose") property var tokens: autocompleteEnabled || syntaxHighlightingEnabled ? parent.tokens(text) : [] Keys.priority: Keys.BeforeItem // Required for knowing which key the user presses. @@ -220,8 +231,8 @@ Item { onEditingFinished: { if(insertButton.focus || insertPopup.focus) return let value = text - if(value != "" && value.toString() != defValue) { - let expr = parse(value) + if(value != "" && value.toString() != parent.defValue) { + let expr = parent.parse(value) if(expr != null) { control.changed(expr) defValue = expr.toEditableString() @@ -269,10 +280,10 @@ Item { acPopupContent.itemSelected = 0 - if(event.text in openAndCloseMatches && autoClosing) { + if(event.text in parent.openAndCloseMatches && autoClosing) { let start = selectionStart insert(selectionStart, event.text) - insert(selectionEnd, openAndCloseMatches[event.text]) + insert(selectionEnd, parent.openAndCloseMatches[event.text]) cursorPosition = start+1 event.accepted = true } @@ -306,9 +317,9 @@ Item { width: parent.width readonly property var identifierTokenTypes: [ - Parsing.TokenType.VARIABLE, - Parsing.TokenType.FUNCTION, - Parsing.TokenType.CONSTANT + JS.Parsing.TokenType.VARIABLE, + JS.Parsing.TokenType.FUNCTION, + JS.Parsing.TokenType.CONSTANT ] property var currentToken: generateTokenInformation(getTokenAt(editor.tokens, editor.cursorPosition)) property var previousToken: generateTokenInformation(getPreviousToken(currentToken.token)) @@ -333,7 +344,7 @@ Item { '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, + 'dot': exists ? (token.type == JS.Parsing.TokenType.PUNCT && token.value == ".") : false, 'identifier': exists ? identifierTokenTypes.includes(token.type) : false } } @@ -372,7 +383,7 @@ Item { */ function getPreviousToken(token) { let newToken = getTokenAt(editor.tokens, token.startPosition) - if(newToken != null && newToken.type == Parsing.TokenType.WHITESPACE) + if(newToken != null && newToken.type == JS.Parsing.TokenType.WHITESPACE) return getPreviousToken(newToken) return newToken } @@ -381,7 +392,7 @@ Item { id: objectPropertiesList category: qsTr("Object Properties") - visbilityCondition: doesObjectExist + visbilityCondition: control.allowGraphObjects && doesObjectExist itemStartIndex: 0 itemSelected: parent.itemSelected property bool isEnteringProperty: ( @@ -392,9 +403,9 @@ Item { property string objectName: isEnteringProperty ? (parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value) : "" - property bool doesObjectExist: isEnteringProperty && (objectName in Objects.currentObjectsByName) + property bool doesObjectExist: isEnteringProperty && (objectName in Modules.Objects.currentObjectsByName) property var objectProperties: doesObjectExist ? - Objects.currentObjectsByName[objectName].constructor.properties() : + Modules.Objects.currentObjectsByName[objectName].constructor.properties() : {} categoryItems: Object.keys(objectProperties) autocompleteGenerator: (item) => { @@ -431,9 +442,9 @@ Item { visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot itemStartIndex: variablesList.itemStartIndex + variablesList.model.length itemSelected: parent.itemSelected - categoryItems: Parsing.CONSTANTS_LIST + categoryItems: JS.Parsing.CONSTANTS_LIST autocompleteGenerator: (item) => {return { - 'text': item, 'annotation': Parsing.CONSTANTS[item], + 'text': item, 'annotation': JS.Parsing.CONSTANTS[item], 'autocomplete': item + " ", 'cursorFinalOffset': 0 }} baseText: parent.visible ? parent.currentToken.value : "" @@ -446,9 +457,9 @@ Item { visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot itemStartIndex: constantsList.itemStartIndex + constantsList.model.length itemSelected: parent.itemSelected - categoryItems: Parsing.FUNCTIONS_LIST + categoryItems: JS.Parsing.FUNCTIONS_LIST autocompleteGenerator: (item) => {return { - 'text': item, 'annotation': Parsing.FUNCTIONS_USAGE[item].join(', '), + 'text': item, 'annotation': JS.Parsing.FUNCTIONS_USAGE[item].join(', '), 'autocomplete': item+'()', 'cursorFinalOffset': -1 }} baseText: parent.visible ? parent.currentToken.value : "" @@ -458,12 +469,12 @@ Item { id: executableObjectsList category: qsTr("Executable Objects") - visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot + visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot itemStartIndex: functionsList.itemStartIndex + functionsList.model.length itemSelected: parent.itemSelected - categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self) + categoryItems: Modules.Objects.getObjectsName("ExecutableObject").filter(obj => obj != self) autocompleteGenerator: (item) => {return { - 'text': item, 'annotation': Objects.currentObjectsByName[item] == null ? '' : Objects.currentObjectsByName[item].constructor.displayType(), + 'text': item, 'annotation': Modules.Objects.currentObjectsByName[item] == null ? '' : Modules.Objects.currentObjectsByName[item].constructor.displayType(), 'autocomplete': item+'()', 'cursorFinalOffset': -1 }} baseText: parent.visible ? parent.currentToken.value : "" @@ -473,12 +484,12 @@ Item { id: objectsList category: qsTr("Objects") - visbilityCondition: parent.currentToken.identifier && !parent.previousToken.dot + visbilityCondition: control.allowGraphObjects && parent.currentToken.identifier && !parent.previousToken.dot itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length itemSelected: parent.itemSelected - categoryItems: Object.keys(Objects.currentObjectsByName).filter(obj => obj != self) + categoryItems: Object.keys(Modules.Objects.currentObjectsByName).filter(obj => obj != self) autocompleteGenerator: (item) => {return { - 'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`, + 'text': item, 'annotation': `${Modules.Objects.currentObjectsByName[item].constructor.displayType()}`, 'autocomplete': item+'.', 'cursorFinalOffset': 0 }} baseText: parent.visible ? parent.currentToken.value : "" @@ -489,29 +500,41 @@ Item { Button { id: insertButton - text: "α" anchors.right: parent.right anchors.rightMargin: 5 anchors.verticalCenter: parent.verticalCenter width: 20 height: width + + Icon { + id: icon + width: 12 + height: 12 + anchors.centerIn: parent + + color: sysPalette.windowText + source: '../icons/properties/expression.svg' + } + onClicked: { insertPopup.open() - insertPopup.focus = true + insertPopup.setFocus() } } P.InsertCharacter { id: insertPopup - x: Math.round((parent.width - width) / 2) - y: Math.round((parent.height - height) / 2) + x: parent.width - width + y: parent.height category: "expression" onSelected: function(c) { editor.insert(editor.cursorPosition, c) - insertPopup.close() + } + + onClosed: function() { focus = false editor.focus = true } @@ -525,9 +548,9 @@ Item { function parse(newExpression) { let expr = null try { - expr = new MathLib.Expression(value.toString()) + expr = new JS.MathLib.Expression(value.toString()) // Check if the expression is valid, throws error otherwise. - if(!expr.allRequirementsFullfilled()) { + if(!expr.allRequirementsFulfilled()) { let undefVars = expr.undefinedVariables() if(undefVars.length > 1) throw new Error(qsTranslate('error', 'No object found with names %1.').arg(undefVars.join(', '))) @@ -538,8 +561,8 @@ Item { throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.')) // Recursive dependencies let dependentOnSelfObjects = expr.requiredObjects().filter( - (obj) => Objects.currentObjectsByName[obj].getDependenciesList() - .includes(Objects.currentObjectsByName[control.self]) + (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)) @@ -559,7 +582,7 @@ Item { Generates a list of tokens from the given. */ function tokens(text) { - let tokenizer = new Parsing.Tokenizer(new Parsing.Input(text), true, false) + let tokenizer = new JS.Parsing.Tokenizer(new JS.Parsing.Input(text), true, false) let tokenList = [] let token while((token = tokenizer.next()) != null) @@ -589,31 +612,31 @@ Item { */ function colorize(tokenList) { let parsedText = "" - let scheme = colorSchemes[Helper.getSettingInt("expression_editor.color_scheme")] + let scheme = colorSchemes[Helper.getSetting("expression_editor.color_scheme")] for(let token of tokenList) { switch(token.type) { - case Parsing.TokenType.VARIABLE: + case JS.Parsing.TokenType.VARIABLE: parsedText += `${token.value}` break; - case Parsing.TokenType.CONSTANT: + case JS.Parsing.TokenType.CONSTANT: parsedText += `${token.value}` break; - case Parsing.TokenType.FUNCTION: - parsedText += `${Utils.escapeHTML(token.value)}` + case JS.Parsing.TokenType.FUNCTION: + parsedText += `${JS.Utils.escapeHTML(token.value)}` break; - case Parsing.TokenType.OPERATOR: - parsedText += `${Utils.escapeHTML(token.value)}` + case JS.Parsing.TokenType.OPERATOR: + parsedText += `${JS.Utils.escapeHTML(token.value)}` break; - case Parsing.TokenType.NUMBER: - parsedText += `${Utils.escapeHTML(token.value)}` + case JS.Parsing.TokenType.NUMBER: + parsedText += `${JS.Utils.escapeHTML(token.value)}` break; - case Parsing.TokenType.STRING: - parsedText += `${token.limitator}${Utils.escapeHTML(token.value)}${token.limitator}` + case JS.Parsing.TokenType.STRING: + parsedText += `${token.limitator}${JS.Utils.escapeHTML(token.value)}${token.limitator}` break; - case Parsing.TokenType.WHITESPACE: - case Parsing.TokenType.PUNCT: + case JS.Parsing.TokenType.WHITESPACE: + case JS.Parsing.TokenType.PUNCT: default: - parsedText += Utils.escapeHTML(token.value).replace(/ /g, ' ') + parsedText += JS.Utils.escapeHTML(token.value).replace(/ /g, ' ') break; } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/Icon.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/Icon.qml similarity index 80% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/Icon.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/Icon.qml index f845eb9..6ba76aa 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/Icon.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/Icon.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ */ import QtQuick import QtQuick.Window -import Qt5Compat.GraphicalEffects +import QtQuick.Controls.impl /*! \qmltype Icon @@ -41,20 +41,16 @@ Item { \qmlproperty string Icon::source Path of the icon image source. */ - property alias sourceSize: img.sourceSize.width + property alias sourceSize: img.sourceS - Image { + ColorImage { id: img height: parent.height width: parent.width - visible: false - sourceSize.width: width*Screen.devicePixelRatio - sourceSize.height: width*Screen.devicePixelRatio - } - - ColorOverlay { - anchors.fill: img - source: img + // visible: false + property int sourceS: width*Screen.devicePixelRatio + sourceSize.width: sourceS + sourceSize.height: sourceS color: parent.color } } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ListSetting.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ListSetting.qml similarity index 99% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ListSetting.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ListSetting.qml index 221b3a3..daad433 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ListSetting.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/ListSetting.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/TextSetting.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/TextSetting.qml similarity index 83% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/TextSetting.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/TextSetting.qml index d1c4c9d..bc105aa 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/TextSetting.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/TextSetting.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -37,7 +37,7 @@ Item { Emitted when the value of the text has been changed. The corresponding handler is \c onChanged. */ - signal changed(string newValue) + signal changed(var newValue) /*! \qmlproperty bool TextSetting::isInt @@ -100,14 +100,14 @@ Item { id: labelItem anchors.left: iconLabel.right anchors.leftMargin: icon == "" ? 0 : 5 - height: parent.height anchors.top: parent.top + height: parent.height + width: visible ? Math.max(85, implicitWidth) : 0 verticalAlignment: TextInput.AlignVCenter //color: sysPalette.windowText text: visible ? qsTranslate("control", "%1: ").arg(control.label) : "" visible: control.label != "" } - TextField { id: input @@ -127,10 +127,15 @@ Item { selectByMouse: true onEditingFinished: function() { if(insertButton.focus || insertPopup.focus) return - var value = text - 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) { + let value = text + if(control.isInt) { + let parsed = parseInt(value) + value = isNaN(parsed) ? control.min : Math.max(control.min,parsed) + } else if(control.isDouble) { + let parsed = parseFloat(value) + value = isNaN(parsed) ? control.min : Math.max(control.min,parsed) + } + if(value !== "" && value.toString() != defValue) { control.changed(value) defValue = value.toString() } @@ -139,28 +144,40 @@ Item { Button { id: insertButton - text: "α" anchors.right: parent.right anchors.rightMargin: 5 anchors.verticalCenter: parent.verticalCenter width: 20 height: width visible: !isInt && !isDouble + + Icon { + id: icon + width: 12 + height: 12 + anchors.centerIn: parent + + color: sysPalette.windowText + source: '../icons/properties/expression.svg' + } + onClicked: { insertPopup.open() - insertPopup.focus = true + insertPopup.setFocus() } } Popup.InsertCharacter { id: insertPopup - x: Math.round((parent.width - width) / 2) - y: Math.round((parent.height - height) / 2) + x: parent.width - width + y: parent.height onSelected: function(c) { input.insert(input.cursorPosition, c) - insertPopup.close() + } + + onClosed: function() { focus = false input.focus = true } diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir index 8106e26..bb78bbe 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Setting/qmldir @@ -1,8 +1,8 @@ module eu.ad5001.LogarithmPlotter.Setting +AutocompletionCategory 1.0 AutocompletionCategory.qml ComboBoxSetting 1.0 ComboBoxSetting.qml +ExpressionEditor 1.0 ExpressionEditor.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 diff --git a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml similarity index 60% rename from LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml index 208e3e9..58db0e9 100644 --- a/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Settings.qml @@ -1,6 +1,6 @@ /** * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 . */ +import QtQuick import QtQuick.Controls -import QtQuick import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup -import "js/utils.js" as Utils +import eu.ad5001.LogarithmPlotter.Common /*! \qmltype Settings @@ -44,88 +44,83 @@ ScrollView { Zoom on the x axis of the diagram, provided from settings. \sa Settings */ - property double xzoom: 100 + property double xzoom: Helper.getSetting('default_graph.xzoom') /*! \qmlproperty double Settings::yzoom Zoom on the y axis of the diagram, provided from settings. \sa Settings */ - property double yzoom: 10 + property double yzoom: Helper.getSetting('default_graph.yzoom') /*! \qmlproperty double Settings::xmin Minimum x of the diagram, provided from settings. \sa Settings */ - property double xmin: 5/10 + property double xmin: Helper.getSetting('default_graph.xmin') /*! \qmlproperty double Settings::ymax Maximum y of the diagram, provided from settings. \sa Settings */ - property double ymax: 25 + property double ymax: Helper.getSetting('default_graph.ymax') /*! \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: "4" + property string xaxisstep: Helper.getSetting('default_graph.xaxisstep') /*! \qmlproperty string Settings::yaxisstep Step of the y axis graduation, provided from settings. \sa Settings */ - property string yaxisstep: "4" + property string yaxisstep: Helper.getSetting('default_graph.yaxisstep') /*! \qmlproperty string Settings::xlabel Label used on the x axis, provided from settings. \sa Settings */ - property string xlabel: "" + property string xlabel: Helper.getSetting('default_graph.xlabel') /*! \qmlproperty string Settings::ylabel Label used on the y axis, provided from settings. \sa Settings */ - property string ylabel: "" + property string ylabel: Helper.getSetting('default_graph.ylabel') /*! \qmlproperty double Settings::linewidth Width of lines that will be drawn into the canvas, provided from settings. \sa Settings */ - property double linewidth: 1 + property double linewidth: Helper.getSetting('default_graph.linewidth') /*! \qmlproperty double Settings::textsize Font size of the text that will be drawn into the canvas, provided from settings. \sa Settings */ - property double textsize: 18 + property double textsize: Helper.getSetting('default_graph.textsize') /*! \qmlproperty bool Settings::logscalex true if the canvas should be in logarithmic mode, false otherwise. Provided from settings. \sa Settings */ - property bool logscalex: true + property bool logscalex: Helper.getSetting('default_graph.logscalex') /*! \qmlproperty bool Settings::showxgrad true if the x graduation should be shown, false otherwise. Provided from settings. \sa Settings */ - property bool showxgrad: true + property bool showxgrad: Helper.getSetting('default_graph.showxgrad') /*! \qmlproperty bool Settings::showygrad true if the y graduation should be shown, false otherwise. Provided from settings. \sa Settings */ - property bool showygrad: true - /*! - \qmlproperty bool Settings::saveFilename - Path of the currently opened file. Empty if no file is opened. - */ - property string saveFilename: "" + property bool showygrad: Helper.getSetting('default_graph.showygrad') Column { spacing: 10 @@ -136,15 +131,18 @@ ScrollView { id: fdiag onAccepted: { var filePath = fdiag.currentFile.toString().substr(7) - settings.saveFilename = filePath + Modules.Settings.set("saveFilename", filePath) if(exportMode) { - root.saveDiagram(filePath) + Modules.IO.saveDiagram(filePath) } else { - 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}) - yAxisLabel.editText = settings.ylabel + Modules.IO.loadDiagram(filePath) + // Adding labels. + if(xAxisLabel.find(Modules.Settings.xlabel) === -1) + xAxisLabel.model.append({text: Modules.Settings.xlabel}) + xAxisLabel.editText = Modules.Settings.xlabel + if(yAxisLabel.find(Modules.Settings.ylabel) === -1) + yAxisLabel.model.append({text: Modules.Settings.ylabel}) + yAxisLabel.editText = Modules.Settings.ylabel } } } @@ -155,28 +153,39 @@ ScrollView { height: 30 isDouble: true label: qsTr("X Zoom") - min: 1 + min: 0.1 icon: "settings/xzoom.svg" width: settings.settingWidth - value: settings.xzoom.toFixed(2) + onChanged: function(newValue) { - settings.xzoom = newValue + Modules.Settings.set("xzoom", newValue, true) settings.changed() } + + function update(newValue) { + value = Modules.Settings.xzoom.toFixed(2) + maxX.update() + } } Setting.TextSetting { id: zoomY height: 30 isDouble: true + min: 0.1 label: qsTr("Y Zoom") icon: "settings/yzoom.svg" width: settings.settingWidth - value: settings.yzoom.toFixed(2) + onChanged: function(newValue) { - settings.yzoom = newValue + Modules.Settings.set("yzoom", newValue, true) settings.changed() } + + function update(newValue) { + value = Modules.Settings.yzoom.toFixed(2) + minY.update() + } } // Positioning the graph @@ -188,14 +197,18 @@ ScrollView { label: qsTr("Min X") icon: "settings/xmin.svg" width: settings.settingWidth - defValue: settings.xmin + onChanged: function(newValue) { - if(parseFloat(maxX.value) > newValue) { - settings.xmin = newValue - settings.changed() - } else { - alert.show("Minimum x value must be inferior to maximum.") - } + Modules.Settings.set("xmin", newValue, true) + settings.changed() + } + + function update(newValue) { + let newVal = Modules.Settings.xmin + if(newVal > 1e-5) + newVal = newVal.toDecimalPrecision(8) + value = newVal + maxX.update() } } @@ -207,11 +220,16 @@ ScrollView { label: qsTr("Max Y") icon: "settings/ymax.svg" width: settings.settingWidth - defValue: settings.ymax + onChanged: function(newValue) { - settings.ymax = newValue + Modules.Settings.set("ymax", newValue, true) settings.changed() } + + function update() { + value = Modules.Settings.ymax + minY.update() + } } Setting.TextSetting { @@ -222,15 +240,24 @@ ScrollView { label: qsTr("Max X") icon: "settings/xmax.svg" width: settings.settingWidth - value: canvas.px2x(canvas.canvasSize.width).toFixed(2) + onChanged: function(xvaluemax) { - if(xvaluemax > settings.xmin) { - settings.xzoom = settings.xzoom * canvas.canvasSize.width/(canvas.x2px(xvaluemax)) // Adjusting zoom to fit. = (end)/(px of current point) + if(xvaluemax > Modules.Settings.xmin) { + const newXZoom = Modules.Settings.xzoom * canvas.width/(Modules.Canvas.x2px(xvaluemax)) // Adjusting zoom to fit. = (end)/(px of current point) + Modules.Settings.set("xzoom", newXZoom, true) + zoomX.update() settings.changed() } else { alert.show("Maximum x value must be superior to minimum.") } } + + function update() { + let newVal = Modules.Canvas.px2x(canvas.width) + if(newVal > 1e-5) + newVal = newVal.toDecimalPrecision(8) + value = newVal + } } Setting.TextSetting { @@ -241,15 +268,21 @@ ScrollView { label: qsTr("Min Y") icon: "settings/ymin.svg" width: settings.settingWidth - defValue: canvas.px2y(canvas.canvasSize.height).toFixed(2) + onChanged: function(yvaluemin) { if(yvaluemin < settings.ymax) { - settings.yzoom = settings.yzoom * canvas.canvasSize.height/(canvas.y2px(yvaluemin)) // Adjusting zoom to fit. = (end)/(px of current point) + const newYZoom = Modules.Settings.yzoom * canvas.height/(Modules.Canvas.y2px(yvaluemin)) // Adjusting zoom to fit. = (end)/(px of current point) + Modules.Settings.set("yzoom", newYZoom, true) + zoomY.update() settings.changed() } else { alert.show("Minimum y value must be inferior to maximum.") } } + + function update() { + value = Modules.Canvas.px2y(canvas.height).toDecimalPrecision(8) + } } Setting.TextSetting { @@ -259,12 +292,16 @@ ScrollView { label: qsTr("X Axis Step") icon: "settings/xaxisstep.svg" width: settings.settingWidth - defValue: settings.xaxisstep - visible: !settings.logscalex + onChanged: function(newValue) { - settings.xaxisstep = newValue + Modules.Settings.set("xaxisstep", newValue, true) settings.changed() } + + function update() { + value = Modules.Settings.xaxisstep + visible = !Modules.Settings.logscalex + } } Setting.TextSetting { @@ -274,11 +311,13 @@ ScrollView { label: qsTr("Y Axis Step") icon: "settings/yaxisstep.svg" width: settings.settingWidth - defValue: settings.yaxisstep + onChanged: function(newValue) { - settings.yaxisstep = newValue + Modules.Settings.set("yaxisstep", newValue, true) settings.changed() } + + function update() { value = Modules.Settings.yaxisstep } } Setting.TextSetting { @@ -289,11 +328,13 @@ ScrollView { min: 1 icon: "settings/linewidth.svg" width: settings.settingWidth - defValue: settings.linewidth + onChanged: function(newValue) { - settings.linewidth = newValue + Modules.Settings.set("linewidth", newValue, true) settings.changed() } + + function update() { value = Modules.Settings.linewidth } } Setting.TextSetting { @@ -304,11 +345,13 @@ ScrollView { min: 1 icon: "settings/textsize.svg" width: settings.settingWidth - defValue: settings.textsize + onChanged: function(newValue) { - settings.textsize = newValue + Modules.Settings.set("textsize", newValue, true) settings.changed() } + + function update() { value = Modules.Settings.textsize } } Setting.ComboBoxSetting { @@ -317,24 +360,31 @@ ScrollView { width: settings.settingWidth label: qsTr('X Label') icon: "settings/xlabel.svg" + editable: true model: ListModel { ListElement { text: "" } ListElement { text: "x" } ListElement { text: "ω (rad/s)" } } - currentIndex: find(settings.xlabel) - editable: true + onAccepted: function(){ - editText = Utils.parseName(editText, false) - if (find(editText) === -1) model.append({text: editText}) - settings.xlabel = editText + editText = JS.Utils.parseName(editText, false) + if(find(editText) === -1) model.append({text: editText}) + currentIndex = find(editText) + Modules.Settings.set("xlabel", editText, true) settings.changed() } + onActivated: function(selectedId) { - settings.xlabel = model.get(selectedId).text + Modules.Settings.set("xlabel", model.get(selectedId).text, true) settings.changed() } - Component.onCompleted: editText = settings.xlabel + + function update() { + editText = Modules.Settings.xlabel + if(find(editText) === -1) model.append({text: editText}) + currentIndex = find(editText) + } } Setting.ComboBoxSetting { @@ -343,6 +393,7 @@ ScrollView { width: settings.settingWidth label: qsTr('Y Label') icon: "settings/ylabel.svg" + editable: true model: ListModel { ListElement { text: "" } ListElement { text: "y" } @@ -351,39 +402,52 @@ ScrollView { ListElement { text: "φ (deg)" } ListElement { text: "φ (rad)" } } - currentIndex: find(settings.ylabel) - editable: true + onAccepted: function(){ - editText = Utils.parseName(editText, false) - if (find(editText) === -1) model.append({text: editText, yaxisstep: root.yaxisstep}) - settings.ylabel = editText + editText = JS.Utils.parseName(editText, false) + if(find(editText) === -1) model.append({text: editText}) + currentIndex = find(editText) + Modules.Settings.set("ylabel", editText, true) settings.changed() } + onActivated: function(selectedId) { - settings.ylabel = model.get(selectedId).text + Modules.Settings.set("ylabel", model.get(selectedId).text, true) settings.changed() } - Component.onCompleted: editText = settings.ylabel + + function update() { + editText = Modules.Settings.ylabel + if(find(editText) === -1) model.append({text: editText}) + currentIndex = find(editText) + } } CheckBox { id: logScaleX - checked: settings.logscalex text: qsTr('X Log scale') onClicked: { - settings.logscalex = checked + Modules.Settings.set("logscalex", checked, true) + if(Modules.Settings.xmin <= 0) // Reset xmin to prevent crash. + Modules.Settings.set("xmin", .5) settings.changed() } + + function update() { + checked = Modules.Settings.logscalex + xAxisStep.update() + } } CheckBox { id: showXGrad - checked: settings.showxgrad text: qsTr('Show X graduation') onClicked: { - settings.showxgrad = checked + Modules.Settings.set("showxgrad", checked, true) settings.changed() } + + function update() { checked = Modules.Settings.showxgrad } } CheckBox { @@ -391,9 +455,10 @@ ScrollView { checked: settings.showygrad text: qsTr('Show Y graduation') onClicked: { - settings.showygrad = checked + Modules.Settings.set("showygrad", checked, true) settings.changed() } + function update() { checked = Modules.Settings.showygrad } } Button { @@ -439,10 +504,10 @@ ScrollView { Saves the current canvas in the opened file. If no file is currently opened, prompts to pick a save location. */ function save() { - if(settings.saveFilename == "") { + if(Modules.Settings.saveFilename == "") { saveAs() } else { - root.saveDiagram(settings.saveFilename) + Modules.IO.saveDiagram(Modules.Settings.saveFilename) } } @@ -463,4 +528,30 @@ ScrollView { fdiag.exportMode = false fdiag.open() } + + /** + * Initializing the settings + */ + Component.onCompleted: function() { + const matchedElements = new Map([ + ["xzoom", zoomX], + ["yzoom", zoomY], + ["xmin", minX], + ["ymax", maxY], + ["xaxisstep", xAxisStep], + ["yaxisstep", yAxisStep], + ["xlabel", xAxisLabel], + ["ylabel", yAxisLabel], + ["linewidth", lineWidth], + ["textsize", textSize], + ["logscalex", logScaleX], + ["showxgrad", showXGrad], + ["showygrad", showYGrad] + ]) + Modules.Settings.on("changed", (evt) => { + if(matchedElements.has(evt.property)) + matchedElements.get(evt.property).update() + }) + Modules.Settings.initialize({ helper: Helper }) + } } diff --git a/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/qmldir b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/qmldir new file mode 100644 index 0000000..c80cae5 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/qmldir @@ -0,0 +1,5 @@ +module eu.ad5001.LogarithmPlotter + +AppMenuBar 1.0 AppMenuBar.qml +LogGraphCanvas 1.0 LogGraphCanvas.qml +Settings 1.0 Settings.qml diff --git a/LogarithmPlotter/qml/eu/ad5001/MixedMenu b/runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/MixedMenu similarity index 100% rename from LogarithmPlotter/qml/eu/ad5001/MixedMenu rename to runtime-pyside6/LogarithmPlotter/qml/eu/ad5001/MixedMenu diff --git a/LogarithmPlotter/util/__init__.py b/runtime-pyside6/LogarithmPlotter/util/__init__.py similarity index 100% rename from LogarithmPlotter/util/__init__.py rename to runtime-pyside6/LogarithmPlotter/util/__init__.py diff --git a/LogarithmPlotter/util/config.py b/runtime-pyside6/LogarithmPlotter/util/config.py similarity index 67% rename from LogarithmPlotter/util/config.py rename to runtime-pyside6/LogarithmPlotter/util/config.py index f19e000..2cce4dc 100644 --- a/LogarithmPlotter/util/config.py +++ b/runtime-pyside6/LogarithmPlotter/util/config.py @@ -1,6 +1,6 @@ """ * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -19,14 +19,16 @@ from os import path, environ, makedirs from platform import system from json import load, dumps -from PySide6.QtCore import QLocale, QTranslator +from shutil import which +from PySide6.QtCore import QLocale, QTranslator DEFAULT_SETTINGS = { "check_for_updates": True, "reset_redo_stack": True, "last_install_greet": "0", - "enable_latex": False, + "enable_latex": which("latex") is not None and which("dvipng") is not None, + "enable_latex_threaded": False, "expression_editor": { "autoclose": True, "colorize": True, @@ -34,45 +36,68 @@ DEFAULT_SETTINGS = { }, "autocompletion": { "enabled": True + }, + "default_graph": { + "xzoom": 100, + "yzoom": 10, + "xmin": 5 / 10, + "ymax": 25, + "xaxisstep": "4", + "yaxisstep": "4", + "xlabel": "", + "ylabel": "", + "linewidth": 1, + "textsize": 18, + "logscalex": True, + "showxgrad": True, + "showygrad": True } } # Create config directory CONFIG_PATH = { - "Linux": path.join(environ["XDG_CONFIG_HOME"], "LogarithmPlotter") if "XDG_CONFIG_HOME" in environ else path.join(path.expanduser("~"), ".config", "LogarithmPlotter"), + "Linux": path.join(environ["XDG_CONFIG_HOME"], "LogarithmPlotter") + if "XDG_CONFIG_HOME" in environ else + path.join(path.expanduser("~"), ".config", "LogarithmPlotter"), "Windows": path.join(path.expandvars('%APPDATA%'), "LogarithmPlotter", "config"), "Darwin": path.join(path.expanduser("~"), "Library", "Application Support", "LogarithmPlotter"), }[system()] CONFIG_FILE = path.join(CONFIG_PATH, "config.json") -initialized = False -current_config= DEFAULT_SETTINGS +current_config = DEFAULT_SETTINGS + + +class UnknownNamespaceError(Exception): pass def init(): """ Initializes the config and loads all possible settings from the file if needs be. """ + global current_config + current_config = DEFAULT_SETTINGS makedirs(CONFIG_PATH, exist_ok=True) if path.exists(CONFIG_FILE): - cfg_data = load(open(CONFIG_FILE, 'r', -1, 'utf8')) + cfg_data = load(open(CONFIG_FILE, 'r', -1, 'utf8')) for setting_name in cfg_data: if type(cfg_data[setting_name]) == dict: for setting_name2 in cfg_data[setting_name]: - setSetting(setting_name+"."+setting_name2, cfg_data[setting_name][setting_name2]) + setSetting(setting_name + "." + setting_name2, cfg_data[setting_name][setting_name2]) else: setSetting(setting_name, cfg_data[setting_name]) - -def save(): + + +def save(file=CONFIG_FILE): """ Saves the config to the path. """ - write_file = open(CONFIG_FILE, 'w', -1, 'utf8') + write_file = open(file, 'w', -1, 'utf8') write_file.write(dumps(current_config)) write_file.close() + def getSetting(namespace): """ Returns a setting from a namespace. @@ -86,9 +111,10 @@ def getSetting(namespace): setting = setting[name] else: # return namespace # Return original name - raise ValueError('Setting ' + namespace + ' doesn\'t exist. Debug: ', setting, name) + raise UnknownNamespaceError(f"Setting {namespace} doesn't exist. Debug: {setting}, {name}") return setting + def setSetting(namespace, data): """ Sets a setting at a namespace with data. @@ -97,11 +123,9 @@ def setSetting(namespace, data): """ names = namespace.split(".") setting = current_config - for name in names: - if name != names[-1]: - if name in setting: - setting = setting[name] - else: - raise ValueError('Setting {} doesn\'t exist. Debug: {}, {}'.format(namespace, setting, name)) + for name in names[:-1]: + if name in setting: + setting = setting[name] else: - setting[name] = data + raise UnknownNamespaceError(f"Setting {namespace} doesn't exist. Debug: {setting}, {name}") + setting[names[-1]] = data diff --git a/runtime-pyside6/LogarithmPlotter/util/debug.py b/runtime-pyside6/LogarithmPlotter/util/debug.py new file mode 100644 index 0000000..c977253 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/util/debug.py @@ -0,0 +1,105 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +from PySide6.QtCore import QtMsgType, qInstallMessageHandler, QMessageLogContext +from math import ceil, log10 +from os import path +from re import compile + +CURRENT_PATH = path.dirname(path.realpath(__file__)) +SOURCEMAP_PATH = path.realpath(f"{CURRENT_PATH}/../qml/eu/ad5001/LogarithmPlotter/Common/index.mjs.map") +SOURCEMAP_INDEX = None +INDEX_REG = compile(r"build\/runtime-pyside6\/LogarithmPlotter\/qml\/eu\/ad5001\/LogarithmPlotter\/Common\/index.mjs:(\d+)") + + +class LOG_COLORS: + GRAY = "\033[90m" + BLUE = "\033[94m" + ORANGE = "\033[38;5;166m" + RED = "\033[38;5;204m" + INVERT = "\033[7m" + RESET_INVERT = "\033[27m" + RESET = "\033[0m" + + +MODES = { + QtMsgType.QtInfoMsg: ['info', LOG_COLORS.BLUE], + QtMsgType.QtWarningMsg: ['warning', LOG_COLORS.ORANGE], + QtMsgType.QtCriticalMsg: ['critical', LOG_COLORS.RED], + QtMsgType.QtFatalMsg: ['critical', LOG_COLORS.RED] +} + +DEFAULT_MODE = ['debug', LOG_COLORS.GRAY] + + +def map_javascript_source(source_file: str, line: str) -> tuple[str, str]: + """ + Maps a line from the compiled javascript to its source. + """ + try: + if SOURCEMAP_INDEX is not None: + token = SOURCEMAP_INDEX.lookup(line, 20) + source_file = token.src.split("../")[-1] + line = token.src_line + except IndexError: + pass # Unable to find source, leave as is. + return source_file, line + + +def create_log_terminal_message(mode: QtMsgType, context: QMessageLogContext, message: str): + """ + Parses a qt log message and returns it. + """ + mode = MODES[mode] if mode in MODES else DEFAULT_MODE + line = context.line + source_file = context.file + # Remove source and line from message + if source_file is not None: + if message.startswith(source_file): + message = message[len(source_file) + 1:] + source_file = "LogarithmPlotter/qml/" + source_file.split("/qml/")[-1] # We're only interested in that part. + if line is not None and message.startswith(str(line)): + line_length = ceil(log10((line + 1) if line > 0 else 1)) + message = message[line_length + 2:] + # Check MJS + if line is not None and source_file is not None and source_file.endswith("index.mjs"): + source_file, line = map_javascript_source(source_file, line) + # Parse message + prefix = f"{LOG_COLORS.INVERT}{mode[1]}[{mode[0].upper()}]{LOG_COLORS.RESET_INVERT}" + message = message + LOG_COLORS.RESET + context = f"{context.function} at {source_file}:{line}" + return f"{prefix} {message} ({context})" + + +def log_qt_debug(mode: QtMsgType, context: QMessageLogContext, message: str): + """ + Parses and renders qt log messages. + """ + print(create_log_terminal_message(mode, context, message)) + + +def setup(): + global SOURCEMAP_INDEX + try: + with open(SOURCEMAP_PATH, "r") as f: + from sourcemap import loads + SOURCEMAP_INDEX = loads(f.read()) + except Exception as e: + log_qt_debug(QtMsgType.QtWarningMsg, QMessageLogContext(), + f"Could not setup JavaScript source mapper in logs: {repr(e)}") + qInstallMessageHandler(log_qt_debug) diff --git a/runtime-pyside6/LogarithmPlotter/util/helper.py b/runtime-pyside6/LogarithmPlotter/util/helper.py new file mode 100644 index 0000000..8adb81e --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/util/helper.py @@ -0,0 +1,164 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from PySide6.QtWidgets import QMessageBox, QApplication +from PySide6.QtCore import QRunnable, QThreadPool, QThread, QObject, Signal, Slot, QCoreApplication, __version__ as PySide6_version +from PySide6.QtQml import QJSValue +from PySide6.QtGui import QImage + +from os import chdir, path +from json import loads +from sys import version as sys_version, argv +from urllib.request import urlopen +from urllib.error import HTTPError, URLError + +from LogarithmPlotter import __VERSION__ +from LogarithmPlotter.util import config +from LogarithmPlotter.util.promise import PyPromise + +SHOW_GUI_MESSAGES = "--test-build" not in argv +CHANGELOG_VERSION = __VERSION__ +CHANGELOG_CACHE_PATH = path.join(path.dirname(path.realpath(__file__)), "CHANGELOG.md") + + +class InvalidFileException(Exception): pass + + +def show_message(msg: str) -> None: + """ + Shows a GUI message if GUI messages are enabled + """ + if SHOW_GUI_MESSAGES: + QMessageBox.warning(None, "LogarithmPlotter", msg) + else: + raise InvalidFileException(msg) + + +def fetch_changelog(): + msg_text = "Unknown changelog error." + try: + # Fetching version + r = urlopen("https://api.ad5001.eu/changelog/logarithmplotter/?version=" + CHANGELOG_VERSION) + lines = r.readlines() + r.close() + msg_text = "".join(map(lambda x: x.decode('utf-8'), lines)).strip() + except HTTPError as e: + msg_text = QCoreApplication.translate("changelog", "Could not fetch changelog: Server error {}.").format( + str(e.code)) + except URLError as e: + msg_text = QCoreApplication.translate("changelog", "Could not fetch update: {}.").format(str(e.reason)) + return msg_text + + +def read_changelog(): + f = open(CHANGELOG_CACHE_PATH, 'r', -1) + data = f.read().strip() + f.close() + return data + + +class Helper(QObject): + def __init__(self, cwd: str, tmpfile: str): + QObject.__init__(self) + self.cwd = cwd + self.tmpfile = tmpfile + + @Slot(str, str) + def write(self, filename, filedata): + chdir(self.cwd) + if path.exists(path.dirname(path.realpath(filename))): + if filename.split(".")[-1] == "lpf": + # Add header to file + filedata = "LPFv1" + filedata + f = open(path.realpath(filename), 'w', -1, 'utf8') + f.write(filedata) + f.close() + chdir(path.dirname(path.realpath(__file__))) + + @Slot(str, result=str) + def load(self, filename): + chdir(self.cwd) + data = '{}' + if path.exists(path.realpath(filename)): + f = open(path.realpath(filename), 'r', -1, 'utf8') + data = f.read() + f.close() + try: + if data[:5] == "LPFv1": + # V1 version of the file + data = data[5:] + elif data[:3] == "LPF": + # More recent version of LogarithmPlotter file, but incompatible with the current format + msg = QCoreApplication.translate('main', + "This file was created by a more recent version of LogarithmPlotter and cannot be backloaded in LogarithmPlotter v{}.\nPlease update LogarithmPlotter to open this file.") + raise InvalidFileException(msg.format(__VERSION__)) + else: + raise InvalidFileException("Invalid LogarithmPlotter file.") + except InvalidFileException as e: # If file can't be loaded + msg = QCoreApplication.translate('main', 'Could not open file "{}":\n{}') + show_message(msg.format(filename, e)) # Cannot parse file + else: + msg = QCoreApplication.translate('main', 'Could not open file: "{}"\nFile does not exist.') + show_message(msg.format(filename)) # Cannot parse file + try: + chdir(path.dirname(path.realpath(__file__))) + except NotADirectoryError as e: + # Triggered on bundled versions of MacOS when it shouldn't. Prevents opening files. + # See more at https://git.ad5001.eu/Ad5001/LogarithmPlotter/issues/1 + pass + return data + + @Slot(result=str) + def gettmpfile(self): + return self.tmpfile + + @Slot() + def copyImageToClipboard(self): + clipboard = QApplication.clipboard() + clipboard.setImage(QImage(self.tmpfile)) + + @Slot(result=str) + def getVersion(self): + return __VERSION__ + + @Slot(str, result=QJSValue) + def getSetting(self, namespace: str) -> QJSValue: + return QJSValue(config.getSetting(namespace)) + + @Slot(str, QJSValue) + def setSetting(self, namespace: str, value: QJSValue): + return config.setSetting(namespace, value.toPrimitive().toVariant()) + + @Slot(result=str) + def getDebugInfos(self): + """ + Returns the version info about Qt, PySide6 & Python + """ + msg = QCoreApplication.translate('main', "Built with PySide6 (Qt) v{} and python v{}") + return msg.format(PySide6_version, sys_version.split("\n")[0]) + + @Slot(result=PyPromise) + def fetchChangelog(self): + """ + Fetches the changelog and returns a Promise. + """ + if path.exists(CHANGELOG_CACHE_PATH): + # We have a cached version of the changelog, for env that don't have access to the internet. + return PyPromise(read_changelog) + else: + # Fetch it from the internet. + return PyPromise(fetch_changelog) diff --git a/runtime-pyside6/LogarithmPlotter/util/js.py b/runtime-pyside6/LogarithmPlotter/util/js.py new file mode 100644 index 0000000..380ab97 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/util/js.py @@ -0,0 +1,110 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from re import Pattern +from typing import Callable +from PySide6.QtCore import QMetaObject, QObject, QDateTime +from PySide6.QtQml import QJSValue + +class InvalidAttributeValueException(Exception): pass +class NotAPrimitiveException(Exception): pass + +class URL: pass + +class PyJSValue: + """ + Wrapper to provide easy way to interact with JavaScript values in Python directly. + """ + + def __init__(self, js_value: QJSValue, parent: QJSValue = None): + self.qjs_value = js_value + self._parent = parent + + def __getattr__(self, item): + return PyJSValue(self.qjs_value.property(item), self.qjs_value) + + def __setattr__(self, key, value): + if key in ['qjs_value', '_parent']: + # Fallback + object.__setattr__(self, key, value) + elif isinstance(value, PyJSValue): + # Set property + self.qjs_value.setProperty(key, value.qjs_value) + elif isinstance(value, QJSValue): + self.qjs_value.setProperty(key, value) + elif type(value) in (int, float, str, bool): + self.qjs_value.setProperty(key, QJSValue(value)) + else: + raise InvalidAttributeValueException(f"Invalid value {value} of type {type(value)} being set to {key}.") + + def __eq__(self, other): + if isinstance(other, PyJSValue): + return self.qjs_value.strictlyEquals(other.qjs_value) + elif isinstance(other, QJSValue): + return self.qjs_value.strictlyEquals(other) + elif type(other) in (int, float, str, bool): + return self.qjs_value.strictlyEquals(QJSValue(other)) + else: + return False + + def __call__(self, *args, **kwargs): + value = None + if self.qjs_value.isCallable(): + if self._parent is None: + value = self.qjs_value.call(args) + else: + value = self.qjs_value.callWithInstance(self._parent, args) + else: + raise InvalidAttributeValueException('Cannot call non-function JS value.') + if isinstance(value, QJSValue): + value = PyJSValue(value) + return value + + def type(self) -> any: + ret = None + matcher = [ + (lambda: self.qjs_value.isArray(), list), + (lambda: self.qjs_value.isBool(), bool), + (lambda: self.qjs_value.isCallable(), Callable), + (lambda: self.qjs_value.isDate(), QDateTime), + (lambda: self.qjs_value.isError(), Exception), + (lambda: self.qjs_value.isNull(), None), + (lambda: self.qjs_value.isNumber(), float), + (lambda: self.qjs_value.isQMetaObject(), QMetaObject), + (lambda: self.qjs_value.isQObject(), QObject), + (lambda: self.qjs_value.isRegExp(), Pattern), + (lambda: self.qjs_value.isUndefined(), None), + (lambda: self.qjs_value.isUrl(), URL), + (lambda: self.qjs_value.isString(), str), + (lambda: self.qjs_value.isObject(), object), + ] + for (test, value) in matcher: + if test(): + ret = value + break + return ret + + def primitive(self): + """ + Returns the pythonic value of the given primitive data. + Raises a NotAPrimitiveException() if this JS value is not a primitive. + """ + if self.type() not in [bool, float, str, None]: + raise NotAPrimitiveException() + return self.qjs_value.toPrimitive().toVariant() + + diff --git a/runtime-pyside6/LogarithmPlotter/util/latex.py b/runtime-pyside6/LogarithmPlotter/util/latex.py new file mode 100644 index 0000000..b446295 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/util/latex.py @@ -0,0 +1,298 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from time import sleep + +from PySide6.QtCore import QObject, Slot, Property, QCoreApplication, Signal +from PySide6.QtGui import QImage, QColor +from PySide6.QtWidgets import QMessageBox + +from os import path, remove, makedirs +from string import Template +from subprocess import Popen, TimeoutExpired, PIPE +from hashlib import sha512 +from shutil import which +from sys import argv + +from LogarithmPlotter.util import config +from LogarithmPlotter.util.promise import PyPromise + +""" +Searches for a valid Latex and DVIPNG (http://savannah.nongnu.org/projects/dvipng/) +installation and collects the binary path in the DVIPNG_PATH variable. +If not found, it will send an alert to the user. +""" +LATEX_PATH = which('latex') +DVIPNG_PATH = which('dvipng') +PACKAGES = ["calligra", "amsfonts", "inputenc"] +SHOW_GUI_MESSAGES = "--test-build" not in argv + +DEFAULT_LATEX_DOC = Template(r""" +\documentclass[]{minimal} +\usepackage[utf8]{inputenc} +\usepackage{calligra} +\usepackage{amsfonts} + +\title{} +\author{} + +\begin{document} + +$$$$ $markup $$$$ + +\end{document} +""") + + +def show_message(msg: str) -> None: + """ + Shows a GUI message if GUI messages are enabled + """ + if SHOW_GUI_MESSAGES: + QMessageBox.warning(None, "LogarithmPlotter - Latex", msg) + + +class MissingPackageException(Exception): pass + + +class RenderError(Exception): pass + + +class Latex(QObject): + """ + Base class to convert Latex equations into PNG images with custom font color and size. + It doesn't have any python dependency, but requires a working latex installation and + dvipng to be installed on the system. + """ + + def __init__(self, cache_path): + QObject.__init__(self) + self.tempdir = path.join(cache_path, "latex") + self.render_pipeline_locks = {} + makedirs(self.tempdir, exist_ok=True) + + @Property(bool) + def latexSupported(self) -> bool: + return LATEX_PATH is not None and DVIPNG_PATH is not None + + @Property(bool) + def supportsAsyncRender(self) -> bool: + return config.getSetting("enable_latex_threaded") + + @Slot(result=bool) + def checkLatexInstallation(self) -> bool: + """ + Checks if the current latex installation is valid. + """ + valid_install = True + if LATEX_PATH is None: + print("No Latex installation found.") + msg = QCoreApplication.translate("latex", + "No Latex installation found.\nIf you already have a latex distribution installed, make sure it's installed on your path.\nOtherwise, you can download a Latex distribution like TeX Live at https://tug.org/texlive/.") + show_message(msg) + valid_install = False + elif DVIPNG_PATH is None: + print("DVIPNG not found.") + msg = QCoreApplication.translate("latex", + "DVIPNG was not found. Make sure you include it from your Latex distribution.") + show_message(msg) + valid_install = False + else: + try: + self.renderSync("", 14, QColor(0, 0, 0, 255)) + except MissingPackageException: + valid_install = False # Should have sent an error message if failed to render + return valid_install + + def lock(self, markup_hash, render_hash, promise): + """ + Locks the render pipeline for a given markup hash and render hash. + """ + # print("Locking", markup_hash, render_hash) + if markup_hash not in self.render_pipeline_locks: + self.render_pipeline_locks[markup_hash] = promise + self.render_pipeline_locks[render_hash] = promise + + + def release_lock(self, markup_hash, render_hash): + """ + Release locks on the markup and render hashes. + """ + # print("Releasing", markup_hash, render_hash) + if markup_hash in self.render_pipeline_locks: + del self.render_pipeline_locks[markup_hash] + del self.render_pipeline_locks[render_hash] + + @Slot(str, float, QColor, result=PyPromise) + def renderAsync(self, latex_markup: str, font_size: float, color: QColor) -> PyPromise: + """ + Prepares and renders a latex string into a png file asynchronously. + """ + markup_hash, render_hash, export_path = self.create_export_path(latex_markup, font_size, color) + promise = None + if render_hash in self.render_pipeline_locks: + # A PyPromise for this specific render is already running. + # print("Already running render of", latex_markup) + promise = self.render_pipeline_locks[render_hash] + elif markup_hash in self.render_pipeline_locks: + # A PyPromise with the same markup, but not the same color or font size is already running. + # print("Chaining render of", latex_markup) + existing_promise = self.render_pipeline_locks[markup_hash] + promise = self._create_async_promise(latex_markup, font_size, color) + existing_promise.then(promise.start) + else: + # No such PyPromise is running. + promise = self._create_async_promise(latex_markup, font_size, color) + promise.start() + return promise + + def _create_async_promise(self, latex_markup: str, font_size: float, color: QColor) -> PyPromise: + """ + Createsa PyPromise to render a latex string into a PNG file. + Internal method. Use renderAsync that makes use of locks. + """ + markup_hash, render_hash, export_path = self.create_export_path(latex_markup, font_size, color) + promise = PyPromise(self.renderSync, [latex_markup, font_size, color], start_automatically=False) + self.lock(markup_hash, render_hash, promise) + # Make the lock release at the end. + def unlock(data, markup_hash=markup_hash, render_hash=render_hash): + self.release_lock(markup_hash, render_hash) + promise.then(unlock, unlock) + return promise + + @Slot(str, float, QColor, result=str) + def renderSync(self, latex_markup: str, font_size: float, color: QColor) -> str: + """ + Prepares and renders a latex string into a png file. + """ + markup_hash, render_hash, export_path = self.create_export_path(latex_markup, font_size, color) + if self.latexSupported and not path.exists(export_path + ".png"): + # Generating file + latex_path = path.join(self.tempdir, str(markup_hash)) + # If the formula is just recolored or the font is just changed, no need to recreate the DVI. + if not path.exists(latex_path + ".dvi"): + self.create_latex_doc(latex_path, latex_markup) + self.convert_latex_to_dvi(latex_path) + self.cleanup(latex_path) + # Creating four pictures of different sizes to better handle dpi. + self.convert_dvi_to_png(latex_path, export_path, font_size, color) + # self.convert_dvi_to_png(latex_path, export_path+"@2", font_size*2, color) + # self.convert_dvi_to_png(latex_path, export_path+"@3", font_size*3, color) + # self.convert_dvi_to_png(latex_path, export_path+"@4", font_size*4, color) + img = QImage(export_path) + # Small hack, not very optimized since we load the image twice, but you can't pass a QImage to QML and expect it to be loaded + return f'{export_path}.png,{img.width()},{img.height()}' + + @Slot(str, float, QColor, result=str) + def findPrerendered(self, latex_markup: str, font_size: float, color: QColor) -> str: + """ + Finds a prerendered image and returns its data if possible, and an empty string if not. + """ + markup_hash, render_hash, export_path = self.create_export_path(latex_markup, font_size, color) + data = "" + if path.exists(export_path + ".png"): + img = QImage(export_path) + data = f'{export_path}.png,{img.width()},{img.height()}' + return data + + def create_export_path(self, latex_markup: str, font_size: float, color: QColor): + """ + Standardizes export path for renders. + Markup hash is unique for the markup + Render hash is unique for the markup, the font size and the color. + """ + markup_hash = "render" + str(sha512(latex_markup.encode()).hexdigest()) + render_hash = f'{markup_hash}_{int(font_size)}_{color.rgb()}' + export_path = path.join(self.tempdir, render_hash) + return markup_hash, render_hash, export_path + + def create_latex_doc(self, export_path: str, latex_markup: str): + """ + Creates a temporary latex document with base file_hash as file name and a given expression markup latex_markup. + """ + f = open(export_path + ".tex", 'w') + f.write(DEFAULT_LATEX_DOC.substitute(markup=latex_markup)) + f.close() + + def convert_latex_to_dvi(self, export_path: str): + """ + Converts a TEX file to a DVI file. + """ + self.run([ + LATEX_PATH, + export_path + ".tex" + ]) + + def convert_dvi_to_png(self, dvi_path: str, export_path: str, font_size: float, color: QColor): + """ + Converts a DVI file to a PNG file. + Documentation: https://linux.die.net/man/1/dvipng + """ + fg = color.convertTo(QColor.Rgb) + fg = f'rgb {fg.redF()} {fg.greenF()} {fg.blueF()}' + depth = int(font_size * 72.27 / 100) * 10 + self.run([ + DVIPNG_PATH, + '-T', 'tight', # Make sure image borders are as tight around the equation as possible to avoid blank space. + '--truecolor', # Make sure it's rendered in 24 bit colors. + '-D', f'{depth}', # Depth of the image + '-bg', 'Transparent', # Transparent background + '-fg', f'{fg}', # Foreground of the wanted color. + f'{dvi_path}.dvi', # Input file + '-o', f'{export_path}.png', # Output file + ]) + + def run(self, process: list): + """ + Runs a subprocess and handles exceptions and messages them to the user. + """ + cmd = " ".join(process) + proc = Popen(process, stdout=PIPE, stderr=PIPE, cwd=self.tempdir) + try: + out, err = proc.communicate(timeout=2) # 2 seconds is already FAR too long. + if proc.returncode != 0: + # Process errored + output = str(out, 'utf8') + "\n" + str(err, 'utf8') + msg = QCoreApplication.translate("latex", + "An exception occured within the creation of the latex formula.\nProcess '{}' ended with a non-zero return code {}:\n\n{}\nPlease make sure your latex installation is correct and report a bug if so.") + show_message(msg.format(cmd, proc.returncode, output)) + raise RenderError( + f"{cmd} process exited with return code {str(proc.returncode)}:\n{str(out, 'utf8')}\n{str(err, 'utf8')}") + except TimeoutExpired: + # Process timed out + proc.kill() + out, err = proc.communicate() + output = str(out, 'utf8') + "\n" + str(err, 'utf8') + if 'not found' in output: + for pkg in PACKAGES: + if f'{pkg}.sty' in output: + # Package missing. + msg = QCoreApplication.translate("latex", + "Your LaTeX installation does not include some required packages:\n\n- {} (https://ctan.org/pkg/{})\n\nMake sure said package is installed, or disable the LaTeX rendering in LogarithmPlotter.") + show_message(msg.format(pkg, pkg)) + raise MissingPackageException("Latex: Missing package " + pkg) + msg = QCoreApplication.translate("latex", + "An exception occured within the creation of the latex formula.\nProcess '{}' took too long to finish:\n{}\nPlease make sure your latex installation is correct and report a bug if so.") + show_message(msg.format(cmd, output)) + raise RenderError(f"{cmd} process timed out:\n{output}") + + def cleanup(self, export_path): + """ + Removes auxiliary, logs and Tex temporary files. + """ + for i in [".tex", ".aux", ".log"]: + remove(export_path + i) diff --git a/LogarithmPlotter/util/native.py b/runtime-pyside6/LogarithmPlotter/util/native.py similarity index 77% rename from LogarithmPlotter/util/native.py rename to runtime-pyside6/LogarithmPlotter/util/native.py index 01d9022..3adf153 100644 --- a/LogarithmPlotter/util/native.py +++ b/runtime-pyside6/LogarithmPlotter/util/native.py @@ -1,6 +1,6 @@ """ * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -20,31 +20,36 @@ from PySide6.QtCore import QObject, QEvent + # On macOS, opening a file through finder can only be fetched through the -# QFileOpenEvent and NOT throught command line parameters. +# QFileOpenEvent and NOT through command line parameters. class MacOSFileOpenHandler(QObject): def __init__(self): - self.initilized = False - self.mainwindow = None + self.initialized = False + self.io_module = None self.opened_file = "" QObject.__init__(self) - - def init_graphics(self, mainwindow): - self.mainwindow = mainwindow - self.initilized = True + + def init_io(self, io_modules): + self.io_module = io_modules + self.initialized = True if self.opened_file != "": self.open_file() - + def open_file(self): - self.mainwindow.loadDiagram(self.opened_file) - + self.io_module.loadDiagram(self.opened_file) + def eventFilter(self, obj, event): if event.type() == QEvent.FileOpen: - print("Got file", event.file(), self.initilized) + print("Got file", event.file(), self.initialized) self.opened_file = event.file() - if self.initilized: + if self.initialized: self.open_file() return True else: # standard event processing return QObject.eventFilter(self, obj, event) + + + + diff --git a/runtime-pyside6/LogarithmPlotter/util/promise.py b/runtime-pyside6/LogarithmPlotter/util/promise.py new file mode 100644 index 0000000..8e17651 --- /dev/null +++ b/runtime-pyside6/LogarithmPlotter/util/promise.py @@ -0,0 +1,175 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from typing import Callable + +from PySide6.QtCore import QRunnable, Signal, Property, QObject, Slot, QThreadPool +from PySide6.QtQml import QJSValue + +from LogarithmPlotter.util.js import PyJSValue + +NO_RETURN = [None, QJSValue.SpecialValue.UndefinedValue] + + +def check_callable(function: Callable|QJSValue) -> Callable|None: + """ + Checks if the given function can be called (either a python callable + or a QJSValue function), and returns the object that can be called directly. + Returns None if not a function. + """ + if isinstance(function, QJSValue) and function.isCallable(): + return PyJSValue(function) + elif callable(function): + return function + return None + +class InvalidReturnValue(Exception): pass + + +class PyPromiseRunner(QRunnable): + """ + QRunnable for running Promises in different threads. + """ + def __init__(self, runner, promise, args): + QRunnable.__init__(self) + self.runner = runner + self.promise = promise + self.args = args + + def run(self): + try: + data = self.runner(*self.args) + if type(data) in [int, str, float, bool]: + data = QJSValue(data) + elif data is None: + data = QJSValue.SpecialValue.UndefinedValue + elif isinstance(data, QJSValue): + data = data + elif isinstance(data, PyJSValue): + data = data.qjs_value + else: + raise InvalidReturnValue("Must return either a primitive, a JS Value, or None.") + self.promise.fulfilled.emit(data) + except Exception as e: + try: + self.promise.rejected.emit(repr(e)) + except RuntimeError as e2: + # Happens when the PyPromise has already been garbage collected. + # In other words, nothing to report to nowhere. + pass + + +class PyPromise(QObject): + """ + Threaded A+/Promise implementation meant to interface between Python and Javascript easily. + Runs to_run in another thread, and calls fulfilled (populated by then) with its return value. + """ + fulfilled = Signal(QJSValue) + rejected = Signal(str) + + def __init__(self, to_run: Callable|QJSValue, args=[], start_automatically=True): + QObject.__init__(self) + self._fulfills = [] + self._rejects = [] + self._state = "pending" + self._started = False + self.fulfilled.connect(self._fulfill) + self.rejected.connect(self._reject) + to_run = check_callable(to_run) + if to_run is None: + raise ValueError("New PyPromise created with invalid function") + self._runner = PyPromiseRunner(to_run, self, args) + if start_automatically: + self.start() + + @Slot() + def start(self, *args, **kwargs): + """ + Starts the thread that will run the promise. + """ + if not self._started: # Avoid getting started twice. + print("Starting", self._runner.args) + QThreadPool.globalInstance().start(self._runner) + self._started = True + + @Property(str) + def state(self): + return self._state + + @Slot(QJSValue, result=QObject) + @Slot(QJSValue, QJSValue, result=QObject) + def then(self, on_fulfill: QJSValue | Callable, on_reject: QJSValue | Callable = None): + """ + Adds listeners for both fulfilment and catching errors of the Promise. + """ + on_fulfill = check_callable(on_fulfill) + on_reject = check_callable(on_reject) + self._fulfills.append(on_fulfill) + self._rejects.append(on_reject) + return self + + def calls_upon_fulfillment(self, function: Callable | QJSValue) -> bool: + """ + Returns True if the given function will be callback upon the promise fulfillment. + False otherwise. + """ + return self._calls_in(function, self._fulfills) + + def calls_upon_rejection(self, function: Callable | QJSValue) -> bool: + """ + Returns True if the given function will be callback upon the promise rejection. + False otherwise. + """ + return self._calls_in(function, self._rejects) + + def _calls_in(self, function: Callable | QJSValue, within: list) -> bool: + """ + Returns True if the given function resides in the given within list, False otherwise. + Internal method of calls_upon_fulfill + """ + function = check_callable(function) + ret = False + if isinstance(function, PyJSValue): + found = next((f for f in within if f.qjs_value == function.qjs_value), None) + ret = found is not None + elif callable(function): + found = next((f for f in within if f == function), None) + ret = found is not None + return ret + + @Slot(QJSValue) + @Slot(QObject) + def _fulfill(self, data): + self._state = "fulfilled" + print("Finished", self._runner.args) + for i in range(len(self._fulfills)): + try: + result = self._fulfills[i](data) + result = result.qjs_value if isinstance(result, PyJSValue) else result + data = result if result not in NO_RETURN else data # Forward data. + except Exception as e: + self._reject(repr(e), start_at=i) + break + + @Slot(QJSValue) + @Slot(str) + def _reject(self, error, start_at=0): + self._state = "rejected" + for i in range(start_at, len(self._rejects)): + result = self._rejects[i](error) + result = result.qjs_value if isinstance(result, PyJSValue) else result + error = result if result not in NO_RETURN else error # Forward data. diff --git a/LogarithmPlotter/util/update.py b/runtime-pyside6/LogarithmPlotter/util/update.py similarity index 75% rename from LogarithmPlotter/util/update.py rename to runtime-pyside6/LogarithmPlotter/util/update.py index 25400ce..e18b93e 100644 --- a/LogarithmPlotter/util/update.py +++ b/runtime-pyside6/LogarithmPlotter/util/update.py @@ -1,6 +1,6 @@ """ * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 + * Copyright (C) 2021-2025 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 @@ -21,9 +21,11 @@ from urllib.request import urlopen from urllib.error import HTTPError, URLError from sys import argv + class UpdateInformation(QObject): got_update_info = Signal(bool, str, bool) + class UpdateCheckerRunnable(QRunnable): def __init__(self, current_version, callback): QRunnable.__init__(self) @@ -40,44 +42,50 @@ class UpdateCheckerRunnable(QRunnable): lines = r.readlines() r.close() # Parsing version - version = "".join(map(chr, lines[0])).strip() # Converts byte to string. + version = "".join(map(chr, lines[0])).strip() # Converts byte to string. version_tuple = version.split(".") is_version_newer = False if "dev" in self.current_version: # We're on a dev version - current_version_tuple = self.current_version.split(".")[:-1] # Removing the dev0+git bit. - is_version_newer = version_tuple >= current_version_tuple # If equals, that means we got out of testing phase. + current_version_tuple = self.current_version.split(".")[:-1] # Removing the dev0+git bit. + is_version_newer = version_tuple >= current_version_tuple # If equals, that means we got out of testing phase. else: current_version_tuple = self.current_version.split(".") is_version_newer = version_tuple > current_version_tuple if is_version_newer: - msg_text = QCoreApplication.translate("update","An update for LogarithPlotter (v{}) is available.").format(version) + msg_text = QCoreApplication.translate("update", "An update for LogarithmPlotter (v{}) is available.") + msg_text = msg_text.format(version) update_available = True else: show_alert = False - msg_text = QCoreApplication.translate("update","No update available.") - + msg_text = QCoreApplication.translate("update", "No update available.") + except HTTPError as e: - msg_text = QCoreApplication.translate("update","Could not fetch update information: Server error {}.").format(str(e.code)) + msg_text = QCoreApplication.translate("update", + "Could not fetch update information: Server error {}.") + msg_text = msg_text.format(str(e.code)) except URLError as e: - msg_text = QCoreApplication.translate("update","Could not fetch update information: {}.").format(str(e.reason)) - self.callback.got_update_info.emit(show_alert, msg_text,update_available) + msg_text = QCoreApplication.translate("update", "Could not fetch update information: {}.") + msg_text = msg_text.format(str(e.reason)) + self.callback.got_update_info.emit(show_alert, msg_text, update_available) + def check_for_updates(current_version, window): """ Checks for updates in the background, and sends an alert with information. """ if "--no-check-for-updates" in argv: - return # + return + def cb(show_alert, msg_text, update_available): - pass if show_alert: window.showAlert(msg_text) if update_available: window.showUpdateMenu() - + update_info = UpdateInformation() update_info.got_update_info.connect(cb) - + runnable = UpdateCheckerRunnable(current_version, update_info) QThreadPool.globalInstance().start(runnable) + return update_info diff --git a/MANIFEST.in b/runtime-pyside6/MANIFEST.in similarity index 100% rename from MANIFEST.in rename to runtime-pyside6/MANIFEST.in diff --git a/runtime-pyside6/poetry.lock b/runtime-pyside6/poetry.lock new file mode 100644 index 0000000..250fc64 --- /dev/null +++ b/runtime-pyside6/poetry.lock @@ -0,0 +1,442 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "altgraph" +version = "0.17.4" +description = "Python graph (network) package" +optional = false +python-versions = "*" +files = [ + {file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"}, + {file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"}, + {file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"}, + {file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"}, + {file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"}, + {file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"}, + {file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"}, + {file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"}, + {file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"}, + {file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"}, + {file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"}, + {file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"}, + {file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"}, + {file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"}, + {file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"}, + {file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"}, + {file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"}, + {file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"}, + {file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"}, + {file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"}, + {file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"}, + {file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"}, + {file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"}, + {file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"}, + {file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"}, + {file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"}, + {file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"}, + {file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"}, + {file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"}, + {file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"}, + {file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"}, + {file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"}, + {file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"}, + {file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "macholib" +version = "1.16.3" +description = "Mach-O header analysis and editing" +optional = false +python-versions = "*" +files = [ + {file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"}, + {file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"}, +] + +[package.dependencies] +altgraph = ">=0.17" + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pefile" +version = "2023.2.7" +description = "Python PE parsing module" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"}, + {file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pyinstaller" +version = "6.11.0" +description = "PyInstaller bundles a Python application and all its dependencies into a single package." +optional = false +python-versions = "<3.14,>=3.8" +files = [ + {file = "pyinstaller-6.11.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:6fd68a3c1207635c49326c54881b89d5c3bd9ba061bbc9daa58c0902db1be39e"}, + {file = "pyinstaller-6.11.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:eddd53f231e51adc65088eac4f40057ca803a990239828d4a9229407fb866239"}, + {file = "pyinstaller-6.11.0-py3-none-manylinux2014_i686.whl", hash = "sha256:e6d229009e815542833fe00332b589aa6984a06f794dc16f2ce1acab1c567590"}, + {file = "pyinstaller-6.11.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:7d2cd2ebdcd6860f8a4abe2977264a7b6d260a7147047008971c7cfc66a656a4"}, + {file = "pyinstaller-6.11.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:d9ec6d4398b4eebc1d4c00437716264ba8406bc2746f594e253070a82378a584"}, + {file = "pyinstaller-6.11.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:04f71828aa9531ab18c9656985c1f09b83d10332c73a1f4a113a48b491906955"}, + {file = "pyinstaller-6.11.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:a843d470768d68b05684ccf4860c45b2eb13727f41667c0b2cd8f57ae231bd18"}, + {file = "pyinstaller-6.11.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:963dedc1f37144a4385f58f7f65f1c69c004a67faae522a2085b5ddb230c908b"}, + {file = "pyinstaller-6.11.0-py3-none-win32.whl", hash = "sha256:c71024c8a19c7b221b9152b2baff4c3ba849cada68dcdd34382ba09f0107451f"}, + {file = "pyinstaller-6.11.0-py3-none-win_amd64.whl", hash = "sha256:0e229610c22b96d741d905706f9496af472c1a9216a118988f393c98ecc3f51f"}, + {file = "pyinstaller-6.11.0-py3-none-win_arm64.whl", hash = "sha256:a5f716bb507517912fda39d109dead91fc0dd2e7b2859562522b63c61aa21676"}, + {file = "pyinstaller-6.11.0.tar.gz", hash = "sha256:cb4d433a3db30d9d17cf5f2cf7bb4df80a788d493c1d67dd822dc5791d9864af"}, +] + +[package.dependencies] +altgraph = "*" +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""} +packaging = ">=22.0" +pefile = {version = ">=2022.5.30,<2024.8.26 || >2024.8.26", markers = "sys_platform == \"win32\""} +pyinstaller-hooks-contrib = ">=2024.8" +pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""} +setuptools = ">=42.0.0" + +[package.extras] +completion = ["argcomplete"] +hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2024.9" +description = "Community maintained hooks for PyInstaller" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyinstaller_hooks_contrib-2024.9-py3-none-any.whl", hash = "sha256:1ddf9ba21d586afa84e505bb5c65fca10b22500bf3fdb89ee2965b99da53b891"}, + {file = "pyinstaller_hooks_contrib-2024.9.tar.gz", hash = "sha256:4793869f370d1dc4806c101efd2890e3c3e703467d8d27bb5a3db005ebfb008d"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +packaging = ">=22.0" +setuptools = ">=42.0.0" + +[[package]] +name = "pyside6-addons" +version = "6.8.0.2" +description = "Python bindings for the Qt cross-platform application and UI framework (Addons)" +optional = false +python-versions = "<3.14,>=3.9" +files = [ + {file = "PySide6_Addons-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:30c9ca570dd18ffbfd34ee95e0a319c34313a80425c4011d6ccc9f4cca0dc4c8"}, + {file = "PySide6_Addons-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:754a9822ab2dc313f9998edef69d8a12bc9fd61727543f8d30806ed272ae1e52"}, + {file = "PySide6_Addons-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:553f3fa412f423929b5cd8b7d43fd5f02161851f10a438174a198b0f1a044df7"}, + {file = "PySide6_Addons-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:ae4377a3e10fe720a9119677b31d8de13e2a5221c06b332df045af002f5f4c3d"}, +] + +[package.dependencies] +PySide6-Essentials = "6.8.0.2" +shiboken6 = "6.8.0.2" + +[[package]] +name = "pyside6-essentials" +version = "6.8.0.2" +description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)" +optional = false +python-versions = "<3.14,>=3.9" +files = [ + {file = "PySide6_Essentials-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:3df4ed75bbb74d74ac338b330819b1a272e7f5cec206765c7176a197c8bc9c79"}, + {file = "PySide6_Essentials-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7df6d6c1da4858dbdea77c74d7270d9c68e8d1bbe3362892abd1a5ade3815a50"}, + {file = "PySide6_Essentials-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:cf490145d18812a6cff48b0b0afb0bfaf7066744bfbd09eb071c3323f1d6d00d"}, + {file = "PySide6_Essentials-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:d2f029b8c9f0106f57b26aa8c435435d7f509c80525075343e07177b283f862e"}, +] + +[package.dependencies] +shiboken6 = "6.8.0.2" + +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-qt" +version = "4.4.0" +description = "pytest support for PyQt and PySide applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-qt-4.4.0.tar.gz", hash = "sha256:76896142a940a4285339008d6928a36d4be74afec7e634577e842c9cc5c56844"}, + {file = "pytest_qt-4.4.0-py3-none-any.whl", hash = "sha256:001ed2f8641764b394cf286dc8a4203e40eaf9fff75bf0bfe5103f7f8d0c591d"}, +] + +[package.dependencies] +pluggy = ">=1.1" +pytest = "*" + +[package.extras] +dev = ["pre-commit", "tox"] +doc = ["sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"}, + {file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"}, +] + +[[package]] +name = "setuptools" +version = "75.2.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, + {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] + +[[package]] +name = "shiboken6" +version = "6.8.0.2" +description = "Python/C++ bindings helper module" +optional = false +python-versions = "<3.14,>=3.9" +files = [ + {file = "shiboken6-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:9019e1fcfeed8bb350222e981748ef05a2fec11e31ddf616657be702f0b7a468"}, + {file = "shiboken6-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fa7d411c3c67b4296847b3f5f572268e219d947d029ff9d8bce72fe6982d92bc"}, + {file = "shiboken6-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:1aaa8b7f9138818322ef029b2c487d1c6e00dc3f53084e62e1d11bdea47e47c2"}, + {file = "shiboken6-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:b11e750e696bb565d897e0f5836710edfb86bd355f87b09988bd31b2aad404d3"}, +] + +[[package]] +name = "sourcemap" +version = "0.2.1" +description = "Parse JavaScript source maps." +optional = false +python-versions = "*" +files = [ + {file = "sourcemap-0.2.1-py2.py3-none-any.whl", hash = "sha256:c448a8c48f9482e522e4582106b0c641a83b5dbc7f13927b178848e3ea20967b"}, + {file = "sourcemap-0.2.1.tar.gz", hash = "sha256:be00a90185e7a16b87bbe62a68ffd5e38bc438ef4700806d9b90e44d8027787c"}, +] + +[[package]] +name = "stdeb" +version = "0.10.0" +description = "Python to Debian source package conversion utility" +optional = false +python-versions = "*" +files = [ + {file = "stdeb-0.10.0.tar.gz", hash = "sha256:08c22c9c03b28a140fe3ec5064b53a5288279f22e596ca06b0be698d50c93cf2"}, +] + +[[package]] +name = "tomli" +version = "2.0.2" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, +] + +[[package]] +name = "zipp" +version = "3.20.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.9,<3.13" +content-hash = "fad810a5ba9b4cb5ab759c9b5641ccba2b735e12064e510c0bfe0f4766c576f1" diff --git a/runtime-pyside6/pyproject.toml b/runtime-pyside6/pyproject.toml new file mode 100644 index 0000000..cafc0cb --- /dev/null +++ b/runtime-pyside6/pyproject.toml @@ -0,0 +1,27 @@ +[tool.poetry] +name = "LogarithmPlotter" +version = "0.6.0" +description = "Create and edit Bode plots" +authors = ["Ad5001 "] +license = "GPL-3.0-or-later" +readme = "README.md" +package-mode = false + +[tool.poetry.dependencies] +python = ">=3.9,<3.13" +PySide6-Essentials = "^6.8" +PySide6-Addons = "^6.8" + +[tool.poetry.group.packaging.dependencies] +pyinstaller = "^6.10.0" +stdeb = "^0.10.0" +setuptools = "^75.1.0" + +[tool.poetry.group.test.dependencies] +pytest = "^8.3.3" +pytest-cov = "^5.0.0" +pytest-qt = "^4.4.0" + +[tool.poetry.group.dev.dependencies] +sourcemap = "^0.2.1" + diff --git a/runtime-pyside6/setup.py b/runtime-pyside6/setup.py new file mode 100644 index 0000000..4100042 --- /dev/null +++ b/runtime-pyside6/setup.py @@ -0,0 +1,156 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import setuptools +import os +import sys +from shutil import copyfile + +print(sys.argv) + +current_dir = os.path.realpath(os.path.dirname(os.path.realpath(__file__))) + +# Check where to install by default +# if "PREFIX" not in os.environ and sys.platform == 'linux': +# from getopt import getopt +# optlist, args = getopt(sys.argv, '', ['prefix=', 'root=']) +# for arg,value in optlist: +# if arg == "prefix" or arg == "root": +# os.environ["PREFIX"] = value +# if "PREFIX" not in os.environ and sys.platform == 'linux': +# if "XDG_DATA_HOME" in os.environ: +# os.environ["PREFIX"] = os.environ["XDG_DATA_HOME"] +# else: +# try: +# # Checking if we have permission to write to root. +# from os import makedirs, rmdir +# makedirs("/usr/share/applications/test") +# rmdir("/usr/share/applications/test") +# os.environ["PREFIX"] = "/usr/share" +# except: +# if ".pybuild" in os.environ["HOME"]: # Launchpad building. +# os.environ["PREFIX"] = "share" +# else: +# os.environ["PREFIX"] = os.environ["HOME"] + "/.local/share" + +from LogarithmPlotter import __VERSION__ as pkg_version + +if "--remove-git-version" in sys.argv: + pkg_version = pkg_version.split(".dev0")[0] + sys.argv.remove("--remove-git-version") + +CLASSIFIERS = """ +Environment :: Graphic +Environment :: X11 Applications :: Qt +License :: OSI Approved :: GNU General Public License v3 (GPLv3) +Natural Language :: English +Development Status :: 3 - Alpha +Operating System :: MacOS :: MacOS X +Operating System :: Microsoft :: Windows +Operating System :: POSIX +Operating System :: POSIX :: BSD +Operating System :: POSIX :: Linux +Programming Language :: Python :: 3.8 +Programming Language :: Python :: 3.9 +Programming Language :: Python :: 3.10 +Programming Language :: Python :: 3.11 +Programming Language :: Python :: 3.12 +Programming Language :: Python :: Implementation :: CPython +Topic :: Utilities +Topic :: Scientific/Engineering +""".strip().splitlines() + +def read_file(file_name): + f = open(file_name, 'r', -1) + data = f.read() + f.close() + return data + +def package_data(): + pkg_data = ["logarithmplotter.svg"] + for d,folders,files in os.walk("LogarithmPlotter/qml"): + d = d[17:] + pkg_data += [os.path.join(d, f) for f in files] + for d,folders,files in os.walk("LogarithmPlotter/i18n"): + d = d[17:] + pkg_data += [os.path.join(d, f) for f in files] + if "FLATPAK_INSTALL" in os.environ: + pkg_data += ["CHANGELOG.md"] + + return pkg_data + +data_files = [] +if sys.platform == 'linux': + data_files.append(('share/applications/', ['assets/native/linux/logarithmplotter.desktop'])) + data_files.append(('share/mime/packages/', ['assets/native/linux/x-logarithm-plot.xml'])) + data_files.append(('share/icons/hicolor/scalable/mimetypes/', ['assets/native/linux/application-x-logarithm-plot.svg'])) + data_files.append(('share/icons/hicolor/scalable/apps/', ['assets/logarithmplotter.svg'])) + # data_files.append((os.environ["PREFIX"] + '/applications/', ['linux/logarithmplotter.desktop'])) + # data_files.append((os.environ["PREFIX"] + '/mime/packages/', ['linux/x-logarithm-plot.xml'])) + # data_files.append((os.environ["PREFIX"] + '/icons/hicolor/scalable/mimetypes/', ['linux/application-x-logarithm-plot.svg'])) + # data_files.append((os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/', ['logplotter.svg'])) + # if len(sys.argv) > 1: + # if sys.argv[1] == "install": + # os.makedirs(os.environ["PREFIX"] + '/applications/', exist_ok=True) + # os.makedirs(os.environ["PREFIX"] + '/mime/packages/', exist_ok=True) + # os.makedirs(os.environ["PREFIX"] + '/icons/hicolor/scalable/mimetypes/', exist_ok=True) + # os.makedirs(os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/', exist_ok=True) + # os.makedirs(os.environ["PREFIX"] + '/metainfo/', exist_ok=True) + # copyfile(current_dir + '/linux/x-logarithm-plot.xml', os.environ["PREFIX"] + '/mime/packages/x-logarithm-plot.xml') + # copyfile(current_dir + '/linux/application-x-logarithm-plot.svg', + # os.environ["PREFIX"] + '/icons/hicolor/scalable/mimetypes/application-x-logarithm-plot.svg') + # copyfile(current_dir + '/logplotter.svg', os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/logplotter.svg') + # elif sys.argv[1] == "uninstall": + # os.remove(os.environ["PREFIX"] + '/applications/logarithmplotter.desktop') + # os.remove(os.environ["PREFIX"] + '/mime/packages/x-logarithm-plot.xml') + # os.remove(os.environ["PREFIX"] + '/icons/hicolor/scalable/mimetypes/application-x-logarithm-plot.svg') + # os.remove(os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/logplotter.svg') + +setuptools.setup( + install_requires=([] if "FLATPAK_INSTALL" in os.environ else ["PySide6-Essentials"]), + python_requires='>=3.9', + + name='logarithmplotter', + version=pkg_version, + + description='Create and edit Bode plots.', + long_description=read_file("README.md"), + keywords='logarithm plotter graph creator bode diagram', + + author='Ad5001', + author_email='mail@ad5001.eu', + + license=('GPLv3'), + url='https://apps.ad5001.eu/logarithmplotter/', + + classifiers=CLASSIFIERS, + zip_safe=False, + packages=["LogarithmPlotter", "LogarithmPlotter.util"], + + package_data={ + 'LogarithmPlotter':package_data(), + }, + include_package_data=True, + data_files = data_files, + entry_points={ + 'console_scripts': [ + 'logarithmplotter = LogarithmPlotter.logarithmplotter:run', + ], + } +) + diff --git a/runtime-pyside6/tests/__init__.py b/runtime-pyside6/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/runtime-pyside6/tests/globals.py b/runtime-pyside6/tests/globals.py new file mode 100644 index 0000000..67a1356 --- /dev/null +++ b/runtime-pyside6/tests/globals.py @@ -0,0 +1,20 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from LogarithmPlotter.logarithmplotter import create_qapp + +app = create_qapp() \ No newline at end of file diff --git a/runtime-pyside6/tests/plugins/__init__.py b/runtime-pyside6/tests/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/runtime-pyside6/tests/plugins/natural/__init__.py b/runtime-pyside6/tests/plugins/natural/__init__.py new file mode 100644 index 0000000..180969f --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/__init__.py @@ -0,0 +1,22 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +from .spy import Spy +from .that import that +from .interfaces.base import Assertion + diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/__init__.py b/runtime-pyside6/tests/plugins/natural/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/assertion.py b/runtime-pyside6/tests/plugins/natural/interfaces/assertion.py new file mode 100644 index 0000000..b81d0e5 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/assertion.py @@ -0,0 +1,39 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 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 . +""" + +class Assertion(Exception): + def __init__(self, assertion: bool, message: str, invert: bool): + self.assertion = assertion + self.message = message + self.invert = invert + + def _invert_message(self): + for verb in ('is', 'was', 'has', 'have'): + for negative in ("n't", ' not', ' never', ' no'): + self.message = self.message.replace(f"{verb}{negative}", verb.upper()) + + def __str__(self): + return self.message + + def __bool__(self): + if not self.invert and not self.assertion: + raise self + if self.invert and self.assertion: + self._invert_message() + raise self + return True # Raises otherwise. \ No newline at end of file diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/base.py b/runtime-pyside6/tests/plugins/natural/interfaces/base.py new file mode 100644 index 0000000..c96c146 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/base.py @@ -0,0 +1,171 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from typing import Self, Callable, Any + +from .assertion import Assertion +from .utils import repr_ + + +class AssertionInterface: + """ + Most basic assertion interface. + You probably want to use BaseAssertionInterface + """ + + def __init__(self, value, parent: Self = None): + self._value = value + self._parent = parent + if parent is None: + self.__not = False + + @property + def _not(self) -> bool: + """ + Internal state of whether the expression was negated. + Use "not_" to set it. + :return: + """ + return self.__not if self._parent is None else self._parent._not + + @_not.setter + def _not(self, value: bool): + if self._not is True: + raise RuntimeError("Cannot call is_not or was_not twice in the same statement.") + if self._parent is None: + self.__not = True + else: + self._parent._not = True + + def instance_of(self, type_: type) -> Assertion: + """ + Checks if the current value is equal to the provided value + """ + value_type_name = type(self._value).__name__ + if not isinstance(type_, type): + raise RuntimeError("Provided 'type' provided is not a class.") + return Assertion( + isinstance(self._value, type_), + f"The value ({value_type_name} {repr_(self._value)}) is not a {type_.__name__}.", + self._not + ) + + def __call__(self, condition: Callable[[Any], bool]) -> Assertion: + """ + Apply condition to value that returns whether or not the value is valid. + """ + return Assertion( + condition(self._value), + f"The value ({repr_(self._value)}) did not match given conditions.", + self._not + ) + + """ + NOT Properties. + """ + + @property + def NOT(self) -> Self: + self._not = True + return self + + @property + def not_(self) -> Self: + self._not = True + return self + + @property + def never(self) -> Self: + self._not = True + return self + + """ + Chain self properties to sound natural + """ + + @property + def that(self) -> Self: + return self + + @property + def is_(self) -> Self: + return self + + @property + def does(self) -> Self: + return self + + @property + def was(self) -> Self: + return self + + @property + def been(self) -> Self: + return self + + @property + def have(self) -> Self: + return self + + @property + def has(self) -> Self: + return self + + @property + def a(self) -> Self: + return self + + @property + def an(self) -> Self: + return self + + +class EqualAssertionInterface(AssertionInterface): + """ + Interface created for when its value should be checked for equality + """ + + def __init__(self, value, parent: AssertionInterface = None): + super().__init__(value, parent) + + def __call__(self, value) -> Assertion: + return Assertion( + value == self._value, + f"The value {repr_(self._value)} is different from {repr(value)}.", + self._not + ) + + @property + def to(self) -> Self: + return self + + +class BaseAssertionInterface(AssertionInterface): + + @property + def equals(self) -> EqualAssertionInterface: + """ + Checks if the current value is equal to the provided value + """ + return EqualAssertionInterface(self._value, self) + + @property + def equal(self) -> EqualAssertionInterface: + """ + Checks if the current value is equal to the provided value + """ + return self.equals diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/basic.py b/runtime-pyside6/tests/plugins/natural/interfaces/basic.py new file mode 100644 index 0000000..85720da --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/basic.py @@ -0,0 +1,83 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from .assertion import Assertion +from .base import BaseAssertionInterface +from .int import NumberInterface +from .utils import repr_ + + +class FixedIteratorInterface(BaseAssertionInterface): + @property + def length(self) -> NumberInterface: + return NumberInterface(len(self._value), self) + + def elements(self, *elements) -> Assertion: + tests = [repr_(elem) for elem in elements if elem not in self._value] + return Assertion( + len(tests) == 0, + f"This value ({repr_(self._value)}) does not have elements {', '.join(tests)}.", + self._not + ) + + def element(self, element) -> Assertion: + return Assertion( + element in self._value, + f"This value ({repr_(self._value)}) does not have element {repr_(element)}.", + self._not + ) + + def contains(self, *elements) -> Assertion: + """ + Check if the element(s) are contained in the iterator. + """ + if len(elements) == 1: + return self.element(elements[0]) + else: + return self.elements(*elements) + + def contain(self, *elements): + """ + Check if the element(s) are contained in the iterator. + """ + return self.contains(*elements) + + +class BoolInterface(BaseAssertionInterface): + @property + def true(self): + return Assertion( + self._value == True, + f"The value ({repr_(self._value)}) is not True.", + self._not + ) + + @property + def false(self): + return Assertion( + self._value == False, + f"The value ({repr_(self._value)}) is not False.", + self._not + ) + + +class StringInterface(FixedIteratorInterface): + pass + + +class ListInterface(FixedIteratorInterface): + pass diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/int.py b/runtime-pyside6/tests/plugins/natural/interfaces/int.py new file mode 100644 index 0000000..b282e4d --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/int.py @@ -0,0 +1,320 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from math import log10, floor +from typing import Self + +from .assertion import Assertion +from .base import AssertionInterface +from .utils import repr_ + + +class NumberComparisonAssertionInterface(AssertionInterface): + def __init__(self, value, parent: AssertionInterface = None): + super().__init__(value, parent) + self._compare_stack = [] + + def _generate_compare_to(self) -> int: + """ + The number generated by the comparison stack. + E.g. can parse one.hundred.million.and.thirty.three.thousand.and.twelve.hundred.and.seven + as ['one', 'hundred', 'million', 'thirty', 'three', 'thousand', 'twelve', 'hundred', 'seven'] + which results 100,034,207 + """ + minus = len(self._compare_stack) > 0 and self._compare_stack[0] == -1 + if len(self._compare_stack) < (2 if minus else 1): + raise RuntimeError("No number to compare the value to provided.") + if minus: + self._compare_stack.pop(0) + # Compute the number + add_stack = [self._compare_stack.pop(0)] + for element in self._compare_stack: + last_power = floor(log10(abs(add_stack[-1]))) + current_power = floor(log10(abs(element))) + if last_power < current_power: # E.g. one hundred + add_stack[-1] *= element + elif last_power == 1 and current_power == 0: # E.g thirty four + add_stack[-1] += element + elif last_power > current_power: # E.g a hundred and five + add_stack.append(element) + else: + raise RuntimeError(f"Cannot chain two numbers with the same power ({add_stack[-1]} => {element}.") + total = sum(add_stack) + return -total if minus else total + + def _compare(self) -> Assertion: + raise RuntimeError(f"No comparison method defined in {type(self).__name__}.") + + def __bool__(self) -> bool: + return bool(self._compare()) + + def __call__(self, compare_to: int) -> Self: + if type(compare_to) not in (float, int): + raise RuntimeError(f"Cannot compare number ({self._value}) to non number ({repr_(compare_to)}).") + self._compare_stack.append(compare_to) + return self + + """ + Chain self properties + """ + + @property + def and_(self) -> Self: + return self + + @property + def AND(self) -> Self: + return self + + """ + Number shorthands + """ + + @property + def once(self) -> Self: + return self(1) + + @property + def twice(self) -> Self: + return self(2) + + @property + def thrice(self) -> Self: + return self(3) + + @property + def minus(self) -> Self: + return self(-1) + + @property + def zero(self) -> Self: + return self(0) + + @property + def one(self) -> Self: + return self(1) + + @property + def two(self) -> Self: + return self(2) + + @property + def three(self) -> Self: + return self(3) + + @property + def four(self) -> Self: + return self(4) + + @property + def five(self) -> Self: + return self(5) + + @property + def six(self) -> Self: + return self(6) + + @property + def seven(self) -> Self: + return self(7) + + @property + def eight(self) -> Self: + return self(8) + + @property + def nine(self) -> Self: + return self(9) + + @property + def ten(self) -> Self: + return self(10) + + @property + def eleven(self) -> Self: + return self(11) + + @property + def twelve(self) -> Self: + return self(12) + + @property + def thirteen(self) -> Self: + return self(13) + + @property + def fourteen(self) -> Self: + return self(14) + + @property + def fifteen(self) -> Self: + return self(15) + + @property + def sixteen(self) -> Self: + return self(16) + + @property + def seventeen(self) -> Self: + return self(17) + + @property + def eighteen(self) -> Self: + return self(18) + + @property + def nineteen(self) -> Self: + return self(19) + + @property + def twenty(self) -> Self: + return self(20) + + @property + def thirty(self) -> Self: + return self(30) + + @property + def forty(self) -> Self: + return self(40) + + @property + def fifty(self) -> Self: + return self(50) + + @property + def sixty(self) -> Self: + return self(60) + + @property + def seventy(self) -> Self: + return self(70) + + @property + def eighty(self) -> Self: + return self(80) + + @property + def ninety(self) -> Self: + return self(90) + + @property + def hundred(self) -> Self: + return self(100) + + @property + def thousand(self) -> Self: + return self(1_000) + + @property + def million(self) -> Self: + return self(1_000_000) + + @property + def billion(self) -> Self: + return self(1_000_000_000) + + +class LessThanComparisonInterface(NumberComparisonAssertionInterface): + def _compare(self) -> Assertion: + compare = self._generate_compare_to() + return Assertion( + self._value < compare, + f"The value ({repr_(self._value)}) is not less than to {repr_(compare)}.", + self._not + ) + + +class MoreThanComparisonInterface(NumberComparisonAssertionInterface): + def _compare(self) -> Assertion: + compare = self._generate_compare_to() + return Assertion( + self._value > compare, + f"The value ({repr_(self._value)}) is not more than to {repr_(compare)}.", + self._not + ) + + +class AtLeastComparisonInterface(NumberComparisonAssertionInterface): + def _compare(self) -> Assertion: + compare = self._generate_compare_to() + return Assertion( + self._value >= compare, + f"The value ({repr_(self._value)}) is not at least to {repr_(compare)}.", + self._not + ) + + +class AtMostComparisonInterface(NumberComparisonAssertionInterface): + def _compare(self) -> Assertion: + compare = self._generate_compare_to() + return Assertion( + self._value <= compare, + f"The value ({repr_(self._value)}) is not at least to {repr_(compare)}.", + self._not + ) + + +class EqualComparisonInterface(NumberComparisonAssertionInterface): + def _compare(self) -> Assertion: + compare = self._generate_compare_to() + return Assertion( + self._value == compare, + f"The value ({repr_(self._value)}) is not equal to {repr_(compare)}.", + self._not + ) + + @property + def to(self) -> Self: + return self + + +class NumberInterface(AssertionInterface): + def __call__(self, value): + return EqualComparisonInterface(self._value, self)(value) + + @property + def equals(self) -> EqualComparisonInterface: + return EqualComparisonInterface(self._value, self) + + @property + def equal(self) -> EqualComparisonInterface: + return EqualComparisonInterface(self._value, self) + + @property + def exactly(self) -> EqualComparisonInterface: + return EqualComparisonInterface(self._value, self) + + @property + def of(self) -> EqualComparisonInterface: + return EqualComparisonInterface(self._value, self) + + @property + def less_than(self) -> LessThanComparisonInterface: + return LessThanComparisonInterface(self._value, self) + + @property + def more_than(self) -> MoreThanComparisonInterface: + return MoreThanComparisonInterface(self._value, self) + + @property + def at_least(self) -> AtLeastComparisonInterface: + return AtLeastComparisonInterface(self._value, self) + + @property + def at_most(self) -> AtMostComparisonInterface: + return AtMostComparisonInterface(self._value, self) diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/spy.py b/runtime-pyside6/tests/plugins/natural/interfaces/spy.py new file mode 100644 index 0000000..ce0b8ff --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/spy.py @@ -0,0 +1,218 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +from typing import Callable, Self + +from .base import Assertion, repr_, AssertionInterface +from .int import NumberComparisonAssertionInterface + +PRINT_PREFIX = (" " * 3) + + +class SpyAssertion(Assertion): + def __init__(self, assertion: bool, message: str, calls: list, invert: bool): + super().__init__(assertion, message + "\n", invert) + if len(calls) > 0: + self.message += self.render_calls(calls) + else: + self.message += f"{PRINT_PREFIX}0 registered calls." + + def render_calls(self, calls): + lines = [f"{PRINT_PREFIX}{len(calls)} registered call(s):"] + for call in calls: + repr_args = [repr_(arg) for arg in call[0]] + repr_kwargs = [f"{key}={repr_(arg)}" for key, arg in call[1].items()] + lines.append(f" - {', '.join([*repr_args, *repr_kwargs])}") + return ("\n" + PRINT_PREFIX).join(lines) + + +class Methods: + AT_LEAST_ONCE = "AT_LEAST_ONCE" + EXACTLY = "EXACTLY" + AT_LEAST = "AT_LEAST" + AT_MOST = "AT_MOST" + MORE_THAN = "MORE_THAN" + LESS_THAN = "LESS_THAN" + + +class CalledInterface(NumberComparisonAssertionInterface): + """ + Internal class generated by Spy.called. + """ + + def __init__(self, calls: list[tuple[list, dict]], parent: AssertionInterface): + super().__init__(len(calls), parent) + self.__calls = calls + self.__method = Methods.AT_LEAST_ONCE + + def __apply_method(self, calls): + required = None if self._compare_stack == [] else self._generate_compare_to() + calls_count = len(calls) + match self.__method: + case Methods.AT_LEAST_ONCE: + compare = len(calls) >= 1 + error = f"Method was not called" + case Methods.EXACTLY: + compare = len(calls) == required + error = f"Method was not called {required} times ({required} != {calls_count})" + case Methods.AT_LEAST: + compare = len(calls) >= required + error = f"Method was not called at least {required} times ({required} >= {calls_count})" + case Methods.AT_MOST: + compare = len(calls) <= required + error = f"Method was not called at most {required} times ({required} <= {calls_count})" + case Methods.MORE_THAN: + compare = len(calls) > required + error = f"Method was not called more than {required} times ({required} > {calls_count})" + case Methods.LESS_THAN: + compare = len(calls) < required + error = f"Method was not called less than {required} times ({required} < {calls_count})" + case _: + raise RuntimeError(f"Unknown method {self.__method}.") + return compare, error + + def __bool__(self) -> bool: + """ + Converts to boolean on assertion. + """ + compare, error = self.__apply_method(self.__calls) + return bool(SpyAssertion(compare, error + ".", self.__calls, self._not)) + + """ + Chaining methods + """ + + def __call__(self, compare_to: int) -> Self: + super().__call__(compare_to) + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.EXACTLY + return self + + @property + def at_least(self) -> Self: + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.AT_LEAST + else: + raise RuntimeError(f"Cannot redefine method from {self.__method} to {Methods.AT_MOST}") + return self + + @property + def at_most(self) -> Self: + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.AT_MOST + else: + raise RuntimeError(f"Cannot redefine method from {self.__method} to {Methods.AT_MOST}") + return self + + @property + def more_than(self) -> Self: + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.MORE_THAN + else: + raise RuntimeError(f"Cannot redefine method from {self.__method} to {Methods.MORE_THAN}") + return self + + @property + def less_than(self) -> Self: + if self.__method == Methods.AT_LEAST_ONCE: + self.__method = Methods.LESS_THAN + else: + raise RuntimeError(f"Cannot redefine method from {self.__method} to {Methods.LESS_THAN}") + return self + + @property + def time(self) -> Self: + return self + + @property + def times(self) -> Self: + return self + + """ + Class properties. + """ + + def __match_calls_for_condition(self, condition: Callable[[list, dict], bool]) -> tuple[bool, str]: + calls = [] + for call in self.__calls: + if condition(call[0], call[1]): + calls.append(call) + compare, error = self.__apply_method(calls) + return compare, error + + def with_arguments(self, *args, **kwargs) -> SpyAssertion: + """ + Checks if the Spy has been called the given number of times + with at least the given arguments. + """ + + def some_args_matched(a, kw): + args_matched = all(( + arg in a + for arg in args + )) + kwargs_matched = all(( + key in kw and kw[key] == arg + for key, arg in kwargs.items() + )) + return args_matched and kwargs_matched + + compare, error = self.__match_calls_for_condition(some_args_matched) + repr_args = ', '.join([repr(arg) for arg in args]) + repr_kwargs = ', '.join([f"{key}={repr(arg)}" for key, arg in kwargs.items()]) + msg = f"{error} with arguments ({repr_args}) and keyword arguments ({repr_kwargs})." + return SpyAssertion(compare, msg, self.__calls, self._not) + + def with_arguments_matching(self, test_condition: Callable[[list, dict], bool]) -> SpyAssertion: + """ + Checks if the Spy has been called the given number of times + with arguments matching the given conditions. + """ + compare, error = self.__match_calls_for_condition(test_condition) + msg = f"{error} with arguments matching given conditions." + return SpyAssertion(compare, msg, self.__calls, self._not) + + def with_exact_arguments(self, *args, **kwargs) -> SpyAssertion: + """ + Checks if the Spy has been called the given number of times + with all the given arguments. + """ + compare, error = self.__match_calls_for_condition(lambda a, kw: a == args and kw == kwargs) + repr_args = ', '.join([repr(arg) for arg in args]) + repr_kwargs = ', '.join([f"{key}={repr(arg)}" for key, arg in kwargs.items()]) + msg = f"{error} with exact arguments ({repr_args}) and keyword arguments ({repr_kwargs})." + return SpyAssertion(compare, msg, self.__calls, self._not) + + def with_no_argument(self) -> SpyAssertion: + """ + Checks if the Spy has been called the given number of times + with all the given arguments. + """ + compare, error = self.__match_calls_for_condition(lambda a, kw: len(a) == 0 and len(kw) == 0) + return SpyAssertion(compare, f"{error} with no arguments.", self.__calls, self._not) + + +class SpyAssertionInterface(AssertionInterface): + + @property + def called(self) -> CalledInterface: + """ + Returns a boolean-able interface to check conditions for a given number of + time the spy was called. + """ + return CalledInterface(self._value.calls, self) diff --git a/runtime-pyside6/tests/plugins/natural/interfaces/utils.py b/runtime-pyside6/tests/plugins/natural/interfaces/utils.py new file mode 100644 index 0000000..5200975 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/interfaces/utils.py @@ -0,0 +1,27 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +from PySide6.QtQml import QJSValue + + +def repr_(data): + if isinstance(data, QJSValue): + variant = data.toVariant() + return f"QJSValue<{type(variant).__name__}>({repr(variant)})" + else: + return repr(data) \ No newline at end of file diff --git a/runtime-pyside6/tests/plugins/natural/spy.py b/runtime-pyside6/tests/plugins/natural/spy.py new file mode 100644 index 0000000..4026b20 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/spy.py @@ -0,0 +1,33 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from typing import Callable + + +class Spy: + """ + Class to spy into method calls with natural language expressions. + """ + + def __init__(self, function: Callable = None): + self.function = function + self.calls = [] + + def __call__(self, *args, **kwargs): + self.calls.append((args, kwargs)) + if self.function is not None: + return self.function(*args, **kwargs) diff --git a/runtime-pyside6/tests/plugins/natural/that.py b/runtime-pyside6/tests/plugins/natural/that.py new file mode 100644 index 0000000..60ad4b6 --- /dev/null +++ b/runtime-pyside6/tests/plugins/natural/that.py @@ -0,0 +1,68 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from typing import overload, Generic, TypeVar + +from . import Spy +from .interfaces.base import AssertionInterface, BaseAssertionInterface +from .interfaces.basic import StringInterface, BoolInterface, ListInterface +from .interfaces.int import NumberInterface +from .interfaces.spy import SpyAssertionInterface + +Interface = TypeVar("Interface", bound=AssertionInterface) + +MATCHES = [ + (str, StringInterface), + (bool, BoolInterface), + (int, NumberInterface), + (float, NumberInterface), + (list, ListInterface), + (Spy, SpyAssertionInterface) +] + + +@overload +def that(value: str) -> StringInterface: ... + + +@overload +def that(value: bool) -> BoolInterface: ... + + +@overload +def that(value: int) -> NumberInterface: ... + + +@overload +def that(value: float) -> NumberInterface: ... + +@overload +def that(value: Spy) -> SpyAssertionInterface: ... + + +@overload +def that[Interface](value: Interface) -> Interface: ... + + +def that(value: any) -> AssertionInterface: + if not isinstance(value, AssertionInterface): + interface = next((i for t, i in MATCHES if isinstance(value, t)), None) + if interface is not None: + value = interface(value) + else: + value = BaseAssertionInterface(value) + return value diff --git a/runtime-pyside6/tests/plugins/tests/__init__.py b/runtime-pyside6/tests/plugins/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/runtime-pyside6/tests/plugins/tests/test_natural.py b/runtime-pyside6/tests/plugins/tests/test_natural.py new file mode 100644 index 0000000..31f85c0 --- /dev/null +++ b/runtime-pyside6/tests/plugins/tests/test_natural.py @@ -0,0 +1,217 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +import pytest + +from ..natural import that, Assertion, Spy + + +def test_string(): + assert that("QWERTY").is_.an.instance_of(str) + assert that("QWERTY").is_.not_.an.instance_of(int) + assert that("QWERTY").is_.equal.to("QWERTY") + assert that("QWERTY").is_.NOT.equal.to("QWERTYUIOP") + assert that("QWERTY").is_.NOT.equal.to(3) + assert that("QWERTY").has.a.length.of(6) + assert that("QWERTY").does.NOT.have.a.length.of(7) + assert that("QWERTY").has.a.length.that.is_.NOT(5) + assert that("QWERTY").contains("WER") + assert that("QWERTY").contains("WER", "TY") + assert that("QWERTY").does.not_.contain("AZERTY") + with pytest.raises(Assertion): + assert that("QWERTY").is_.an.instance_of(int) + with pytest.raises(Assertion): + assert that("QWERTY").is_.equal.to(False) + with pytest.raises(Assertion): + assert that("QWERTY").has.a.length.of(1) + with pytest.raises(Assertion): + assert that("QWERTY").contains("AZERTY") + with pytest.raises(Assertion): + assert that("QWERTY").does.NOT.contain("QWE") + +def test_bool(): + assert that(True).is_.an.instance_of(bool) + assert that(True).is_.an.instance_of(int) + assert that(True).is_.NOT.an.instance_of(str) + assert that(True).equals(True) + assert that(True).is_.true + assert that(True).is_.NOT.false + assert that(False).is_.equal.to(False) + assert that(False).is_.false + assert that(False).is_.NOT.true + with pytest.raises(Assertion): + assert that(True).is_.false + with pytest.raises(Assertion): + assert that(True).is_.NOT.true + +def test_int(): + assert that(2).is_.an.instance_of(int) + assert that(2).is_.NOT.an.instance_of(bool) + assert that(2).is_.NOT.an.instance_of(str) + assert that(2).is_.more_than(1) + assert that(2).is_.NOT.less_than(1) + assert that(2).is_.less_than(3) + assert that(2).is_.NOT.more_than(3) + assert that(2).is_.at_least(1) + assert that(2).is_.NOT.at_most(1) + assert that(2).is_.at_most(3) + assert that(2).is_.NOT.at_least(3) + assert that(2).is_.at_most(2) + assert that(2).is_.at_least(2) + # Equality + assert that(2).is_(2) + assert that(2).was(2) + assert that(2).is_.exactly(2) + assert that(2).is_.equal.to(2) + assert that(2).equals(2) + assert that(2).is_.NOT(3) + assert that(2).does.NOT.equal(3) + +def test_int_shorthands(): + # Direct numbers + assert that(0).equals.zero + assert that(1).equals.one + assert that(2).equals.two + assert that(3).equals.three + assert that(4).equals.four + assert that(5).equals.five + assert that(6).equals.six + assert that(7).equals.seven + assert that(8).equals.eight + assert that(9).equals.nine + assert that(10).equals.ten + assert that(11).equals.eleven + assert that(12).equals.twelve + assert that(13).equals.thirteen + assert that(14).equals.fourteen + assert that(15).equals.fifteen + assert that(16).equals.sixteen + assert that(17).equals.seventeen + assert that(18).equals.eighteen + assert that(19).equals.nineteen + assert that(20).equals.twenty + assert that(30).equals.thirty + assert that(40).equals.forty + assert that(50).equals.fifty + assert that(60).equals.sixty + assert that(70).equals.seventy + assert that(80).equals.eighty + assert that(90).equals.ninety + assert that(100).equals.a.hundred + assert that(1000).equals.a.thousand + assert that(1_000_000).equals.a.million + +def test_add_natural_complex(): + # Test composed + assert that(34).equals.thirty.four + assert that(-34).equals.minus.thirty.four + assert that(100_033_207).equals.one.hundred.million.AND.thirty.three.thousand.AND.two.hundred.AND.seven + assert that(-1_200_033_207).equals.minus.one.billion.AND.two.hundred.million.AND.thirty.three.thousand.AND.two.hundred.AND.seven + assert that(7890).equals.seven.thousand.eight.hundred.and_.ninety + assert that(7890).equals.seventy.eight.hundred.and_.ninety + assert that(7890).equals(78)(100)(90) + with pytest.raises(RuntimeError): + assert that(1_000_000).equals.a.thousand.thousand + with pytest.raises(RuntimeError): + assert that(600).equals.one.twenty.thirty + with pytest.raises(RuntimeError): + assert that(2).equals + with pytest.raises(RuntimeError): + assert that(2).equals.one.minus.two + +def test_spy(): + spy = Spy(lambda *args, **kw: 10) + assert that(spy).is_.an.instance_of(Spy) + assert that(spy).is_(callable) + # Check calls + assert that(spy).was.never.called + assert that(spy).was.called.zero.times + assert spy(30, arg="string") == 10 + assert that(spy).was.called + assert that(spy).was.called.once + assert that(spy).was.called.one.time + assert that(spy).was.NOT.called.more_than.once + assert that(spy).was.called.with_arguments(30) + assert that(spy).was.called.with_arguments_matching(lambda args, kwargs: len(args) == 1 and len(kwargs) == 1) + assert that(spy).was.NOT.called.with_arguments(50) + assert that(spy).was.NOT.called.with_exact_arguments(30) + assert that(spy).was.NOT.called.with_no_argument() + assert that(spy).was.called.with_exact_arguments(30, arg="string") + with pytest.raises(Assertion): + assert that(spy).was.called.with_arguments(50) + with pytest.raises(Assertion): + assert that(spy).was.called.with_exact_arguments(30) + with pytest.raises(Assertion): + assert that(spy).was.called.with_no_argument() + +def test_spy_seral_calls(): + spy = Spy() + obj = object() + spy() + spy(30, arg="string") + spy(obj, 30, example=obj, none=None) + assert that(spy).was.called + assert that(spy).was.called.more_than.once + assert that(spy).was.called.more_than.twice + assert that(spy).was.NOT.called.more_than.thrice + assert that(spy).was.called.at_most.thrice + assert that(spy).was.called.at_least.thrice + assert that(spy).was.called.three.times + assert that(spy).was.called.less_than(4).times + # Check arguments + assert that(spy).was.called.once.with_no_argument() + assert that(spy).was.called.at_most.once.with_no_argument() + assert that(spy).was.called.twice.with_arguments(30) + assert that(spy).was.NOT.called.less_than.twice.with_arguments(30) + assert that(spy).was.called.once.with_arguments(obj) + assert that(spy).was.called.once.with_arguments(arg="string") + assert that(spy).was.called.once.with_arguments(30, obj) + assert that(spy).was.called.once.with_arguments(none=None) + assert that(spy).was.NOT.called.with_arguments(None) + assert that(spy).was.NOT.called.with_arguments(obj, 30, arg="string") + with pytest.raises(Assertion): + assert that(spy).was.called.with_arguments(obj, 30, arg="string") + # Checking with exact arguments + assert that(spy).was.called.once.with_exact_arguments(30, arg="string") + assert that(spy).was.called.once.with_exact_arguments(obj, 30, example=obj, none=None) + assert that(spy).was.NOT.called.with_exact_arguments(obj, 30, arg="string") + with pytest.raises(Assertion): + assert that(spy).was.called.with_exact_arguments(obj, 30, arg="string") + # Check arguments matching + assert that(spy).has.NOT.been.called.with_arguments_matching(lambda a, kw: len(a) + len(kw) == 3) + assert that(spy).was.called.once.with_arguments_matching(lambda a, kw: len(a) + len(kw) == 2) + assert that(spy).was.called.once.with_arguments_matching(lambda a, kw: len(a) + len(kw) == 4) + with pytest.raises(Assertion): + assert that(spy).was.called.with_arguments_matching(lambda a, kw: len(a) + len(kw) == 3) + + +def test_wrongful_expressions(): + spy = Spy() + with pytest.raises(RuntimeError): + assert that(3).is_.less_than("str") + with pytest.raises(RuntimeError): + assert that(3).does.NOT.NOT.equal(3) + with pytest.raises(RuntimeError): + assert that(3).is_.an.instance_of("non type") + with pytest.raises(RuntimeError): + assert that(spy).was.called.more_than.at_least.once + with pytest.raises(RuntimeError): + assert that(spy).was.called.more_than.at_most.once + with pytest.raises(RuntimeError): + assert that(spy).was.called.more_than.less_than.once + with pytest.raises(RuntimeError): + assert that(spy).was.called.more_than.more_than.once \ No newline at end of file diff --git a/runtime-pyside6/tests/test_config.py b/runtime-pyside6/tests/test_config.py new file mode 100644 index 0000000..a95a9d9 --- /dev/null +++ b/runtime-pyside6/tests/test_config.py @@ -0,0 +1,60 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import pytest +from LogarithmPlotter.util import config +from tempfile import TemporaryDirectory +from os.path import join + + +@pytest.fixture() +def resource(): + directory = TemporaryDirectory() + config.CONFIG_FILE = join(directory.name, "config.json") + config.init() + yield config.CONFIG_FILE + directory.cleanup() + + +class TestConfig: + + def test_init(self, resource): + assert config.current_config == config.DEFAULT_SETTINGS + + def test_get(self, resource): + assert config.getSetting("expression_editor.autoclose") == True + with pytest.raises(config.UnknownNamespaceError): + config.getSetting("unknown_setting") + + def test_set(self, resource): + assert config.setSetting("expression_editor.autoclose", False) is None + assert config.getSetting("expression_editor.autoclose") == False # Ensure set is working. + with pytest.raises(config.UnknownNamespaceError): + config.setSetting("unknown_dict.unknown_setting", False) + + def test_reinit(self, resource): + default_value = config.getSetting("expression_editor.autoclose") + config.setSetting("expression_editor.autoclose", not default_value) + config.init() + assert config.getSetting("expression_editor.autoclose") != default_value # Ensure setting has been reset. + + def test_save(self, resource): + config.setSetting("expression_editor.autoclose", False) + config.save(resource) + config.init() + assert config.getSetting("expression_editor.autoclose") == False # Ensure setting has been saved. diff --git a/runtime-pyside6/tests/test_debug.py b/runtime-pyside6/tests/test_debug.py new file mode 100644 index 0000000..467e5b8 --- /dev/null +++ b/runtime-pyside6/tests/test_debug.py @@ -0,0 +1,68 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import pytest + +from os.path import exists +from PySide6.QtCore import QtMsgType, QMessageLogContext + +from LogarithmPlotter.util import debug + + +def test_setup(): + sourcemap_installed = False + try: + import sourcemap + sourcemap_installed = True + except: + pass + if sourcemap_installed: + file = debug.SOURCEMAP_PATH + debug.SOURCEMAP_PATH = None + debug.setup() # Nothing could be setup. + debug.SOURCEMAP_PATH = file + debug.setup() + assert (sourcemap_installed and exists(debug.SOURCEMAP_PATH)) == (debug.SOURCEMAP_INDEX is not None) + + +def test_map_source(): + sourcemap_available = debug.SOURCEMAP_INDEX is not None + if sourcemap_available: + assert debug.map_javascript_source("js/index.mjs", 22) != ("js/module/index.mjs", 22) + assert debug.map_javascript_source("js/index.mjs", 100000) == ("js/index.mjs", 100000) # Too long, not found + debug.SOURCEMAP_INDEX = None + assert debug.map_javascript_source("js/index.mjs", 21) == ("js/index.mjs", 21) + + +def test_log_terminal_message(): + msg1 = debug.create_log_terminal_message( + QtMsgType.QtWarningMsg, QMessageLogContext(), + "a long and random message" + ) + assert "[WARNING]" in msg1 + assert "a long and random message" in msg1 + msg2 = debug.create_log_terminal_message( + QtMsgType.QtCriticalMsg, + QMessageLogContext("LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Index.qml", 15, "anotherFunctionName", + "aCategoryDifferent"), + "LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter/Index.qml:15: a potentially potential error message" + ) + assert "[CRITICAL]" in msg2 + assert "Index.qml" in msg2 + assert "a potentially potential error message" in msg2 + assert "anotherFunctionName" in msg2 diff --git a/runtime-pyside6/tests/test_helper.py b/runtime-pyside6/tests/test_helper.py new file mode 100644 index 0000000..f2f6d70 --- /dev/null +++ b/runtime-pyside6/tests/test_helper.py @@ -0,0 +1,181 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import pytest +from os import getcwd, remove, path +from os.path import join +from tempfile import TemporaryDirectory +from json import loads +from shutil import copy2 + +from PySide6.QtCore import QObject, Signal, QThreadPool +from PySide6.QtGui import QImage +from PySide6.QtQml import QJSValue +from PySide6.QtWidgets import QApplication + +from LogarithmPlotter import __VERSION__ as version +from LogarithmPlotter.util import config, helper +from LogarithmPlotter.util.helper import Helper, fetch_changelog, read_changelog, InvalidFileException + +pwd = getcwd() +helper.SHOW_GUI_MESSAGES = False + +@pytest.fixture() +def temporary(): + directory = TemporaryDirectory() + config.CONFIG_PATH = join(directory.name, "config.json") + tmpfile = join(directory.name, "graph.png") + yield tmpfile, directory + directory.cleanup() + + +def create_changelog_callback_asserter(promise, expect_404=False): + def cb(changelog, expect_404=expect_404): + # print("Got changelog", changelog) + assert isinstance(changelog, QJSValue) + assert changelog.isString() + changlogValue = changelog.toVariant() + assert ('404' in changlogValue) == expect_404 + def error(e): + raise eval(e) + promise.then(cb, error) + +CHANGELOG_BASE_PATH = path.realpath(path.join(path.dirname(path.realpath(__file__)), "..", "CHANGELOG.md")) + + +class TestHelper: + def test_changelog(self, temporary, qtbot): + # Exists + helper.CHANGELOG_VERSION = '0.5.0' + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + promise = obj.fetchChangelog() + create_changelog_callback_asserter(promise, expect_404=False) + with qtbot.waitSignal(promise.fulfilled, timeout=10000): + pass + assert type(fetch_changelog()) == str + # Does not exist + helper.CHANGELOG_VERSION = '2.0.0' + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + promise = obj.fetchChangelog() + create_changelog_callback_asserter(promise, expect_404=True) + with qtbot.waitSignal(promise.fulfilled, timeout=10000): + pass + # Local + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + assert path.exists(CHANGELOG_BASE_PATH) + copy2(CHANGELOG_BASE_PATH, helper.CHANGELOG_CACHE_PATH) + assert path.exists(helper.CHANGELOG_CACHE_PATH) + promise = obj.fetchChangelog() + create_changelog_callback_asserter(promise, expect_404=False) + with qtbot.waitSignal(promise.fulfilled, timeout=100): # Local + pass + assert type(read_changelog()) == str + + def test_read(self, temporary): + # Test file reading and information loading. + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + data = obj.load("ci/test1.lpf") + assert type(data) == str + data = loads(data) + assert data['type'] == "logplotv1" + # Checking data['types'] of valid file. + # See https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/LogarithmPlotter-file-format-v1.0 + assert type(data['width']) == int + assert type(data['height']) == int + assert type(data['xzoom']) in (int, float) + assert type(data['yzoom']) in (int, float) + assert type(data['xmin']) in (int, float) + assert type(data['ymax']) in (int, float) + assert type(data['xaxisstep']) == str + assert type(data['yaxisstep']) == str + assert type(data['xaxislabel']) == str + assert type(data['yaxislabel']) == str + assert type(data['logscalex']) == bool + assert type(data['linewidth']) in (int, float) + assert type(data['showxgrad']) == bool + assert type(data['showygrad']) == bool + assert type(data['textsize']) in (int, float) + assert type(data['history']) == list and len(data['history']) == 2 + assert type(data['history'][0]) == list + assert type(data['history'][1]) == list + for action_list in data['history']: + for action in action_list: + assert type(action[0]) == str + assert type(action[1]) == list + assert type(data['objects']) == dict + for obj_type, objects in data['objects'].items(): + assert type(obj_type) == str + assert type(objects) == list + for obj in objects: + assert type(obj) == list + + def test_read_newer(self, temporary): + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + newer_file_path = join(directory.name, "newer.lpf") + with open(newer_file_path, "w") as f: + f.write("LPFv2[other invalid data]") + with pytest.raises(InvalidFileException): + obj.load(newer_file_path) + + def test_read_invalid_file(self, temporary): + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + with pytest.raises(InvalidFileException): + obj.load("./inexistant.lpf") + with pytest.raises(InvalidFileException): + obj.load("./pyproject.toml") + + def test_write(self, temporary): + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + target = join(directory.name, "target.lpf") + data = "example_data" + obj.write(target, data) + with open(target, "r") as f: + read_data = f.read() + # Ensure data has been written. + assert read_data == "LPFv1" + data + + def test_tmp_graphic(self, temporary): + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + assert obj.gettmpfile() == tmpfile + obj.copyImageToClipboard() + clipboard = QApplication.clipboard() + assert type(clipboard.image()) == QImage + + def test_strings(self, temporary): + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + assert obj.getVersion() == version + assert type(obj.getDebugInfos()) == str + assert type(obj.getSetting("last_install_greet").toVariant()) == str + assert type(obj.getSetting("check_for_updates").toVariant()) == bool + assert type(obj.getSetting("default_graph.xzoom").toVariant()) in [float, int] + + def test_set_config(self, temporary): + tmpfile, directory = temporary + obj = Helper(pwd, tmpfile) + obj.setSetting("last_install_greet", obj.getSetting("last_install_greet")) + obj.setSetting("check_for_updates", obj.getSetting("check_for_updates")) + obj.setSetting("default_graph.xzoom", obj.getSetting("default_graph.xzoom")) diff --git a/runtime-pyside6/tests/test_latex.py b/runtime-pyside6/tests/test_latex.py new file mode 100644 index 0000000..4ffbc6c --- /dev/null +++ b/runtime-pyside6/tests/test_latex.py @@ -0,0 +1,125 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import pytest +from tempfile import TemporaryDirectory +from shutil import which +from os.path import exists +from re import match +from PySide6.QtGui import QColor +from PySide6.QtQml import QJSValue + +from LogarithmPlotter.util import latex + +latex.SHOW_GUI_MESSAGES = False + + +@pytest.fixture() +def latex_obj(): + directory = TemporaryDirectory() + obj = latex.Latex(directory.name) + if not obj.checkLatexInstallation(): + raise Exception("Cannot run LaTeX tests without a proper LaTeX installation. Make sure to install a LaTeX distribution, DVIPNG, and the calligra package, and run the tests again.") + yield obj + directory.cleanup() + + +BLACK = QColor(0, 0, 0, 255) +BLUE = QColor(128, 128, 255, 255) + +def check_render_results(result): + if isinstance(result, QJSValue): + result = result.toVariant() + assert type(result) == str + [path, width, height] = result.split(",") + assert exists(path) + assert match(r"\d+", width) + assert match(r"\d+", height) + return True + +class TestLatex: + def test_check_install(self, latex_obj: latex.Latex) -> None: + assert latex_obj.latexSupported == True + assert latex_obj.checkLatexInstallation() == True + assert type(latex_obj.supportsAsyncRender) is bool + bkp = [latex.DVIPNG_PATH, latex.LATEX_PATH] + # Check what happens when one is missing. + latex.DVIPNG_PATH = None + assert latex_obj.latexSupported == False + assert latex_obj.checkLatexInstallation() == False + latex.DVIPNG_PATH = bkp[0] + latex.LATEX_PATH = None + assert latex_obj.latexSupported == False + assert latex_obj.checkLatexInstallation() == False + # Reset + [latex.DVIPNG_PATH, latex.LATEX_PATH] = bkp + + def test_render_sync(self, latex_obj: latex.Latex) -> None: + result = latex_obj.renderSync("\\frac{d \\sqrt{\\mathrm{f}(x \\times 2.3)}}{dx}", 14, BLACK) + # Ensure result format + check_render_results(result) + # Ensure it returns errors on invalid latex. + with pytest.raises(latex.RenderError): + latex_obj.renderSync("\\nonexistant", 14, BLACK) + # Replace latex bin with one that returns errors + bkp = latex.LATEX_PATH + latex.LATEX_PATH = which("false") + with pytest.raises(latex.RenderError): + latex_obj.renderSync("\\mathrm{f}(x)", 14, BLACK) + # Replace latex bin with one goes indefinitely + # latex.LATEX_PATH = which("import") # TODO: Find one such executable + # with pytest.raises(latex.RenderError): + # latex_obj.renderSync("\\mathrm{f}(x)", 14, BLACK) + latex.LATEX_PATH = bkp + + def test_prerendered(self, latex_obj: latex.Latex) -> None: + args = ["\\frac{d \\sqrt{\\mathrm{f}(x \\times 2.3)}}{dx}", 14, BLACK] + latex_obj.renderSync(*args) + prerendered = latex_obj.findPrerendered(*args) + assert type(prerendered) == str + [path, width, height] = prerendered.split(",") + assert exists(path) + assert match(r"\d+", width) + assert match(r"\d+", height) + prerendered2 = latex_obj.findPrerendered(args[0], args[1]+2, args[2]) + assert prerendered2 == "" + + def test_render_async(self, latex_obj: latex.Latex, qtbot) -> None: + formula = "\\int\\limits^{3x}_{-\\infty}9\\mathrm{f}(x)^3+t dx" + og_promise = latex_obj.renderAsync(formula, 14, BLACK) + # Ensure we get the same locked one if we try to render it again. + assert og_promise == latex_obj.renderAsync(formula, 14, BLACK) + # Ensure queued renders. + promises = [ + latex_obj.renderAsync(formula, 14, BLUE), + latex_obj.renderAsync(formula, 10, BLACK), + latex_obj.renderAsync(formula, 10, BLUE), + ] + for prom in promises: + assert og_promise.calls_upon_fulfillment(prom.start) + # Ensure other renders get done in parallel. + other_promise = latex_obj.renderAsync(formula+" dt", 10, BLACK) + assert not og_promise.calls_upon_fulfillment(other_promise.start) + # Ensure all of them render. + proms = [og_promise, *promises, other_promise] + with qtbot.waitSignals( + [p.fulfilled for p in proms], + raising=True, timeout=10000, + check_params_cbs=[check_render_results]*len(proms) + ): + pass diff --git a/runtime-pyside6/tests/test_main.py b/runtime-pyside6/tests/test_main.py new file mode 100644 index 0000000..1595236 --- /dev/null +++ b/runtime-pyside6/tests/test_main.py @@ -0,0 +1,108 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import pytest +from os import environ +from os.path import exists, join +from PySide6.QtGui import QIcon +from tempfile import TemporaryDirectory + +from .globals import app + +from LogarithmPlotter.logarithmplotter import get_linux_theme, LINUX_THEMES, get_platform_qt_style, \ + register_icon_directories, install_translation, create_engine +from LogarithmPlotter.util import config +from LogarithmPlotter.util.helper import Helper +from LogarithmPlotter.util.latex import Latex + +THEMES = [ + "Basic", + "Universal", + "Material", + "Fusion", + "Windows", + "macOS" +] + +OS_PLATFORMS = [ + "Linux", + "Windows", + "Darwin", + "Android" +] + +@pytest.fixture() +def temporary(): + directory = TemporaryDirectory() + config.CONFIG_PATH = join(directory.name, "config.json") + tmpfile = join(directory.name, "graph.png") + yield tmpfile, directory + directory.cleanup() + +class TestMain: + def test_linux_themes(self): + # Check without a desktop + if "XDG_SESSION_DESKTOP" in environ: + del environ["XDG_SESSION_DESKTOP"] + assert get_linux_theme() in THEMES + # Test various environments. + environ["XDG_SESSION_DESKTOP"] = "GNOME" + assert get_linux_theme() in THEMES + # Test various environments. + environ["XDG_SESSION_DESKTOP"] = "NON-EXISTENT" + assert get_linux_theme() in THEMES + # Check all linux themes are in list + for desktop, theme in LINUX_THEMES.items(): + assert theme in THEMES + + def test_os_themes(self): + for platform in OS_PLATFORMS: + assert get_platform_qt_style(platform) in THEMES + + def test_icon_directories(self): + base_paths = QIcon.fallbackSearchPaths() + register_icon_directories() + # Check if registered + assert len(base_paths) < len(QIcon.fallbackSearchPaths()) + # Check if all exists + for p in QIcon.fallbackSearchPaths(): + assert exists(p) + + def test_app(self, temporary): + assert not app.windowIcon().isNull() + # Translations + translator = install_translation(app) + assert not translator.isEmpty() + # Engine + tmpfile, tempdir = temporary + helper = Helper(".", tmpfile) + latex = Latex(tempdir.name) + engine, js_globals = create_engine(helper, latex, 0) + assert len(engine.rootObjects()) > 0 # QML File loaded. + assert type(engine.rootContext().contextProperty("TestBuild")) is bool + assert engine.rootContext().contextProperty("StartTime") == 0 + assert js_globals.Latex.type() is not None + assert js_globals.Helper.type() is not None + assert js_globals.Modules.type() is not None + # Check if modules have loaded + assert js_globals.Modules.History.type() is not None + assert js_globals.Modules.Latex.type() is not None + assert js_globals.Modules.Canvas.type() is not None + assert js_globals.Modules.IO.type() is not None + assert js_globals.Modules.Objects.type() is not None + assert js_globals.Modules.Preferences.type() is not None diff --git a/runtime-pyside6/tests/test_native.py b/runtime-pyside6/tests/test_native.py new file mode 100644 index 0000000..d60b907 --- /dev/null +++ b/runtime-pyside6/tests/test_native.py @@ -0,0 +1,56 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import pytest + +from PySide6.QtCore import QEvent, QObject, QUrl +from PySide6.QtGui import QActionEvent, QFileOpenEvent + +from LogarithmPlotter.util.native import MacOSFileOpenHandler + + +class LoadDiagramCalledSuccessfully(Exception): pass + + +class MockIO: + def loadDiagram(self, file_name): + assert type(file_name) == str + raise LoadDiagramCalledSuccessfully() + + +class MockFileOpenEvent(QEvent): + def __init__(self, file): + QEvent.__init__(self, QEvent.FileOpen) + self._file = file + + def file(self): + return self._file + + +def test_native(): + event_filter = MacOSFileOpenHandler() + # Nothing should happen here. The module hasn't been initialized + event_filter.eventFilter(None, QFileOpenEvent(QUrl.fromLocalFile("ci/test1.lpf"))) + with pytest.raises(LoadDiagramCalledSuccessfully): + event_filter.init_io(MockIO()) # Now that we've initialized, the loadDiagram function should be called. + with pytest.raises(LoadDiagramCalledSuccessfully): + # And now it will do so every time an event is loaded. + event_filter.eventFilter(None, QFileOpenEvent(QUrl.fromLocalFile("ci/test1.lpf"))) + # Check what happens when a non file open qevent is launched against it. + event_filter.eventFilter(QObject(), QEvent(QEvent.ActionAdded)) + diff --git a/runtime-pyside6/tests/test_promise.py b/runtime-pyside6/tests/test_promise.py new file mode 100644 index 0000000..ef71a55 --- /dev/null +++ b/runtime-pyside6/tests/test_promise.py @@ -0,0 +1,177 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from time import sleep + +import pytest +from PySide6.QtQml import QJSValue + +from .plugins.natural import that, Spy +from LogarithmPlotter.util.js import PyJSValue +from LogarithmPlotter.util.promise import PyPromise + + +def check_promise_result(value): + def got_result(args, kwargs, val=value): + valid = len(args) == 1 and len(kwargs) == 0 + if valid: + got_value = args[0].toVariant() if isinstance(args[0], QJSValue) else args[0] + valid = got_value == val + return valid + return got_result + +def create_async_func(value): + def async_function(val=value): + sleep(1) + return val + + return async_function + + +def qjs_eq(origin): + def compare(result): + res = result.toVariant() == origin + print("Unknown res!", res, repr(result.toVariant()), repr(origin)) + return res + return compare + + +def async_throw(): + sleep(1) + raise Exception("aaaa") + + +class TestPyPromise: + + def test_invalid_function(self): + with pytest.raises(ValueError): + promise = PyPromise("not a function") + + def test_fulfill_values(self, qtbot): + qjsv = QJSValue(3) + values = [ + [True, qjs_eq(True)], + [3, qjs_eq(3)], + [2.2, qjs_eq(2.2)], + ["String", qjs_eq("String")], + [qjsv, qjs_eq(3)], + [None, qjs_eq(None)], + [PyJSValue(QJSValue("aaa")), qjs_eq("aaa")] + ] + for [value, test] in values: + promise = PyPromise(create_async_func(value)) + with qtbot.assertNotEmitted(promise.rejected, wait=1000): + with qtbot.waitSignal(promise.fulfilled, check_params_cb=test, timeout=2000): + assert promise.state == "pending" + assert promise.state == "fulfilled" + + def test_reject(self, qtbot): + promise = PyPromise(async_throw) + with qtbot.assertNotEmitted(promise.fulfilled, wait=1000): + with qtbot.waitSignal(promise.rejected, timeout=10000, + check_params_cb=lambda t: t == "Exception('aaaa')"): + assert promise.state == "pending" + assert promise.state == "rejected" + + def test_fulfill(self, qtbot): + fulfilled = Spy() + rejected = Spy() + promise = PyPromise(create_async_func(3)) + then_res = promise.then(fulfilled, rejected) + # Check if the return value is the same promise (so we can chain then) + assert that(then_res).does.equal(promise) + # Check on our spy. + with qtbot.waitSignal(promise.fulfilled, timeout=10000): + pass + assert that(fulfilled).was.called.once + assert that(fulfilled).was.NOT.called.with_arguments(3) + assert that(fulfilled).was.called.with_arguments_matching(check_promise_result(3)) + assert that(rejected).was.never.called + + def test_rejected(self, qtbot): + fulfilled = Spy() + rejected = Spy() + promise = PyPromise(async_throw) + then_res = promise.then(fulfilled, rejected) + # Check if the return value is the same promise (so we can chain then) + assert that(then_res).does.equal(promise) + # Check on our spies. + with qtbot.waitSignal(promise.rejected, timeout=10000): + pass + assert that(rejected).was.called.once + assert that(rejected).was.called.with_arguments("Exception('aaaa')") + assert that(fulfilled).has.never.been.called + + def test_chain_fulfill(self, qtbot): + convert = Spy(lambda v: v.toVariant()) + plus = Spy(lambda v: v + 1) + rejected = Spy() + promise = PyPromise(create_async_func(5)) + then_res = promise.then(convert, rejected).then(plus, rejected).then(plus, rejected).then(plus, rejected) + # Check if the return value is the same promise (so we can chain then) + assert that(then_res).does.equal(promise) + with qtbot.waitSignal(promise.fulfilled, timeout=10000): + pass + assert that(convert).was.called.once.with_arguments_matching(check_promise_result(5)) + assert that(rejected).was.never.called + assert that(plus).was.called.three.times + assert that(plus).was.called.once.with_exact_arguments(5) + assert that(plus).was.called.once.with_exact_arguments(6) + assert that(plus).was.called.once.with_exact_arguments(7) + + def test_chain_reject(self, qtbot): + fulfilled = Spy() + convert = Spy(lambda v: len(v)) + minus = Spy(lambda v: v - 1) + promise = PyPromise(async_throw) + then_res = promise.then(fulfilled, convert).then(fulfilled, minus).then(fulfilled, minus).then(fulfilled, minus) + # Check if the return value is the same promise (so we can chain then) + assert that(then_res).does.equal(promise) + with qtbot.waitSignal(promise.rejected, timeout=10000): + pass + assert that(fulfilled).was.never.called + assert that(convert).was.called.once.with_arguments_matching(check_promise_result("Exception('aaaa')")) + assert that(minus).was.called.three.times + assert that(minus).was.called.once.with_exact_arguments(17) + assert that(minus).was.called.once.with_exact_arguments(16) + assert that(minus).was.called.once.with_exact_arguments(15) + + def test_check_calls_upon(self): + promise = PyPromise(async_throw) + fulfilled = Spy() + rejected = Spy() + promise.then(fulfilled, rejected) + assert promise.calls_upon_fulfillment(fulfilled) + assert promise.calls_upon_rejection(rejected) + assert not promise.calls_upon_fulfillment(rejected) + assert not promise.calls_upon_rejection(fulfilled) + + def test_reject_in_fulfill(self, qtbot): + def fulfilled_throw(x): + raise Exception('noooo') + promise = PyPromise(create_async_func("3")) + fulfilled_throw = Spy(fulfilled_throw) + fulfilled = Spy() + rejected = Spy() + then_res = promise.then(fulfilled, rejected).then(fulfilled_throw, rejected).then(fulfilled, rejected).then(fulfilled, rejected) + # Check if the return value is the same promise (so we can chain then) + assert that(then_res).does.equal(promise) + with qtbot.waitSignal(promise.fulfilled, timeout=10000): + pass + assert that(fulfilled_throw).has.been.called.once + assert that(rejected).has.been.called.three.times + assert that(rejected).has.been.called.three.times.with_arguments("Exception('noooo')") \ No newline at end of file diff --git a/runtime-pyside6/tests/test_pyjs.py b/runtime-pyside6/tests/test_pyjs.py new file mode 100644 index 0000000..9177b4e --- /dev/null +++ b/runtime-pyside6/tests/test_pyjs.py @@ -0,0 +1,76 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" + +import pytest +from re import Pattern +from PySide6.QtQml import QJSEngine, QJSValue + +from LogarithmPlotter.util.js import PyJSValue, InvalidAttributeValueException, NotAPrimitiveException + +@pytest.fixture() +def data(): + engine = QJSEngine() + obj = PyJSValue(engine.globalObject()) + yield engine, obj + +class TestPyJS: + def test_set(self, data): + engine, obj = data + obj.num1 = 2 + obj.num2 = QJSValue(2) + obj.num3 = PyJSValue(QJSValue(2)) + with pytest.raises(InvalidAttributeValueException): + obj.num3 = object() + + def test_eq(self, data): + engine, obj = data + obj.num = QJSValue(2) + assert obj.num == 2 + assert obj.num == QJSValue(2) + assert obj.num == PyJSValue(QJSValue(2)) + assert obj.num != object() + + def test_function(self, data): + engine, obj = data + function = PyJSValue(engine.evaluate("(function(argument) {return argument*2})")) + assert function(3) == 6 + assert function(10) == 20 + function2 = PyJSValue(engine.evaluate("(function(argument) {return argument+3})"), obj.qjs_value) + assert function2(3) == 6 + assert function2(10) == 13 + function3 = PyJSValue(engine.evaluate("2+2")) + with pytest.raises(InvalidAttributeValueException): + function3() + + def test_type(self, data): + engine, obj = data + assert PyJSValue(engine.evaluate("[]")).type() == list + assert PyJSValue(engine.evaluate("undefined")).type() is None + assert PyJSValue(engine.evaluate("/[a-z]/g")).type() == Pattern + assert PyJSValue(QJSValue(2)).type() == float + assert PyJSValue(QJSValue("3")).type() == str + assert PyJSValue(QJSValue(True)).type() == bool + + def test_primitive(self, data): + engine, obj = data + assert PyJSValue(QJSValue(2)).primitive() == 2 + assert PyJSValue(QJSValue("string")).primitive() == "string" + assert PyJSValue(QJSValue(True)).primitive() == True + assert PyJSValue(engine.evaluate("undefined")).primitive() is None + with pytest.raises(NotAPrimitiveException): + assert PyJSValue(engine.evaluate("[]")).primitive() == [] diff --git a/runtime-pyside6/tests/test_update.py b/runtime-pyside6/tests/test_update.py new file mode 100644 index 0000000..16b562e --- /dev/null +++ b/runtime-pyside6/tests/test_update.py @@ -0,0 +1,68 @@ +""" + * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. + * Copyright (C) 2021-2025 Ad5001 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +""" +from sys import argv + +import pytest +from PySide6.QtCore import QThreadPool + +from LogarithmPlotter import __VERSION__ as version +from LogarithmPlotter.util.update import UpdateInformation, UpdateCheckerRunnable, check_for_updates + + +class MockWindow: + def showAlert(self, msg): raise Exception(msg) + def showUpdateMenu(self, msg): pass + +def check_update_callback_type(show_alert, msg_text, update_available): + assert type(show_alert) == bool + assert type(msg_text) == str + assert type(update_available) == bool + +def test_update(qtbot): + def check_older(show_alert, msg_text, update_available): + check_update_callback_type(show_alert, msg_text, update_available) + assert update_available + assert show_alert + + def check_newer(show_alert, msg_text, update_available): + check_update_callback_type(show_alert, msg_text, update_available) + assert not update_available + assert not show_alert + + update_info_older = UpdateInformation() + update_info_older.got_update_info.connect(check_older) + update_info_newer = UpdateInformation() + update_info_newer.got_update_info.connect(check_newer) + runnable = UpdateCheckerRunnable('1.0.0', update_info_newer) + with qtbot.waitSignal(update_info_newer.got_update_info, timeout=10000): + runnable.run() + runnable = UpdateCheckerRunnable('0.1.0', update_info_older) + with qtbot.waitSignal(update_info_older.got_update_info, timeout=10000): + runnable.run() + runnable = UpdateCheckerRunnable('0.5.0+dev0+git20240101', update_info_older) + with qtbot.waitSignal(update_info_older.got_update_info, timeout=10000): + runnable.run() + +def test_update_checker(qtbot): + update_info = check_for_updates('0.6.0', MockWindow()) + assert QThreadPool.globalInstance().activeThreadCount() >= 1 + with qtbot.waitSignal(update_info.got_update_info, timeout=10000): + pass + argv.append("--no-check-for-updates") + update_info = check_for_updates('0.6.0', MockWindow()) + assert QThreadPool.globalInstance().activeThreadCount() < 2 # No new update checks where added diff --git a/scripts/build-macosx.sh b/scripts/build-macosx.sh index 03da330..8f3f38d 100755 --- a/scripts/build-macosx.sh +++ b/scripts/build-macosx.sh @@ -1,22 +1,35 @@ #!/usr/bin/env bash DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$DIR/.." +cd "$DIR/.." || exit 1 +rebuild=true + +while [ $# -gt 0 ]; do + case "$1" in + --no-rebuild) + rebuild=false + ;; + *) + box "Error: Invalid argument." + exit 1 + esac + shift +done + +if [ "$rebuild" == "true" ]; then + rm -rf build + bash scripts/build.sh +fi + +cd build/runtime-pyside6 || exit 1 -rm $(find . -name "*.qmlc") rm $(find . -name "*.pyc") -python3 -m pip install -U pyinstaller<6.0 - -# Building translations -cd "LogarithmPlotter/i18n/" -bash release.sh -cd ../../ pyinstaller --add-data "LogarithmPlotter/qml:qml" \ --add-data "LogarithmPlotter/i18n:i18n" \ - --add-data "LICENSE.md:." \ - --add-data "mac/logarithmplotterfile.icns:." \ - --add-data "README.md:." \ + --add-data "../../LICENSE.md:." \ + --add-data "../../assets/native/mac/logarithmplotterfile.icns:." \ + --add-data "../../README.md:." \ --exclude-module "FixTk" \ --exclude-module "tcl" \ --exclude-module "tk" \ @@ -25,21 +38,19 @@ pyinstaller --add-data "LogarithmPlotter/qml:qml" \ --exclude-module "Tkinter" \ --noconsole \ --noconfirm \ - --icon=mac/logarithmplotter.icns \ + --icon=../../assets/native/mac/logarithmplotter.icns \ --osx-bundle-identifier eu.ad5001.LogarithmPlotter \ -n LogarithmPlotter \ LogarithmPlotter/logarithmplotter.py -cp mac/Info.plist dist/LogarithmPlotter.app/Contents/Info.plist +cp ../../assets/native/mac/Info.plist dist/LogarithmPlotter.app/Contents/Info.plist # Remove QtWebEngine, 3D and all other unused libs libs rm -rf dist/LogarithmPlotter.app/Contents/MacOS/{QtWeb*,*3D*,QtRemote*,QtPdf,QtCharts,QtLocation,QtTest,QtMultimedia,QtSpatialAudio,QtDataVisualization,QtQuickParticles,QtChartsQml,QtScxml,QtDataVisualizationQml,QtTest,QtPositioningQuick,QtQuickTest,QtSql,QtSensorsQuick} -rm -rf dist/LogarithmPlotter.app/Contents/MacOS/PySide6/{QtNetwork.abi3.so} +rm -rf dist/LogarithmPlotter.app/Contents/MacOS/PySide6/QtNetwork.abi3.so # Removing QtQuick3D -rm -rf dist/LogarithmPlotter.app/Contents/MacOS/PySide6/Qt/qml/QtQuick3D -rm -rf dist/LogarithmPlotter.app/Contents/MacOS/PySide6/Qt/qml/Qt3D -rm -rf dist/LogarithmPlotter.app/Contents/MacOS/PySide6/Qt/qml/QtWebEngine +rm -rf dist/LogarithmPlotter.app/Contents/MacOS/PySide6/Qt/qml/{QtQuick3D,Qt3D,QtWebEngine} # Remove the QtQuick styles that are unused rm -rf dist/LogarithmPlotter.app/Contents/MacOS/PySide6/Qt/qml/QtQuick/Controls/{Imagine,Material,iOS,Universal,designer} diff --git a/scripts/build-windows.bat b/scripts/build-windows.bat deleted file mode 100644 index ef92aee..0000000 --- a/scripts/build-windows.bat +++ /dev/null @@ -1,17 +0,0 @@ -rem Make sure pyinstaller is installed -python -m pip install -U pyinstaller - -rem Building translations -cd "LogarithmPlotter\i18n" -cmd release.sh -cd ..\.. - -pyinstaller --add-data "logplotter.svg;." --add-data "LogarithmPlotter/qml;qml" --add-data "LogarithmPlotter/i18n;i18n" --noconsole LogarithmPlotter/logarithmplotter.py --icon=win/logarithmplotter.ico -n logarithmplotter - -rem Remove QtWebEngine -del dist\logarithmplotter\PySide6\Qt6WebEngineCore.dll -rem Remove the QtQuick styles that are unused -rmdir dist\logarithmplotter\PySide6\qml\QtQuick\Controls\Imagine /s /q -rmdir dist\logarithmplotter\PySide6\qml\QtQuick\Controls\Material /s /q -rmdir dist\logarithmplotter\PySide6\qml\QtQuick\Controls\designer /s /q -rem Remove unused translations diff --git a/scripts/build-wine.sh b/scripts/build-wine.sh index 638ba1d..fa78bb1 100644 --- a/scripts/build-wine.sh +++ b/scripts/build-wine.sh @@ -1,18 +1,36 @@ #!/bin/bash -cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." +cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." || exit -rm -rf dist +rebuild=true + +while [ $# -gt 0 ]; do + case "$1" in + --no-rebuild) + rebuild=false + ;; + *) + box "Error: Invalid argument." + exit 1 + esac + shift +done + +if [ "$rebuild" == "true" ]; then + rm -rf build + bash scripts/build.sh +fi + +cd build/runtime-pyside6 || exit 1 -rm $(find . -name "*.qmlc") rm -rf $(find . -name "*.pyc") -wine python -m pip install -U pyinstaller -# Building translations -cd "LogarithmPlotter/i18n/" -bash release.sh -cd ../../ - -wine pyinstaller --add-data "logplotter.svg;." --add-data "LogarithmPlotter/qml;qml" --add-data "LogarithmPlotter/i18n;i18n" --noconsole LogarithmPlotter/logarithmplotter.py --icon=win/logarithmplotter.ico -n logarithmplotter +wine pyinstaller --add-data "LogarithmPlotter/logarithmplotter.svg;." \ + --add-data "LogarithmPlotter/qml;qml" \ + --add-data "LogarithmPlotter/i18n;i18n" \ + --noconsole \ + LogarithmPlotter/logarithmplotter.py \ + --icon=../../assets/native/win/logarithmplotter.ico \ + -n logarithmplotter # Copy Qt6ShaderTools, a required library for for Qt5Compat PYSIDE6PATH="$(wine python -c "import PySide6; from os import path; print(path.dirname(PySide6.__file__));")" diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..257b2c4 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. +# Copyright (C) 2021-2025 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 . +# + +# This script builds a dist version of LogarithmPlotter + +DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$DIR/.." || exit 1 + +BUILD_DIR="build/runtime-pyside6" +BUILD_QML_DIR="$BUILD_DIR/LogarithmPlotter/qml/eu/ad5001/LogarithmPlotter" + + +box() { + len=${#1} + echo "┌─$(printf '─%.0s' $(seq 1 "$len"))─┐" + echo "│ $1 │" + echo "└─$(printf '─%.0s' $(seq 1 "$len"))─┘" +} + +rm -rf build +mkdir -p "$BUILD_DIR" + +# Copy python +box "Copying pyside6 python runtime..." +cp -r runtime-pyside6/{setup.py,LogarithmPlotter} "$BUILD_DIR" + +box "Building ecmascript modules..." +mkdir -p "$BUILD_QML_DIR/js" +cd common && \ + (npm run build || exit) && \ + cd .. + +box "Building translations..." +cd assets/i18n/ && (bash release.sh || exit) && cd ../../ +mkdir -p "$BUILD_DIR/LogarithmPlotter/i18n" && cp assets/i18n/*.qm "$BUILD_DIR/LogarithmPlotter/i18n/" + +box "Building icons..." +cp -r assets/icons "$BUILD_QML_DIR" +cp assets/logarithmplotter.svg "$BUILD_DIR/LogarithmPlotter/" diff --git a/linux/generate-appstream-changelog.sh b/scripts/generate-appstream-changelog.sh similarity index 68% rename from linux/generate-appstream-changelog.sh rename to scripts/generate-appstream-changelog.sh index 331239f..e37c9fa 100644 --- a/linux/generate-appstream-changelog.sh +++ b/scripts/generate-appstream-changelog.sh @@ -11,13 +11,13 @@ BEGIN { print "
                        " print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-v"version"-setup.exe" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-v"version"-setup.exe" print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/LogarithmPlotter-v"version"-setup.dmg" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/LogarithmPlotter-v"version"-setup.dmg" print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-"version".tar.gz" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-"version".tar.gz" print " " print " " print "
                        " @@ -28,7 +28,7 @@ BEGIN { version = substr($2,2,5) print " " print " " - print "

                        Changes for "$2":

                        " + print "

                        Changes for "$2":

                        " } /^\s*\*\*/ { if(listBegan) { @@ -58,13 +58,13 @@ BEGIN { print "
                        " print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-v"version"-setup.exe" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-v"version"-setup.exe" print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/LogarithmPlotter-v"version"-setup.dmg" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/LogarithmPlotter-v"version"-setup.dmg" print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-"version".tar.gz" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-"version".tar.gz" print " " print " " print "
                        " @@ -74,13 +74,13 @@ END { print " " print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-v"version"-setup.exe" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-v"version"-setup.exe" print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/LogarithmPlotter-v"version"-setup.dmg" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/LogarithmPlotter-v"version"-setup.dmg" print " " print " " - print " https://artifacts.accountfree.org/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-"version".tar.gz" + print " https://artifacts.ad5001.eu/repository/apps.ad5001.eu-apps/logarithmplotter/v"version"/logarithmplotter-"version".tar.gz" print " " print " " print " " diff --git a/scripts/package-deb.sh b/scripts/package-deb.sh new file mode 100755 index 0000000..e01c370 --- /dev/null +++ b/scripts/package-deb.sh @@ -0,0 +1,43 @@ +#!/bin/bash +cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." || exit 1 + +rebuild=true + +while [ $# -gt 0 ]; do + case "$1" in + --no-rebuild) + rebuild=false + ;; + *) + box "Error: Invalid argument." + exit 1 + esac + shift +done + +if [ "$rebuild" == "true" ]; then + rm -rf build + bash scripts/build.sh +fi + +cd build/runtime-pyside6 || exit 1 + +mkdir assets +cp -r ../../assets/{native,*.svg} assets/ +cp ../../README.md . + +# Build for noble +python3 setup.py --remove-git-version --command-packages=stdeb.command sdist_dsc \ + --package logarithmplotter --copyright-file assets/native/linux/debian/copyright \ + --suite noble --depends3 "$(cat assets/native/linux/debian/depends.wheels)" --section science \ + --debian-version +wheels-1 bdist_deb + +mv deb_dist deb_dist.noble + +# Build for oracular (different dependencies) +python3 setup.py --remove-git-version --command-packages=stdeb.command sdist_dsc \ + --package logarithmplotter --copyright-file assets/native/linux/debian/copyright \ + --suite oracular --depends3 "$(cat assets/native/linux/debian/depends.packaged)" --section science \ + --debian-version +packaged-1 bdist_deb + +mv deb_dist deb_dist.oracular diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh deleted file mode 100755 index af79276..0000000 --- a/scripts/package-linux.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." - -# Building translations -cd "LogarithmPlotter/i18n/" -bash release.sh -cd ../../ - -# Deb -sudo python3 setup.py --remove-git-version --command-packages=stdeb.command sdist_dsc \ - --package logarithmplotter --copyright-file linux/debian/copyright --suite jammy --depends3 "$(cat linux/debian/depends)" --section science \ - bdist_deb - -# Flatpak building -FLATPAK_BUILDER=$(which flatpak-builder) -if [ -z $FLATPAK_BUILDER ]; then - echo "flatpak-builder not installed. Will not proceed to build flatpak." -else - cd linux - git clone https://github.com/Ad5001/eu.ad5001.LogarithmPlotter - cd eu.ad5001.LogarithmPlotter - flatpak-builder AppDir eu.ad5001.LogarithmPlotter.json --user --force-clean --install - cd ../../ -fi - -# Snapcraft building -SNAPCRAFT=$(which snapcraft) -if [ -z $SNAPCRAFT ]; then - echo "snapcraft not installed. Will not proceed to build snap" -else - snapcraft -fi diff --git a/scripts/package-macosx.sh b/scripts/package-macosx.sh index f7e9cbe..c050a32 100644 --- a/scripts/package-macosx.sh +++ b/scripts/package-macosx.sh @@ -1,25 +1,24 @@ #!/usr/bin/env bash -cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." +cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/../build/runtime-pyside6/dist" || exit 1 -VERSION=0.5.0 +VERSION=0.6.0 title="LogarithmPlotter v${VERSION} Setup" finalDMGName="LogarithmPlotter-v${VERSION}-setup.dmg" applicationName=LogarithmPlotter backgroundPictureName=logarithmplotter-installer-background.png source=Installer -cd dist rm -rf Installer mkdir -p Installer mkdir -p Installer/.background -cp ../mac/install-bg.png "./Installer/.background/${backgroundPictureName}" +cp ../../../assets/native/mac/install-bg.png "./Installer/.background/${backgroundPictureName}" cp -r LogarithmPlotter.app Installer/LogarithmPlotter.app -cp ../LICENSE.md Installer/LICENSE.md -cp ../README.md Installer/README.md +cp ../../../LICENSE.md Installer/LICENSE.md +cp ../../../README.md Installer/README.md # Calculating folder size duoutput=$(du -h Installer | tail -n1) -size=$(expr ${duoutput%M*} + 2) # +2 for allowing small space to edit. +size=$(( ${duoutput%M*} + 2)) # +2 for allowing small space to edit. echo "Creating DMG file with size ${size}M." # Adapted from https://stackoverflow.com/a/1513578 @@ -27,7 +26,7 @@ hdiutil create -srcfolder "${source}" -volname "${title}" -fs HFS+ \ -fsargs "-c c=64,a=16,e=16" -format UDRW -size ${size}M pack.temp.dmg device=$(hdiutil attach -readwrite -noverify -noautoopen "pack.temp.dmg" | \ - egrep '^/dev/' | sed 1q | awk '{print $1}') + grep -E '^/dev/' | sed 1q | awk '{print $1}') sleep 3 @@ -54,10 +53,10 @@ echo ' end tell end tell ' | osascript -chmod -Rf go-w /Volumes/"${title}" +chmod -Rf go-w "/Volumes/${title}" sync sync -hdiutil detach ${device} +hdiutil detach "${device}" hdiutil convert "pack.temp.dmg" -format UDZO -imagekey zlib-level=9 -o "${finalDMGName}" rm -f pack.temp.dmg rm -rf Installer diff --git a/scripts/package-windows.bat b/scripts/package-windows.bat deleted file mode 100644 index 777f2f9..0000000 --- a/scripts/package-windows.bat +++ /dev/null @@ -1,7 +0,0 @@ -XCOPY win\*.* dist\logarithmplotter /C /S /D /Y /I -XCOPY README.md dist\logarithmplotter /C /D /Y -XCOPY LICENSE.md dist\logarithmplotter /C /D /Y -rem Creating installer -cd dist\logarithmplotter -"C:\Program Files (x86)\NSIS\makensis" installer.nsi -cd ..\.. diff --git a/scripts/package-wine.sh b/scripts/package-wine.sh index 98209e0..89295e7 100644 --- a/scripts/package-wine.sh +++ b/scripts/package-wine.sh @@ -1,8 +1,8 @@ #!/bin/bash -cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." +cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." || exit 1 # Moving files -cp win/* README.md LICENSE.md dist/logarithmplotter/ +cp assets/native/win/* README.md LICENSE.md build/runtime-pyside6/dist/logarithmplotter/ # Creating installer -cd dist/logarithmplotter/ +cd build/runtime-pyside6/dist/logarithmplotter/ || exit 1 makensis installer.nsi diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh new file mode 100644 index 0000000..3f73e37 --- /dev/null +++ b/scripts/run-tests.sh @@ -0,0 +1,50 @@ +#!/bin/bash +cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/.." || exit 1 + +box() { + len=${#1} + echo "┌─$(printf '─%.0s' $(seq 1 "$len"))─┐" + echo "│ $1 │" + echo "└─$(printf '─%.0s' $(seq 1 "$len"))─┘" +} + +rebuild=true + +cd runtime-pyside6/tests/plugins || exit 1 +box "Testing pytest natural plugins..." +PYTHONPATH="$PYTHONPATH:." pytest --cov=natural --cov-report term-missing . || exit 1 +cd ../../../ + +while [ $# -gt 0 ]; do + case "$1" in + --no-rebuild) + rebuild=false + ;; + *) + box "Error: Invalid argument." + exit 1 + esac + shift +done + +if [ "$rebuild" == "true" ]; then + rm -rf build + bash scripts/build.sh +fi + + + +# Run python tests +rm -rf build/runtime-pyside6/tests +cp -r runtime-pyside6/tests build/runtime-pyside6 +cp -r ci CHANGELOG.md build/runtime-pyside6 +cd build/runtime-pyside6 || exit 1 +box "Testing runtime-pyside6..." +PYTHONPATH="$PYTHONPATH:." pytest --cov=LogarithmPlotter --cov-report term-missing . || exit 1 +cd ../../ + +# Run js tests +cd common || exit 1 +box "Testing common..." +npm test || exit 1 + diff --git a/scripts/sign-deb.sh b/scripts/sign-deb.sh index 6939243..05bee26 100755 --- a/scripts/sign-deb.sh +++ b/scripts/sign-deb.sh @@ -1,24 +1,26 @@ #!/bin/bash # This script is used to sign the LogarithmPlotter deb directly from it's DSC file. # Adapted from https://github.com/astraw/stdeb/issues/181 +cd "$(dirname "$(readlink -f "$0" || realpath "$0")")/../build/runtime-pyside6/" || exit 1 PPA_ARCHIVE="ppa:ad5001/logarithmplotter" -cd ../deb_dist +for dist in `echo noble oracular`; do + echo "Signing $dist deb..." + # create a temporary folder + mkdir "deb_dist.$dist/tmp" -p + cd "deb_dist.$dist/tmp" || exit 1 + rm -rf * -# create a temporary folder -mkdir tmp -p -cd tmp -rm -rf * + # DSC file variables + dsc_file="$(find ../ -regextype sed -regex ".*/*.dsc" | cut -c 4-)" + source_package_name="$(echo $dsc_file | cut -c -$(expr ${#dsc_file} - 4))" -# DSC file variables -dsc_file="$(find ../ -regextype sed -regex ".*/*.dsc" | cut -c 4-)" -source_package_name="$(echo $dsc_file | cut -c -$(expr ${#dsc_file} - 4))" + # extract and sign the files + dpkg-source -x "../$dsc_file" + cd "$(find . -type d | head -n 2 | tail -n 1 | cut -c 3-)" # go to the (only) directory. + debuild -S -sa -k"mail@ad5001.eu" -# extract and sign the files -dpkg-source -x "../$dsc_file" -cd "$(find . -type d | head -n 2 | tail -n 1 | cut -c 3-)" # go to the (only) directory. -debuild -S -sa -k"mail@ad5001.eu" - -# upload package to my PPA -dput $PPA_ARCHIVE "../${source_package_name}_source.changes" + # upload package to my PPA + dput $PPA_ARCHIVE "../${source_package_name}_source.changes" +done diff --git a/setup.py b/setup.py deleted file mode 100644 index 8a7408c..0000000 --- a/setup.py +++ /dev/null @@ -1,156 +0,0 @@ -""" - * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. - * Copyright (C) 2021-2024 Ad5001 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -""" - -import setuptools -import os -import sys -from shutil import copyfile - -print(sys.argv) - -current_dir = os.path.realpath(os.path.dirname(os.path.realpath(__file__))) - -# Check where to install by default -if "PREFIX" not in os.environ and sys.platform == 'linux': - from getopt import getopt - optlist, args = getopt(sys.argv, '', ['prefix=', 'root=']) - for arg,value in optlist: - if arg == "prefix" or arg == "root": - os.environ["PREFIX"] = value - if "PREFIX" not in os.environ and sys.platform == 'linux': - if "XDG_DATA_HOME" in os.environ: - os.environ["PREFIX"] = os.environ["XDG_DATA_HOME"] - else: - try: - # Checking if we have permission to write to root. - from os import makedirs, rmdir - makedirs("/usr/share/applications/test") - rmdir("/usr/share/applications/test") - os.environ["PREFIX"] = "/usr/share" - except: - if ".pybuild" in os.environ["HOME"]: # Launchpad building. - os.environ["PREFIX"] = "share" - else: - os.environ["PREFIX"] = os.environ["HOME"] + "/.local/share" - -from LogarithmPlotter import __VERSION__ as pkg_version - -if "--remove-git-version" in sys.argv: - pkg_version = pkg_version.split(".dev0")[0] - sys.argv.remove("--remove-git-version") - -CLASSIFIERS = """ -Environment :: Graphic -Environment :: X11 Applications :: Qt -License :: OSI Approved :: GNU General Public License v3 (GPLv3) -Natural Language :: English -Development Status :: 3 - Alpha -Operating System :: MacOS :: MacOS X -Operating System :: Microsoft :: Windows -Operating System :: POSIX -Operating System :: POSIX :: BSD -Operating System :: POSIX :: Linux -Programming Language :: Python :: 3.8 -Programming Language :: Python :: 3.9 -Programming Language :: Python :: 3.10 -Programming Language :: Python :: 3.11 -Programming Language :: Python :: 3.12 -Programming Language :: Python :: Implementation :: CPython -Topic :: Utilities -Topic :: Scientific/Engineering -""".strip().splitlines() - -def read_file(file_name): - f = open(file_name, 'r', -1) - data = f.read() - f.close() - return data - -def package_data(): - pkg_data = ["logarithmplotter.svg"] - for d,folders,files in os.walk("LogarithmPlotter/qml"): - d = d[17:] - pkg_data += [os.path.join(d, f) for f in files] - for d,folders,files in os.walk("LogarithmPlotter/i18n"): - d = d[17:] - pkg_data += [os.path.join(d, f) for f in files] - if "FLATPAK_INSTALL" in os.environ: - pkg_data += ["CHANGELOG.md"] - - return pkg_data - -data_files = [] -if sys.platform == 'linux': - data_files.append(('share/applications/', ['linux/logarithmplotter.desktop'])) - data_files.append(('share/mime/packages/', ['linux/x-logarithm-plot.xml'])) - data_files.append(('share/icons/hicolor/scalable/mimetypes/', ['linux/application-x-logarithm-plot.svg'])) - data_files.append(('share/icons/hicolor/scalable/apps/', ['logplotter.svg'])) - data_files.append((os.environ["PREFIX"] + '/applications/', ['linux/logarithmplotter.desktop'])) - data_files.append((os.environ["PREFIX"] + '/mime/packages/', ['linux/x-logarithm-plot.xml'])) - data_files.append((os.environ["PREFIX"] + '/icons/hicolor/scalable/mimetypes/', ['linux/application-x-logarithm-plot.svg'])) - data_files.append((os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/', ['logplotter.svg'])) - if len(sys.argv) > 1: - if sys.argv[1] == "install": - os.makedirs(os.environ["PREFIX"] + '/applications/', exist_ok=True) - os.makedirs(os.environ["PREFIX"] + '/mime/packages/', exist_ok=True) - os.makedirs(os.environ["PREFIX"] + '/icons/hicolor/scalable/mimetypes/', exist_ok=True) - os.makedirs(os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/', exist_ok=True) - os.makedirs(os.environ["PREFIX"] + '/metainfo/', exist_ok=True) - copyfile(current_dir + '/linux/x-logarithm-plot.xml', os.environ["PREFIX"] + '/mime/packages/x-logarithm-plot.xml') - copyfile(current_dir + '/linux/application-x-logarithm-plot.svg', - os.environ["PREFIX"] + '/icons/hicolor/scalable/mimetypes/application-x-logarithm-plot.svg') - copyfile(current_dir + '/logplotter.svg', os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/logplotter.svg') - elif sys.argv[1] == "uninstall": - os.remove(os.environ["PREFIX"] + '/applications/logarithmplotter.desktop') - os.remove(os.environ["PREFIX"] + '/mime/packages/x-logarithm-plot.xml') - os.remove(os.environ["PREFIX"] + '/icons/hicolor/scalable/mimetypes/application-x-logarithm-plot.svg') - os.remove(os.environ["PREFIX"] + '/icons/hicolor/scalable/apps/logplotter.svg') - -setuptools.setup( - install_requires=([] if "FLATPAK_INSTALL" in os.environ else ["PySide6-Essentials"]), - python_requires='>=3.8', - - name='logarithmplotter', - version=pkg_version, - - description='2D plotter software to make BODE plots, sequences and repartition functions.', - long_description=read_file("README.md"), - keywords='logarithm plotter graph creator bode diagram', - - author='Ad5001', - author_email='mail@ad5001.eu', - - license=('GPLv3'), - url='https://apps.ad5001.eu/logarithmplotter/', - - classifiers=CLASSIFIERS, - zip_safe=False, - packages=["LogarithmPlotter", "LogarithmPlotter.util"], - - package_data={ - 'LogarithmPlotter':package_data(), - }, - include_package_data=True, - data_files = data_files, - entry_points={ - 'console_scripts': [ - 'logarithmplotter = LogarithmPlotter.logarithmplotter:run', - ], - } -) - diff --git a/snapcraft.yaml b/snapcraft.yaml index 0fbd1c7..12b41d2 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,17 +1,16 @@ name: logarithmplotter title: LogarithmPlotter -version: '0.5.0' +version: '0.6.0' summary: Create and edit Bode plots confinement: strict -base: core20 +base: core22 grade: stable -icon: LogarithmPlotter/logarithmplotter.svg +icon: assets/logarithmplotter.svg adopt-info: linuxfiles license: GPL-3.0+ architectures: - - build-on: amd64 - run-on: amd64 + - amd64 plugs: gtk-3-themes: @@ -58,21 +57,26 @@ parts: # - fcitx-frontend-gtk3 # - libgtk2.0-0 launchers: - source: linux/snapcraft/launcher/ + source: assets/native/linux/snapcraft/launcher/ plugin: dump organize: '*': bin/ linuxfiles: - source: linux/ + source: assets/native/linux/ plugin: dump parse-info: [eu.ad5001.LogarithmPlotter.metainfo.xml] organize: logarithmplotter.desktop: usr/share/applications/logarithmplotter.desktop x-logarithm-plot.xml: usr/share/mime/packages/x-logarithm-plot.xml application-x-logarithm-plot.svg: usr/share/mime/packages/application-x-logarithm-plot.svg + filetypeicon: + source: assets/ + plugin: dump + organize: + logplotterfile.svg: usr/share/mime/packages/application-x-logarithm-plot.svg logarithmplotter: plugin: python - source: . + source: build stage-packages: - breeze-icon-theme # Latex dependencies @@ -146,7 +150,7 @@ parts: source: . plugin: dump organize: - CHANGELOG.md: lib/python3.8/site-packages/LogarithmPlotter/util/ + CHANGELOG.md: lib/python3.12/site-packages/LogarithmPlotter/util/ apps: logarithmplotter: