Compare commits

...

258 commits

Author SHA1 Message Date
c74c2fb747
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-27 22:30:36 +02:00
c2eae30bd6
Updating qmldirs 2024-09-27 22:26:15 +02:00
80cea6d280
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 95.1% (256 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2024-09-27 18:16:19 +02:00
f8ce98d4ad
Removing logplotter.svg file (duplicate) + adding PySide6-Addons as dependency 2024-09-27 04:15:34 +02:00
fbb85083c1
Translated using Weblate (German)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-25 22:15:42 +02:00
gallegonovato
17b6e40d60
Translated using Weblate (Spanish)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-24 19:15:41 +02:00
6a1f01ba1f
Better interfaces!
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-24 00:04:11 +02:00
4c1403c983
Implementing modules requiring interfaces 2024-09-23 20:55:48 +02:00
8c8964e75e
Renaming internal ExprEval Expression class to ExprEvalExpression
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-23 18:37:57 +02:00
45dff33bb5
Moving mathlib and historylib into math/index and history/index
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-23 18:32:11 +02:00
bac802ec5b
Using translated property name in usage error messages.
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-23 18:23:45 +02:00
34a436fe49
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-23 18:19:09 +02:00
cb2ffcf77a
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 94.7% (255 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2024-09-23 18:19:00 +02:00
a27d3109ed
Translated using Weblate (French)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2024-09-23 18:19:00 +02:00
a998c52eec
Translated using Weblate (Spanish)
Currently translated at 98.8% (266 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-23 18:19:00 +02:00
8b8a280b37
Translated using Weblate (German)
Currently translated at 99.6% (268 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-23 18:19:00 +02:00
3befff41fa
Translated using Weblate (English)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2024-09-23 18:18:59 +02:00
9810435456
Updating usages 2024-09-23 18:18:56 +02:00
84a65cd1fc
Testing the Helper.
All checks were successful
continuous-integration/drone/push Build is passing
We're now getting to 88% coverage on python. I don't think I can get it much higher between the statements that pytest doesn't count,
the ones which aren't easy to reproduce in test env (eg no internet connection), and the ones essential to the app's startup workflow.
2024-09-23 05:58:29 +02:00
5bdf81b2ed
Adding new python 2024-09-23 04:35:10 +02:00
c66d08b352
Moving Modules to separate directory
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-22 21:59:11 +02:00
a4e9ad7f5a
Removing usage of "Modules" to refer to modules in JavaScript (using imports instead) 2024-09-22 19:35:40 +02:00
a88be86350
Adding translations to current branch
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-22 19:18:18 +02:00
4baf4f8aa6
Updating some keys (should be the last time) 2024-09-22 19:17:24 +02:00
1f2b8e5c4b
Translated using Weblate (French)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2024-09-22 19:13:12 +02:00
gallegonovato
8eac624bb2
Translated using Weblate (Spanish)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-22 19:13:12 +02:00
976088ad03
Translated using Weblate (German)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-22 19:13:12 +02:00
dcb63c48e9
Translated using Weblate (English)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2024-09-22 19:13:12 +02:00
dcc47104ef
Changing all JS qsTr to qsTranslate (better compatibility moving forward) 2024-09-22 19:10:23 +02:00
a32d480b43
Renaming objects to their proper english names. 2024-09-22 18:58:19 +02:00
e0601379ba
Update README
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-22 18:44:45 +02:00
3c42cbb6d0
Updating translations for new expr-eval
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-22 18:28:36 +02:00
cc0f277da7
Nearly rewrote expr-eval to be compatible as an ECMAScript module, the last blocker for many bugs and JS tests!
All checks were successful
continuous-integration/drone/push Build is passing
commit f91538de446ef0e497ec7e87e2729f504a4172cb
Author: Ad5001 <mail@ad5001.eu>
Date:   Sun Sep 22 05:55:12 2024 +0200

    Converted expr-eval back to regular JS!

commit 23c346f6c65b5b5c4bb4ad610f0554bd1d9a3700
Author: Ad5001 <mail@ad5001.eu>
Date:   Sun Sep 22 05:06:59 2024 +0200

    Reformatting

commit 66608c980fd44f26ae8e6855ecd5fc3e7db55a7b
Author: Ad5001 <mail@ad5001.eu>
Date:   Sun Sep 22 05:04:27 2024 +0200

    Removed all 'var's

commit 545886fd38c99cf11bc576caa40bec0d7fe0ac30
Author: Ad5001 <mail@ad5001.eu>
Date:   Sun Sep 22 04:53:32 2024 +0200

    Removing function definition

commit 489602b24bb70cb6ad782871e269a22c92fcf072
Author: Ad5001 <mail@ad5001.eu>
Date:   Sun Sep 22 04:51:21 2024 +0200

    Removing semicolons

commit 3ee069edbeb8ebfb5c7d15d319014f7a085ff623
Author: Ad5001 <mail@ad5001.eu>
Date:   Sun Sep 22 04:49:32 2024 +0200

    Converting all classes to ECMAScript classes
2024-09-22 05:57:36 +02:00
1299fe469d
Updating README
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-21 03:21:49 +02:00
5d0f3eec56
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 95.9% (258 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2024-09-21 02:47:17 +02:00
gallegonovato
c8ada79776
Translated using Weblate (Spanish)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-21 02:47:17 +02:00
43a0aa3529
Translated using Weblate (English)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2024-09-21 02:47:17 +02:00
9c76d469d6
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-20 23:59:56 +02:00
3c57f207b6
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 96.2% (259 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2024-09-20 23:59:20 +02:00
77d03e5837
Translated using Weblate (Norwegian Bokmål)
Currently translated at 71.7% (193 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/nb_NO/
2024-09-20 23:59:20 +02:00
ed5b7e95a6
Translated using Weblate (French)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2024-09-20 23:59:20 +02:00
2bf7df12a8
Translated using Weblate (Spanish)
Currently translated at 98.5% (265 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-20 23:59:20 +02:00
0e23d9181e
Translated using Weblate (German)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-20 23:59:20 +02:00
7e698eda32
Translated using Weblate (English)
Currently translated at 100.0% (269 of 269 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2024-09-20 23:59:20 +02:00
6e92c428f9
Removing vanished translation from Spanish file 2024-09-20 23:52:05 +02:00
b18e1ca56e
Update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Cleanup translation files" hook in Weblate.

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/
2024-09-20 23:51:37 +02:00
1ef1d27452
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 96.1% (298 of 310 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2024-09-20 23:51:00 +02:00
7c12b757ee
Translated using Weblate (Norwegian Bokmål)
Currently translated at 68.0% (211 of 310 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/nb_NO/
2024-09-20 23:51:00 +02:00
484888f80f
Translated using Weblate (French)
Currently translated at 100.0% (310 of 310 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2024-09-20 23:51:00 +02:00
4e265b66c2
Translated using Weblate (Spanish)
Currently translated at 98.3% (305 of 310 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-20 23:50:59 +02:00
6d3f4ba372
Translated using Weblate (German)
Currently translated at 100.0% (310 of 310 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-20 23:50:56 +02:00
2321a1076c
Translated using Weblate (English)
Currently translated at 100.0% (310 of 310 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2024-09-20 23:50:14 +02:00
dd2ae7a2c8
Implemented workaround for QTBUG-123819 (lupdate not parsing *.mjs files)
All checks were successful
continuous-integration/drone/push Build is passing
Woohoo! No longer any duplicate and vanished translations (might cause a few that need to be checked, but translations should be properlerly reused).
2024-09-20 23:44:29 +02:00
2fc9bdee86
Fixing import of Objects
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-20 22:58:37 +02:00
67190e1b4c
Fixing rendering of LaTeX for position changing.
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-20 22:14:52 +02:00
8ab461ff72
Removing ObjectsCommon tests
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-20 19:29:02 +02:00
9b5356f8e7
Fixing another bug with X Cursor relating to rounding to a superior precision than what the number originally has.
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-20 19:28:11 +02:00
370402f303
Fixing a particularly smelly bug in regard to X Cursor targetting causing issues with certain functions. 2024-09-20 19:02:10 +02:00
7f57ed13c7
Removing ObjectsCommon API 2024-09-20 17:56:59 +02:00
325eef57e2
Fixing ES flag in Thanks 2024-09-20 17:40:42 +02:00
956de5f9e3
FIxing building location
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-19 21:57:41 +02:00
48427b7923
Fixing lrelease installation
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-19 21:33:29 +02:00
fceb5e711c
Upgrading CI images
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-19 20:21:04 +02:00
836d13386e
Adding building translations in CI.
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-19 18:19:35 +02:00
c3daa92280
Testing for modules existance in globals.
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-19 02:34:49 +02:00
d566c285fd
Adding values and types to PyJS 2024-09-19 02:20:42 +02:00
a9e47dbc17
Adding main tests (note: full main app may not be completely tested though) 2024-09-19 01:52:23 +02:00
4a5756f24d
Update translation files
All checks were successful
continuous-integration/drone/push Build is passing
Updated by "Cleanup translation files" hook in Weblate.

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/
2024-09-18 22:57:16 +00:00
a250f532d9
Adding pyside6-addons as dependency until https://bugreports.qt.io/browse/PYSIDE-2871 is resolved.
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-19 00:51:28 +02:00
e68411e93c
Fixing typos 2024-09-19 00:51:16 +02:00
b55b2a11fe
Starting main tests 2024-09-19 00:50:47 +02:00
7dd64c8e31
Fixing issue with Settings no longer being translated.
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-19 00:22:03 +02:00
8f95479689
Making pyinstaller command multiline. 2024-09-18 23:26:07 +02:00
9712ff15bb
Giving script a GUI env lest Qt dumps
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-18 23:20:00 +02:00
a3cf99f3af
Filtering report paths
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-18 23:19:00 +02:00
2ee7da7995
Fixing CI building
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-18 23:11:44 +02:00
3104dea918
Trying to fix building
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-18 23:03:44 +02:00
cb0db7fae1
Running tests from script
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-18 22:52:53 +02:00
4ac7de48b0
Latex tests 2024-09-18 22:51:23 +02:00
5211821a84
Setting up Poetry for dependency management.
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-18 18:15:30 +02:00
29e48e284c
Adding pytest to drone ci
Some checks failed
continuous-integration/drone/push Build is failing
2024-09-18 18:01:51 +02:00
4759bdcd33
Continuing python tests
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-18 00:31:17 +02:00
9072e94d14
Starting python tests
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-18 00:30:58 +02:00
b50d56d511
Improving python coding format 2024-09-17 22:43:24 +02:00
1bf175b09c
Adding type hints 2024-09-17 22:26:17 +02:00
52fd95551c
Adding translations (again)
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-17 22:02:19 +02:00
769ad22ea6
Cleaning up and update Linux data
+ Removing (no longer working) local install for linux.

+ Moving appstream changelog generation to scripts.

+ Updating debian package information.
2024-09-17 21:58:12 +02:00
gallegonovato
8b36ad81ab
Translated using Weblate (Spanish)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-17 21:38:38 +02:00
1e32faa1d1
Adding translations
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-17 20:38:01 +02:00
78d7e6f310
Bumping required Python version to v3.9 + adding new Polyfills for IDEs. 2024-09-17 20:36:46 +02:00
ab45109206
Fixing typos. 2024-09-17 19:21:29 +02:00
12b48a2e74
Translated using Weblate (French)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2024-09-17 02:20:41 +02:00
1bc3aaf53b
Translated using Weblate (Spanish)
Currently translated at 99.6% (308 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-17 02:20:40 +02:00
b52cc1de29
Translated using Weblate (German)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-17 02:20:40 +02:00
214dd687b1
Translated using Weblate (Hungarian)
Currently translated at 98.0% (303 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2024-09-17 02:19:33 +02:00
6b4da2b061
Translated using Weblate (French)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2024-09-17 02:19:33 +02:00
gallegonovato
bcf76dcd28
Translated using Weblate (Spanish)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-17 02:19:33 +02:00
207a2254f3
Translated using Weblate (German)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-17 02:19:32 +02:00
0f39930b88
Translated using Weblate (English)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2024-09-17 02:19:32 +02:00
d4e97f2860
Moving last methods from LogGraphCanvas to Canvas JS module. 2024-09-17 02:14:47 +02:00
a2fa16949a
Fixing some bugs, including outzooming too much. 2024-09-17 01:58:34 +02:00
91e4220397
Upping logarithmic graduation limit to 309 (maximum before Infinity, probably some dragons to be had there) 2024-09-17 01:35:47 +02:00
d7fe760900
Slightly improving phases sum cleanness.
Didn't have quite as many bugs, but isn't quite as clean as the magnitude one as of now
2024-09-17 01:01:46 +02:00
a16f02fd5f
Refractoring the magnitude sum object cache calculation which should (hopefully) reduce bugs and improve maintainability. 2024-09-17 00:44:14 +02:00
601efc6122
Fixing issue with LocationPickOverlay snap to grid. 2024-09-16 23:52:42 +02:00
51807a80d0
Fixing position of filter item 2024-09-16 23:41:34 +02:00
ef14db8bbb
Fixing issue with object's execution category 2024-09-16 23:33:59 +02:00
7e0262e4fe
Making Objects List Latex render async 2024-09-16 23:33:08 +02:00
70f1c03cb6
Making history items being rendered with LaTeX (e.g. positions and properties) asynchronous. 2024-09-16 22:56:54 +02:00
8d6891c4f0
Starting async LaTeX rendering. 2024-09-16 22:05:17 +02:00
c9a597ea82
Translated using Weblate (Norwegian Bokmål)
Currently translated at 69.2% (214 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/nb_NO/
2024-09-16 21:17:26 +02:00
c1a468148d
Translated using Weblate (French)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2024-09-16 21:17:26 +02:00
59d4d2f728
Translated using Weblate (Spanish)
Currently translated at 80.5% (249 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-16 21:17:26 +02:00
gallegonovato
9ca48bbcfa
Translated using Weblate (Spanish)
Currently translated at 80.5% (249 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-16 21:17:25 +02:00
3994d8d49d
Translated using Weblate (German)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-16 21:17:25 +02:00
53124fc8d7
Translated using Weblate (Norwegian Bokmål)
Currently translated at 69.9% (216 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/nb_NO/
2024-09-16 21:01:51 +02:00
35fef4cb1e
Translated using Weblate (French)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2024-09-16 21:01:51 +02:00
f1e2278695
Translated using Weblate (Spanish)
Currently translated at 75.7% (234 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-16 21:01:51 +02:00
gallegonovato
6a1f0013d6
Translated using Weblate (Spanish)
Currently translated at 75.7% (234 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-09-16 21:01:51 +02:00
6626362d23
Translated using Weblate (German)
Currently translated at 99.6% (308 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-09-16 21:01:51 +02:00
107cea1308
Translated using Weblate (English)
Currently translated at 100.0% (309 of 309 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2024-09-16 21:01:51 +02:00
7b0bc4469f
Improving message for missing packages in LaTeX. 2024-09-16 20:15:57 +02:00
e3eea751cb
Improving ThanksTo (to add scrolling) 2024-09-16 20:05:57 +02:00
7b76a8fe08
Improving testing of LaTeX configuration. 2024-09-16 20:01:35 +02:00
4b1cf2cd9d
Improving linux metainfo generation. 2024-09-16 20:00:56 +02:00
88797d00be
Adding new translations for v0.6.0 + adding Spanish credits 2024-09-16 19:34:35 +02:00
f98852c336
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter 2024-09-15 21:34:50 +02:00
606ec428ab
Fixing issue with setting JS objects being serialized into ListViews.
+ Fixing broken links on Linux
2024-09-15 21:30:44 +02:00
ovari
136ac4c7bc
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (304 of 304 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2024-04-21 08:07:05 +02:00
gallegonovato
87d8882db1
Translated using Weblate (Spanish)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 80.2% (244 of 304 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/es/
2024-04-03 23:01:47 +02:00
915c6b5246
Setting version to v0.6.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-03 22:05:57 +02:00
54f9802975
Adding default canvas settings
All checks were successful
continuous-integration/drone/push Build is passing
+ Getting rid of Qt5COmpat for Icon.
+ Fixing some (non production) bugs.
2024-04-03 21:39:06 +02:00
997a1645a0
Removing Settings submenu.
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 22:50:43 +02:00
fefb0f92b0
New, rewamped Greet Screen. 2024-04-02 22:49:56 +02:00
665906ecb3
New preferences panel. 2024-04-02 22:11:54 +02:00
781a0f4aae
Enforcing type safety at export
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-02 00:11:56 +02:00
5f8c756dc7
Enforcing type safety at import. 2024-04-01 23:48:57 +02:00
8e75f13e9a
Removing vanished translations.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-29 17:56:09 +01:00
82e8413e56
Creating IO JS module.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-29 17:53:10 +01:00
73cba85592
Creating Canvas JS Module.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-29 01:55:13 +01:00
861bb001c9
Changing version, making Helper objects globals, adding their polyfills.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-28 22:52:14 +01:00
08fea34366
Converting as many JS libraries to ECMAScript modules.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-28 03:09:37 +01:00
a6fcf6da19
Fixing sequence no longer working with current rendering method.
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-25 18:58:07 +01:00
f730121047
Fixing the icon on wayland, building on MacOS, and date on changelog
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-06 18:52:58 +01:00
80f1077b35
Updating snapcraft
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-11 23:28:42 +01:00
33c790b113
Updating debian information
Some checks reported errors
continuous-integration/drone/push Build encountered an error
continuous-integration/drone Build is failing
2024-01-11 21:20:23 +01:00
f9b0bb99ce
Updating appstream
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2024-01-11 20:15:25 +01:00
557592708f
Fixing building for debian 2024-01-11 19:56:25 +01:00
1f72c76fb4
Releasing v0.5.0
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2024-01-11 19:24:06 +01:00
66b4c8df90
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2024-01-11 02:13:44 +01:00
70b1ac09f7
Translated using Weblate (German)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (271 of 271 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2024-01-11 02:13:33 +01:00
2d6389bfd5
Updating images for CI 2024-01-11 01:49:29 +01:00
d0851b819f
Updating windows installer
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-11 01:15:43 +01:00
cff994d9d7
Fixing macos building
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-11 00:59:01 +01:00
fde2526c54
Updating copyrights
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-11 00:11:09 +01:00
e5fe8afd06
Fixing issue with derivative and integrals
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-11 00:09:35 +01:00
f9a7443631
New icons for macos and windows
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-10 23:42:17 +01:00
874046960f
Changing linux descriptions
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-10 23:32:47 +01:00
d53573d468
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
Some checks failed
continuous-integration/drone/push Build is failing
2024-01-10 22:57:33 +01:00
1da1221c51
Updating icons 2024-01-10 22:57:18 +01:00
ovari
8bb147cbee
Translated using Weblate (Hungarian)
Some checks failed
continuous-integration/drone/push Build is failing
Currently translated at 100.0% (271 of 271 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2023-10-20 04:11:48 +00:00
bd2e692285
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-18 16:06:26 +02:00
298acaace2
Reworking SVG object icons to use default system sans-serif font.
Provides better native integration at the cost of uniformisation.
2023-10-18 16:06:11 +02:00
5284c71ee7
Translated using Weblate (French)
Some checks failed
continuous-integration/drone/push Build is failing
Currently translated at 100.0% (271 of 271 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2023-10-11 15:01:31 +00:00
b8d679e118
Translated using Weblate (English)
Currently translated at 100.0% (271 of 271 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2023-10-11 15:01:30 +00:00
22106f97b5
Adding new strings
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-10 02:13:17 +02:00
bf211df67d
Adding function usage to auto complete.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-10 01:58:55 +02:00
803416d08d
New system for integrals the same as for derivatives.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-10 01:33:57 +02:00
d991deee7b
Reworking the derivative function, removed assignement in parser.
The new derivative now supports executable elements.
2023-10-10 00:53:35 +02:00
ed4d30573c
Adding usage for functions. 2023-10-10 00:05:19 +02:00
3f1d089a78
Minor modifications, adding usage to derivative. 2023-10-09 23:28:29 +02:00
4b692894f2
Removing old and unused bits for previously planned new parser, that would have remained unused.
Some checks failed
continuous-integration/drone/push Build is failing
Some bits (like the tokenizer and the reference) are still used for the syntax highlighting and autocomplete though.
2023-10-09 23:27:30 +02:00
062fde6b16
Updating build script for newest pyinstaller version. 2023-10-09 22:26:50 +02:00
2b5d442d3a
Changing v0.4.1 to v0.5.0 considering the number of changes.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-09 20:29:21 +02:00
038dd9f4a8
Fixing the (previously very broken) text renderer when using symbols.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-09 18:50:34 +02:00
cf754a7a34
Better handling of constants in simplified expressions. 2023-10-09 18:37:28 +02:00
bc35b18da0
Insert character popup's characters are now contextually aware.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-09 17:55:06 +02:00
7542d63121
Adding new characters to insert chars popup. 2023-10-09 17:20:32 +02:00
4fe1086d68
Adding new behavior for object creation to default on picker when pickable.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-08 22:36:23 +02:00
a66ccd1319
Changing version to v0.4.1
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-08 18:34:51 +02:00
9879e7fbc9
Fixing changelog 2023-10-08 18:34:09 +02:00
999999832a
Taking edge cases into account into new drawing system.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-08 17:22:21 +02:00
7408520819
Reworking continuous functions' rendering to make it faster.
Some checks failed
continuous-integration/drone/push Build is failing
+ Diminishing pixel precision from 1 to 10.
+ The change is barely noticable by the human eye in most functions.
+ Reworked domain checking to keep the pixel perfect precision.

The only visual degradations are functions with very high y variations for very small x variations (like cos and sin on log scale).
2023-10-08 17:00:12 +02:00
9f2b08b938
Fixing "Unknown variable ∞." message.
It occured when using a domain with the infinity symbol.
2023-10-08 16:31:01 +02:00
3039aade29
Fixing some issues with infinity in domains. 2023-10-08 16:24:16 +02:00
d6a83b0f4b
Adding the ability to scroll to zoom.
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-07 23:38:51 +02:00
0b5aa36c23
Adding view position changer overlay. 2023-10-07 19:11:19 +02:00
25347a8eff
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
Some checks failed
continuous-integration/drone/push Build is failing
2023-10-07 02:54:36 +02:00
ovari
d6d7f3a511
Translated using Weblate (Hungarian)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (267 of 267 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2023-06-11 06:48:17 +02:00
e67619771b
Fixing macOS building script. 2023-05-27 11:09:13 +02:00
df3e7d4796
I thought I pushed the updated script for macOS, but it looks like I didn't.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-27 10:36:37 +02:00
19e403ce98
Forgot to remove auto reappearance from greet screen.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-27 10:11:42 +02:00
273d5539ec
Forgot to update the debian changelog. 2023-05-27 10:11:19 +02:00
5e7dbb8fa6
Adding changelog 2023-05-27 10:07:40 +02:00
1e30bee220
Updating snapcraft building
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 20:11:41 +02:00
ebf07dccfa
Disabling interactive thanks dialog.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 18:05:05 +02:00
95546bd14e
Qt6 testing and porting on macOS.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 14:54:28 +02:00
59145905ea
Changing dependencies to only PySide6's essentials.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 08:16:48 +02:00
66da751b36
Updating windows builders for pyside6 (halving the bundled installer size)
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 06:25:29 +02:00
deb4355756
Preparing debian package for PySide6
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-25 19:40:06 +02:00
8358cc142c
Fixing localisation issue on position picker
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-25 15:08:12 +02:00
62e70885e0
Changing default themes for the ones available in Qt6
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-25 12:15:35 +02:00
6fa0aea0e5
Disabling auto detect of qtquick style if manual set.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-25 10:27:27 +02:00
08edd7be14
Merge branch 'master' of https://git.ad5001.eu/Ad5001/LogarithmPlotter
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-24 11:49:59 +02:00
16effe064c
Disabling generation of HDPI latex pngs, as they aren't used in PySide6. 2023-05-24 11:49:45 +02:00
98f01845e1
Translated using Weblate (French)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (267 of 267 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2023-05-24 10:13:06 +02:00
87b4787a2b
Translated using Weblate (German)
Currently translated at 100.0% (267 of 267 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2023-05-24 10:13:05 +02:00
a610d0949e
Translated using Weblate (English)
Currently translated at 100.0% (267 of 267 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2023-05-24 10:13:05 +02:00
803670635b
Forgot to disable Greet Screen seen saving.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-24 10:00:47 +02:00
09c1a44150
Adding escape shortcut for base dialog.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-24 09:58:58 +02:00
40743e54c3
Allow the user to choose what he wants to pick.
All checks were successful
continuous-integration/drone/push Build is passing
Updating translations.
2023-05-24 08:00:58 +02:00
9239eac78a
Fixing several function calls with no argument errors.
All checks were successful
continuous-integration/drone/push Build is passing
+ Functions max and min now throw an error if no arguments are provided.
+ Objects called now throw an error if no argument is provided.
+ Fixing tokenizer's seek error reporting system (leads to very rare types of errors).
+ Removing expression simplifications that could make the app freeze when no arguments are provided to a function.
2023-05-24 07:43:40 +02:00
35ce1c4824
Adding error handling for function argument errors. 2023-05-24 07:03:12 +02:00
75e70903f1
Fixing coloration in dark theme of autocomplete categories. 2023-05-24 06:47:34 +02:00
016a21ecb4
Adding new translations
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-24 04:18:55 +02:00
f7dd40d2f5
Changing omega zero icon file name to fix issue with finding it.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-24 04:00:09 +02:00
d1843b455a
Fixing issue with sums & latex variables. 2023-05-24 03:58:13 +02:00
6abbbe5e61
Adding picker for properties (x,y,labelX)
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-24 03:51:42 +02:00
5cef8e23fa
Fixing several issues with repartition
+ Underscores being automatically removed from the name the moment it's edited.
+ Fixing issue with function name change.
2023-05-24 03:17:21 +02:00
d6dee6a2be
Fixing color picker not working
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-24 02:48:35 +02:00
3da51430f1
Translated using Weblate (French)
All checks were successful
continuous-integration/drone/push Build is passing
Currently translated at 100.0% (258 of 258 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/
2023-05-23 14:13:36 +02:00
ae930be621
Translated using Weblate (German)
Currently translated at 100.0% (258 of 258 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/
2023-05-23 14:13:35 +02:00
abae3ef94b
Translated using Weblate (English)
Currently translated at 100.0% (258 of 258 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/
2023-05-23 14:13:35 +02:00
82d53748b3
Forgot to change shiboken2 to shiboken6 in windows installer.
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-23 13:03:00 +02:00
fce2a5ba0d
Adding version freeze for changelog
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-23 12:51:47 +02:00
3904dc0217
Removing last instances of PySide2, updating copyrights & version to v0.4
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-23 12:42:43 +02:00
7a80455e25
Fixing the usage of symbols in text.
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-22 11:02:34 +02:00
20c1ed005e
Removing warning
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-22 09:49:20 +02:00
d8534d1e0d
Adding bottom borders to dialogs 2023-05-22 09:31:43 +02:00
4be92a07e3
Adding revision to CI building image.
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-22 07:43:44 +02:00
b143f7e10d
Disable Windows testing (issue between PySide6, wine & Xvfb).
Some checks failed
continuous-integration/drone/push Build is failing
Reinstating windows building.
2023-05-22 07:29:15 +02:00
0ac690d0c1
Fixing cursor from expression editor.
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-22 07:22:33 +02:00
37150b5823
Fixing CI image
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-22 06:18:03 +02:00
0539f988e2
Adding new strings
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-05-22 06:17:08 +02:00
bf067b7e7c
Fixing object edition dialog height
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2023-05-22 06:11:15 +02:00
9f20a80228
Merge branch 'qt6' 2023-05-22 05:57:03 +02:00
5ef8cac1c0
Adding color schemes for expressions 2023-05-22 05:56:34 +02:00
7ec80e6682
Custom color schemes! 2023-05-22 05:17:12 +02:00
43d78e17e5
Improving LaTeX rendering DPI. 2023-05-22 04:21:53 +02:00
93308f2bfa
Forgot adding setting icon 2023-05-22 03:41:50 +02:00
508f316bc5
Better pick location settings overlay 2023-05-22 03:41:20 +02:00
424eef6e17
Fixing opening file in settings, updating CI images to PySide6. 2023-05-22 01:50:22 +02:00
f40c242877
Bumping copyright 2023-05-22 00:19:58 +02:00
aecc02c606
Updating to Qt6 2023-05-22 00:15:09 +02:00
78ffc8c645
Changing RegExpValidator to RegularExpressionValidator. 2023-05-21 22:32:49 +02:00
98f26d4919
Removing unclosed tag.
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-28 22:02:52 +02:00
65c42abd26
Updating metadata images for the future.
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-28 21:53:19 +02:00
ba98fc3611
Fixing appstream data.
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-28 19:26:00 +02:00
839f8e9f36
Updating changelog to not overflow the popup.
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-28 18:15:10 +02:00
fe13351e99
Removing flatpak submodule.
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-28 17:38:41 +02:00
225ae67834
Releasing v0.3.0!
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-28 16:41:20 +02:00
6307855b87
Adding v0.3.0 changelog.
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-28 16:36:18 +02:00
3a36e41738
Fixing MacOS building version string.
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-24 04:51:25 +02:00
ee26bc04c5
Fixing syntax error.
All checks were successful
continuous-integration/drone/push Build is passing
2022-10-23 23:26:34 +02:00
ovari
02c7cb8afc
Translated using Weblate (Hungarian)
Some checks failed
continuous-integration/drone/push Build is failing
Currently translated at 100.0% (253 of 253 strings)

Translation: LogarithmPlotter/LogarithmPlotter
Translate-URL: https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/
2022-10-23 10:05:26 +02:00
0a064694f5
Adding new functions for autocomplete, and disabling the ones not implemented in expr-eval.
Some checks failed
continuous-integration/drone/push Build is failing
2022-10-22 18:26:36 +02:00
b0b77834a8
Fixing inability to open and load files due to greet screen fixes. 2022-10-22 14:17:52 +02:00
fb1c4c0de7
Using Screen.devicePixelRatio as image depth.
Fixes bluriness of images on HDPI screens and disabling it on those who don't need it.
2022-10-22 14:16:15 +02:00
177 changed files with 16095 additions and 10380 deletions

8
.gitignore vendored
View file

@ -16,20 +16,20 @@ linux/flatpak/.flatpak-builder
*.jsc *.jsc
*.qmlc *.qmlc
*.log *.log
**/*.dxvk-cache
.DS_Store .DS_Store
**/.DS_Store **/.DS_Store
**/__pycache__/ **/__pycache__/
.ropeproject .ropeproject
.vscode .vscode
*.kdev4
.kdev4
.coverage
build build
docs/html docs/html
.directory .directory
*.kdev4
*.lpf *.lpf
*.lgg *.lgg
*.spec *.spec
.kdev4
AccountFree.pro
AccountFree.pro.user
*.egg-info/ *.egg-info/
*.tar.gz *.tar.gz

3
.gitmodules vendored
View file

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

View file

@ -1,5 +1,123 @@
# Changelog # Changelog
## v0.5.0 (11 Jan 2024)
**New**
* New, reworked application icon.
* Graph is now mouse interactive:
* You can now drag to move and scroll to zoom!
* Builtin functions now provide usage when used in the autocomplete of the expression editor.
**Changes**
* When creating an object that can be positioned, new default behavior is to pick first instead of opening object settings.
* Icons with text now use the SVG's text element, allowing them to integrate better with the system's default font.
* Special characters popup is now context aware (e.g. no sub/supscript symbols in expressions).
* New symbols in special characters popup.
* Integrals and derivatives can now be provided with an executable object (e.g. Functions) instead of strings as function.
* New description on Linux.
**Fixed bugs**
* Fixing ∞ 'variable' in domains and expressions.
* Several other bugs related to constants in expresions were fixed as well.
* Builtin functions now send an error message when not provided with the proper arguments.
**Internal changes**
* Updated to PySide6 v6.6.1.
* Reworked continuous functions' rendering to make it faster.
* Removed old bits from an unfinished new parser that weren't used.
## v0.4.0 (27 May 2023)
**Changes**
* Fully ported to PySide6 (Qt6).
* Greet screen settings are now scrollable.
* Changelog is now freezed to current version.
**New**
* Customizable color schemes for expressions.
* New, rewamped and improved picked location overlay settings:
* It's now possible to disable picking x or y when setting a location.
* Properties which are related to positioning (X, Y, Label's X position) can now be set using the picker.
* Visual redesign that enhances readability of settings.
* There is now a button to hide picker settings.
**Fixed bugs**
* Cursors in expression are now easier to see.
* Symbols in LaTeX rendered Texts cause the LaTeX renderer to crash.
* Underscores in distribution names are automatically removed if the name is modified.
* Autocomplete categories now properly respect theme colors.
* Functions in expressions (like indexOf, map...) now properly send errors when the arguments are of the wrong type or count.
* Executable Objects called (like functions, bode magnitures, phases...) now send an error if provided with no arguments.
* Function calls with no argument no longer make LogarithmPlotter crash under certain circumstances.
* Thank you dialog's lists are no longer draggable.
**Internal changes**
* A lot of inner changes led by porting to Qt6, fixing a lot of bugs at the same time.
* Disabled auto detect of visual theme if the `QT_QUICK_CONTROLS_STYLE` environment variable is set.
* (macOS, Windows, Flatpak) Drastically reducing installer sizes (more than halved).
* (Launchpad/Ubuntu) Using custom built packages of PySide6, meaning smaller installation and distro dependency.
## v0.3.0 (28 Oct 2022)
**New**
* New completely revamped expression editor:
* Automatic closing of parentheses and brackets (can be disabled in settings).
* Syntax highlighting (can be disabled in the settings).
* Autocompletion is now available (for functions, variables and constants, object names and properties) (can be disabled in the settings).
* Object properties can now be used in expressions (e.g. if you have a point named A, you can use A.x to access its x value).
* Executable objects can be now be used in expressions (e.g. if you have a function named 'f', it's accessible using `f(<value>)`).
* LaTeX-rendered formulas are now used in the Objects and History tabs when LaTeX rendering is enabled.
* Errors in formulas are now reported in message boxes.
**Changes**
* The Object Editor dialog has been completely reworked internally, resulting in notable performance improvements.
* Vast improvements to the objects system: names can no longer be shared amongst different objects.
* Disabled access to custom variable and function definition in expressions (can cause issues and vulnerabilities)
* When using the set position cursor, the position change is now saved a single history action.
* Distribution are now prefixed with an 'F_' to prevent confusion with X Cursors.
**Added translations**
* Autocompletion categories (English, French, German, Hungarian).
* Expression editor settings (English, French, German, Hungarian).
* Expression syntax errors (English, French, German, Hungarian).
* On top of the above:
* Hungarian: v0.2.0 added text (thanks @ovari!)
* Spanish: Menu bars (thanks @Sergio Varela)
* You can contribute to translation on [Weblate](https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/#repository).
**Fixed bugs**
* Fixing Texts not being properly recognized as texts when saving.
* Text's 'Disable LaTeX' property is now properly saved.
* X Cursors LaTeX rendering made the app crash.
* Attempting to insert special character no longer automatically saves the expression you're editing.
* Proper HDPI support for icons and buttons (note: HDPI is not available for the rendered canvas yet).
* Support for non-latin characters in variables (e.g. greek letters, subtext, suptext)
* Silent error when misentering variable names in the expression editor causing internal issues.
* Fixing some utils function simplifying parentheses when they shouldn't have.
* (flatpak and KDE SDK) Fixing the sometimes invisible buttons on the objects tab on startup.
* (macos) Application string version does not match LogarithmPlotter's version.
* (debian) (Normally) Fixing deb building.
**Internal changes**
* Object dependencies are now registered on both the dependant object, and the object it's depending on.
* Objects now have a proper per-name registry.
* Object Editor Dialog has been reworked to use loaders insteads.
* Reworked the file loading system to be able to load dependencies properly.
## v0.2.0 (22 Apr 2022) ## v0.2.0 (22 Apr 2022)
**New** **New**

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,2 +1,54 @@
#!/bin/bash #!/bin/bash
#
# This file automatically renames .mjs files to js, and (tries) to fix most common ECMAScript
# specificities so that lupdate doesn't cry out in pain.
# See also: https://bugreports.qt.io/browse/QTBUG-123819
#
escape() {
str="$1"
str="${str//\//\\/}" # Escape slashes
str="${str//\*/\\*}" # Escape asterixes
echo "$str"
}
replace() {
file="$1"
from="$(escape "$2")"
to="$(escape "$3")"
sed -i "s/${from}/${to}/g" "$file"
}
files=$(find .. -name *.mjs)
for file in $files; do
echo "Moving '$file' to '${file%.*}.js'..."
mv "$file" "${file%.*}.js"
# Replacements to make it valid js
replace "${file%.*}.js" "^import" "/*import"
replace "${file%.*}.js" '.mjs"$' '.mjs"*/'
replace "${file%.*}.js" "^export default" "/*export default*/"
replace "${file%.*}.js" "^export" "/*export*/"
done
echo "----------------------------"
echo "| Updating translations... |"
echo "----------------------------"
lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts lupdate -extensions js,qs,qml,py -recursive .. -ts lp_*.ts
# Updating locations in files
for lp in *.ts; do
echo "Replacing locations in $lp..."
for file in $files; do
echo " > Replacing for file $file..."
replace "$lp" "${file%.*}.js" "$file"
done
done
for file in $files; do
echo "Moving '${file%.*}.js' to '$file'..."
mv "${file%.*}.js" "$file"
# Resetting changes
replace "$file" "^/*import" "import"
replace "$file" '.mjs"*/$' '.mjs"'
replace "$file" "^/*export default*/" "export default"
replace "$file" "^/*export*/" "export"
done

View file

@ -1,6 +1,6 @@
""" """
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,18 +16,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from os import getcwd, chdir, environ, path
from platform import release as os_release
from sys import path as sys_path
from sys import platform, argv, exit
from tempfile import TemporaryDirectory
from time import time from time import time
from PySide2.QtWidgets import QApplication from PySide6.QtCore import QTranslator, QLocale
from PySide2.QtQml import QQmlApplicationEngine from PySide6.QtGui import QIcon
from PySide2.QtCore import Qt, QTranslator, QLocale from PySide6.QtQml import QQmlApplicationEngine
from PySide2.QtGui import QIcon from PySide6.QtQuickControls2 import QQuickStyle
from PySide6.QtWidgets import QApplication
from tempfile import TemporaryDirectory
from os import getcwd, chdir, environ, path, remove, close
from platform import release as os_release
from sys import platform, argv, version as sys_version, exit
from sys import path as sys_path
start_time = time() start_time = time()
@ -36,119 +36,163 @@ tempdir = TemporaryDirectory()
tmpfile = path.join(tempdir.name, 'graph.png') tmpfile = path.join(tempdir.name, 'graph.png')
pwd = getcwd() pwd = getcwd()
chdir(path.dirname(path.realpath(__file__))) logarithmplotter_path = path.dirname(path.realpath(__file__))
chdir(logarithmplotter_path)
if path.realpath(path.join(getcwd(), "..")) not in sys_path: if path.realpath(path.join(getcwd(), "..")) not in sys_path:
sys_path.append(path.realpath(path.join(getcwd(), ".."))) sys_path.append(path.realpath(path.join(getcwd(), "..")))
from LogarithmPlotter import __VERSION__ from LogarithmPlotter import __VERSION__
from LogarithmPlotter.util import config, native from LogarithmPlotter.util import config, native
from LogarithmPlotter.util.update import check_for_updates from LogarithmPlotter.util.update import check_for_updates
from LogarithmPlotter.util.helper import Helper from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex from LogarithmPlotter.util.latex import Latex
from LogarithmPlotter.util.js import PyJSValue
config.init() 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():
des = { def get_linux_theme() -> str:
"KDE": "org.kde.desktop",
"gnome": "default",
"lxqt": "fusion",
"mate": "fusion",
}
if "XDG_SESSION_DESKTOP" in environ: if "XDG_SESSION_DESKTOP" in environ:
return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "fusion" if environ["XDG_SESSION_DESKTOP"] in LINUX_THEMES:
return LINUX_THEMES[environ["XDG_SESSION_DESKTOP"]]
return "Fusion"
else: else:
# Android # Android
return "Material" return "Material"
def run():
environ["QT_QUICK_CONTROLS_STYLE"] = { def get_platform_qt_style(os) -> str:
return {
"linux": get_linux_theme(), "linux": get_linux_theme(),
"freebsd": get_linux_theme(), "freebsd": get_linux_theme(),
"win32": "universal" if os_release == "10" else "fusion", "win32": "Universal" if os_release() in ["10", "11", "12", "13", "14"] else "Windows",
"cygwin": "fusion", "cygwin": "Fusion",
"darwin": "default" "darwin": "macOS"
}[platform] }[os]
dep_time = time()
print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.")
icon_fallbacks = QIcon.fallbackSearchPaths(); def register_icon_directories() -> None:
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons") icon_fallbacks = QIcon.fallbackSearchPaths()
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "common"))) base_icon_path = path.join(logarithmplotter_path, "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "objects"))) paths = [["common"], ["objects"], ["history"], ["settings"], ["settings", "custom"]]
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "history"))) for p in paths:
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings"))) icon_fallbacks.append(path.realpath(path.join(base_icon_path, *p)))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom"))) QIcon.setFallbackSearchPaths(icon_fallbacks)
QIcon.setFallbackSearchPaths(icon_fallbacks);
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
def create_qapp() -> QApplication:
app = QApplication(argv) app = QApplication(argv)
app.setApplicationName("LogarithmPlotter") app.setApplicationName("LogarithmPlotter")
app.setApplicationDisplayName("LogarithmPlotter")
app.setApplicationVersion(f"v{__VERSION__}")
app.setDesktopFileName("eu.ad5001.LogarithmPlotter")
app.setOrganizationName("Ad5001") app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True) app.styleHints().setShowShortcutsInContextMenus(True)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg")))) app.setWindowIcon(QIcon(path.realpath(path.join(logarithmplotter_path, "logarithmplotter.svg"))))
return app
def install_translation(app: QApplication) -> QTranslator:
# Installing translators # Installing translators
translator = QTranslator() translator = QTranslator()
# Check if lang is forced. # Check if lang is forced.
forcedlang = [p for p in argv if p[:7]=="--lang="] forcedlang = [p for p in argv if p[:7] == "--lang="]
i18n_path = path.realpath(path.join(logarithmplotter_path, "i18n"))
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale() locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))): if not translator.load(locale, "lp", "_", i18n_path):
app.installTranslator(translator); # Load default translation
print("Loading default language en...")
translator.load(QLocale("en"), "lp", "_", i18n_path)
app.installTranslator(translator)
return translator
# Installing macOS file handler.
macOSFileOpenHandler = None
if platform == "darwin":
macOSFileOpenHandler = native.MacOSFileOpenHandler()
app.installEventFilter(macOSFileOpenHandler)
engine = QQmlApplicationEngine() def create_engine(helper: Helper, latex: Latex, dep_time: float) -> tuple[QQmlApplicationEngine, PyJSValue]:
global tmpfile global tmpfile
helper = Helper(pwd, tmpfile) engine = QQmlApplicationEngine()
latex = Latex(tempdir) js_globals = PyJSValue(engine.globalObject())
engine.rootContext().setContextProperty("Helper", helper) js_globals.Modules = engine.newObject()
engine.rootContext().setContextProperty("Latex", latex) js_globals.Helper = engine.newQObject(helper)
js_globals.Latex = engine.newQObject(latex)
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv) engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
engine.rootContext().setContextProperty("StartTime", dep_time) engine.rootContext().setContextProperty("StartTime", dep_time)
app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file. qml_path = path.realpath(path.join(logarithmplotter_path, "qml"))
engine.addImportPath(qml_path)
engine.load(path.join(qml_path, "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml"))
engine.addImportPath(path.realpath(path.join(getcwd(), "qml"))) return engine, js_globals
engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
if not engine.rootObjects(): def run():
config.init()
if not 'QT_QUICK_CONTROLS_STYLE' in environ:
QQuickStyle.setStyle(get_platform_qt_style(platform))
dep_time = time()
print("Loaded dependencies in " + str((dep_time - start_time) * 1000) + "ms.")
register_icon_directories()
app = create_qapp()
translator = install_translation(app)
# Installing macOS file handler.
macos_file_open_handler = None
if platform == "darwin":
macos_file_open_handler = native.MacOSFileOpenHandler()
app.installEventFilter(macos_file_open_handler)
helper = Helper(pwd, tmpfile)
latex = Latex(tempdir)
engine, js_globals = create_engine(helper, latex, dep_time)
if len(engine.rootObjects()) == 0: # No root objects loaded
print("No root object", path.realpath(path.join(getcwd(), "qml"))) print("No root object", path.realpath(path.join(getcwd(), "qml")))
print(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
exit(-1) exit(-1)
# Open the current diagram # Open the current diagram
chdir(pwd) chdir(pwd)
if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']: if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']:
engine.rootObjects()[0].loadDiagram(argv[-1]) js_globals.Modules.IO.loadDiagram(argv[-1])
chdir(path.dirname(path.realpath(__file__))) chdir(path.dirname(path.realpath(__file__)))
if platform == "darwin": if platform == "darwin":
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0]) macos_file_open_handler.init_io(js_globals.Modules.IO)
# Check for LaTeX installation if LaTeX support is enabled # Check for LaTeX installation if LaTeX support is enabled
if config.getSetting("enable_latex"): if config.getSetting("enable_latex"):
latex.check_latex_install() latex.checkLatexInstallation()
# Check for updates # Check for updates
if config.getSetting("check_for_updates"): if config.getSetting("check_for_updates"):
check_for_updates(__VERSION__, engine.rootObjects()[0]) check_for_updates(__VERSION__, engine.rootObjects()[0])
exit_code = app.exec_() exit_code = app.exec()
tempdir.cleanup() tempdir.cleanup()
config.save() config.save()
exit(exit_code) exit(exit_code)
if __name__ == "__main__": if __name__ == "__main__":
run() run()

View file

@ -1,9 +1,64 @@
<svg id="SVGRoot" width="48" height="48" version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<title>LogarithmPlotter Icon v1.0</title> <svg
<g fill-rule="evenodd" stroke-width="2"> width="24.0px"
<rect width="48" height="48" ry="6" fill="#fff"/> height="24.0px"
<rect x="2" y="38" width="44" height="4" stroke-opacity="0"/> viewBox="0 0 24.0 24.0"
<rect x="18" y="2" width="4" height="44" stroke-opacity="0"/> version="1.1"
</g> id="SVGRoot"
<path d="M 42.05,2 A 40.05,36.05 0 0 1 2,38.05" fill="none" stroke="#f00" stroke-width="3.9012"/> xml:space="preserve"
</svg> xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><title
id="title836">LogarithmPlotter Icon v1.0</title><defs
id="defs833" /><metadata
id="metadata836"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>LogarithmPlotter Icon v1.0</dc:title><cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" /><dc:date>2021</dc:date><dc:creator><cc:Agent><dc:title>Ad5001</dc:title></cc:Agent></dc:creator><dc:rights><cc:Agent><dc:title>(c) Ad5001 2021 - All rights reserved</dc:title></cc:Agent></dc:rights></cc:Work><cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/"><cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" /><cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><g
id="layer2"
transform="matrix(1,0,0,0.94444444,0,1.1666667)"
style="fill:#666666"><rect
style="fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1546"
width="18"
height="18"
x="3"
y="3"
ry="2.25" /></g><g
id="layer2-6"
transform="matrix(1,0,0,0.94444444,0,0.16666668)"
style="fill:#f9f9f9"><rect
style="fill:#f9f9f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1546-7"
width="18"
height="18"
x="3"
y="3"
ry="2.25" /></g><g
id="layer1"
style="stroke-width:2;stroke-dasharray:none"><rect
style="fill:#000000;fill-rule:evenodd;stroke-width:1.86898;stroke-dasharray:none;stroke-opacity:0"
id="rect1410"
width="14"
height="2"
x="5"
y="15.5" /><rect
style="fill:#000000;fill-rule:evenodd;stroke-width:2;stroke-dasharray:none;stroke-opacity:0"
id="rect1412"
width="2"
height="15"
x="9"
y="3.9768662" /><path
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1529"
d="M 18,4 C 18,10.017307 13.40948,15.5 5,15.5" /></g></svg>

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,13 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Dialogs 1.3 import Qt.labs.platform as Native
//import QtQuick.Controls 2.15 //import QtQuick.Controls 2.15
import eu.ad5001.MixedMenu 1.1 import eu.ad5001.MixedMenu 1.1
import "js/objects.js" as Objects import "js/history/index.mjs" as HistoryLib
import "js/historylib.js" as HistoryLib
import "js/math/latex.js" as LatexJS
/*! /*!
@ -35,7 +33,6 @@ import "js/math/latex.js" as LatexJS
\sa LogarithmPlotter \sa LogarithmPlotter
*/ */
MenuBar { MenuBar {
property var settings: settingsMenu
Menu { Menu {
title: qsTr("&File") title: qsTr("&File")
@ -92,30 +89,36 @@ MenuBar {
icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText icon.color: enabled ? sysPalette.windowText : sysPaletteIn.windowText
enabled: history.redoCount > 0 enabled: history.redoCount > 0
} }
MenuSeparator { }
Action { Action {
text: qsTr("&Copy plot") text: qsTr("&Copy plot")
shortcut: StandardKey.Copy shortcut: StandardKey.Copy
onTriggered: root.copyDiagramToClipboard() onTriggered: root.copyDiagramToClipboard()
icon.name: 'edit-copy' icon.name: 'edit-copy'
} }
MenuSeparator { }
Action {
text: qsTr("&Preferences")
shortcut: StandardKey.Copy
onTriggered: preferences.open()
icon.name: 'settings'
}
} }
Menu { Menu {
title: qsTr("&Create") title: qsTr("&Create")
// Services repeater // Services repeater
Repeater { Repeater {
model: Object.keys(Objects.types) model: Object.keys(Modules.Objects.types)
MenuItem { MenuItem {
text: Objects.types[modelData].displayType() text: Modules.Objects.types[modelData].displayType()
visible: Objects.types[modelData].createable() visible: Modules.Objects.types[modelData].createable()
height: visible ? implicitHeight : 0 height: visible ? implicitHeight : 0
icon.name: modelData icon.name: modelData
icon.source: './icons/objects/' + modelData + '.svg' icon.source: './icons/objects/' + modelData + '.svg'
icon.color: sysPalette.buttonText icon.color: sysPalette.buttonText
onTriggered: { onTriggered: {
var newObj = Objects.createNewRegisteredObject(modelData) var newObj = Modules.Objects.createNewRegisteredObject(modelData)
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export())) history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
objectLists.update() objectLists.update()
} }
@ -123,78 +126,6 @@ MenuBar {
} }
} }
Menu {
id: settingsMenu
title: qsTr("&Settings")
Action {
id: checkForUpdatesMenuSetting
text: qsTr("Check for updates on startup")
checkable: true
checked: Helper.getSettingBool("check_for_updates")
onTriggered: Helper.setSettingBool("check_for_updates", checked)
icon.name: 'update'
}
Action {
id: resetRedoStackMenuSetting
text: qsTr("Reset redo stack automaticly")
checkable: true
checked: Helper.getSettingBool("reset_redo_stack")
onTriggered: Helper.setSettingBool("reset_redo_stack", checked)
icon.name: 'timeline'
}
Action {
id: 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'
}
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'
}
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'
}
Action {
id: autocompleteFormulaSetting
text: qsTr("Enable autocompletion")
checkable: true
checked: Helper.getSettingBool("autocompletion.enabled")
onTriggered: {
Helper.setSettingBool("autocompletion.enabled", checked)
}
icon.name: 'label'
}
}
}
Menu { Menu {
title: qsTr("&Help") title: qsTr("&Help")
Action { Action {
@ -236,16 +167,17 @@ MenuBar {
} }
} }
MessageDialog { Native.MessageDialog {
id: saveUnsavedChangesDialog id: saveUnsavedChangesDialog
title: qsTr("Save unsaved changes?") title: qsTr("Save unsaved changes?")
icon: StandardIcon.Question
text: qsTr("This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue?") text: qsTr("This plot contains unsaved changes. By doing this, all unsaved data will be lost. Continue?")
standardButtons: StandardButton.Yes | StandardButton.No buttons: Native.MessageDialog.Save | Native.MessageDialog.Discard | Native.MessageDialog.Cancel
onYes: Qt.quit()
onSaveClicked: settings.save()
onDiscardClicked: Qt.quit()
} }
function showSaveUnsavedChangesDialog() { function openSaveUnsavedChangesDialog() {
saveUnsavedChangesDialog.visible = true saveUnsavedChangesDialog.open()
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,18 +16,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQml 2.12 import QtQml
import "../js/objects.js" as Objects import QtQuick.Window
import "../js/historylib.js" as HistoryLib import "../js/history/index.mjs" as HistoryLib
import "../js/history/common.js" as HistoryCommon
/*! /*!
\qmltype History \qmltype History
\inqmlmodule eu.ad5001.LogarithmPlotter.History \inqmlmodule eu.ad5001.LogarithmPlotter.History
\brief QObject holding persistantly for undo & redo stacks. \brief QObject holding persistantly for undo & redo stacks.
\sa HistoryBrowser, historylib \sa HistoryBrowser, HistoryLib
*/ */
Item { Item {
// Using a QtObject is necessary in order to have proper property propagation in QML // Using a QtObject is necessary in order to have proper property propagation in QML
@ -108,7 +107,7 @@ Item {
/*! /*!
\qmlmethod void History::addToHistory(var action) \qmlmethod void History::addToHistory(var action)
Adds an instance of historylib.Action to history. Adds an instance of HistoryLib.Action to history.
*/ */
function addToHistory(action) { function addToHistory(action) {
if(action instanceof HistoryLib.Action) { if(action instanceof HistoryLib.Action) {
@ -125,7 +124,7 @@ Item {
/*! /*!
\qmlmethod void History::undo(bool updateObjectList = true) \qmlmethod void History::undo(bool updateObjectList = true)
Undoes the historylib.Action at the top of the undo stack and pushes it to the top of the redo stack. Undoes the HistoryLib.Action at the top of the undo stack and pushes it to the top of the redo stack.
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false. By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
*/ */
function undo(updateObjectList = true) { function undo(updateObjectList = true) {
@ -143,7 +142,7 @@ Item {
/*! /*!
\qmlmethod void History::redo(bool updateObjectList = true) \qmlmethod void History::redo(bool updateObjectList = true)
Redoes the historylib.Action at the top of the redo stack and pushes it to the top of the undo stack. Redoes the HistoryLib.Action at the top of the redo stack and pushes it to the top of the undo stack.
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false. By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
*/ */
function redo(updateObjectList = true) { function redo(updateObjectList = true) {
@ -161,7 +160,7 @@ Item {
/*! /*!
\qmlmethod void History::undoMultipleDefered(int toUndoCount) \qmlmethod void History::undoMultipleDefered(int toUndoCount)
Undoes several historylib.Action at the top of the undo stack and pushes them to the top of the redo stack. Undoes several HistoryLib.Action at the top of the undo stack and pushes them to the top of the redo stack.
It undoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes. It undoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
*/ */
function undoMultipleDefered(toUndoCount) { function undoMultipleDefered(toUndoCount) {
@ -174,7 +173,7 @@ Item {
/*! /*!
\qmlmethod void History::redoMultipleDefered(int toRedoCount) \qmlmethod void History::redoMultipleDefered(int toRedoCount)
Redoes several historylib.Action at the top of the redo stack and pushes them to the top of the undo stack. Redoes several HistoryLib.Action at the top of the redo stack and pushes them to the top of the undo stack.
It redoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes. It redoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
*/ */
function redoMultipleDefered(toRedoCount) { function redoMultipleDefered(toRedoCount) {
@ -213,7 +212,11 @@ Item {
} }
Component.onCompleted: { Component.onCompleted: {
HistoryLib.history = historyObj Modules.History.initialize({
HistoryCommon.themeTextColor = sysPalette.windowText historyObj,
themeTextColor: sysPalette.windowText.toString(),
imageDepth: Screen.devicePixelRatio,
fontSize: 14
})
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls 2.12 import QtQuick.Controls
import QtQuick 2.12 import QtQuick
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/utils.js" as Utils import "../js/utils.mjs" as Utils
/*! /*!
@ -53,7 +53,9 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.rightMargin: 5
placeholderText: qsTr("Filter...") placeholderText: qsTr("Filter...")
category: "all"
} }
ScrollView { ScrollView {

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls 2.12 import QtQuick.Controls
import QtQuick 2.12 import QtQuick
import QtGraphicalEffects 1.15 import Qt5Compat.GraphicalEffects
import "../js/utils.js" as Utils import "../js/utils.mjs" as Utils
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
@ -116,10 +116,23 @@ Button {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: !hidden visible: !hidden
font.pixelSize: 14 font.pixelSize: 14
text: historyAction.getHTMLString().replace(/\$\{tag_color\}/g, clr) text: ""
textFormat: Text.RichText textFormat: Text.RichText
clip: true clip: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Component.onCompleted: function() {
// Render HTML, might be string, but could also be a promise
const html = historyAction.getHTMLString()
if(typeof html === "string") {
label.text = html.replace(/\$\{tag_color\}/g, clr)
} else {
// Promise! We need to way to wait for it to be completed.
html.then(rendered => {
label.text = rendered.replace(/\$\{tag_color\}/g, clr)
})
}
}
} }
Rectangle { Rectangle {
@ -132,8 +145,6 @@ Button {
color: sysPalette.windowText color: sysPalette.windowText
} }
//text: content
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.delay: 200 ToolTip.delay: 200
ToolTip.text: content ToolTip.text: content

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import "js/objects.js" as Objects import Qt.labs.platform as Native
import "js/utils.js" as Utils import "js/utils.mjs" as Utils
import "js/mathlib.js" as MathLib import "js/math/index.mjs" as MathLib
/*! /*!
\qmltype LogGraphCanvas \qmltype LogGraphCanvas
@ -120,43 +120,6 @@ Canvas {
*/ */
property bool showygrad: false property bool showygrad: false
/*!
\qmlproperty int LogGraphCanvas::maxgradx
Max power of the logarithmic scaled on the x axis in logarithmic mode.
*/
property int maxgradx: 20
/*!
\qmlproperty var LogGraphCanvas::yaxisstepExpr
Expression for the y axis step (used to create labels).
*/
property var yaxisstepExpr: (new MathLib.Expression(`x*(${yaxisstep})`))
/*!
\qmlproperty double LogGraphCanvas::yaxisstep1
Value of the for the y axis step.
*/
property double yaxisstep1: yaxisstepExpr.execute(1)
/*!
\qmlproperty int LogGraphCanvas::drawMaxY
Minimum value of y that should be drawn onto the canvas.
*/
property int drawMaxY: Math.ceil(Math.max(Math.abs(ymax), Math.abs(px2y(canvasSize.height)))/yaxisstep1)
/*!
\qmlproperty var LogGraphCanvas::xaxisstepExpr
Expression for the x axis step (used to create labels).
*/
property var xaxisstepExpr: (new MathLib.Expression(`x*(${xaxisstep})`))
/*!
\qmlproperty double LogGraphCanvas::xaxisstep1
Value of the for the x axis step.
*/
property double xaxisstep1: xaxisstepExpr.execute(1)
/*!
\qmlproperty int LogGraphCanvas::drawMaxX
Maximum value of x that should be drawn onto the canvas.
*/
property int drawMaxX: Math.ceil(Math.max(Math.abs(xmin), Math.abs(px2x(canvasSize.width)))/xaxisstep1)
/*! /*!
\qmlproperty var LogGraphCanvas::imageLoaders \qmlproperty var LogGraphCanvas::imageLoaders
Dictionary of format {image: [callback.image data]} containing data for defered image loading. Dictionary of format {image: [callback.image data]} containing data for defered image loading.
@ -168,25 +131,25 @@ Canvas {
*/ */
property var ctx property var ctx
Component.onCompleted: 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) { onPaint: function(rect) {
//console.log('Redrawing') //console.log('Redrawing')
if(rect.width == canvas.width) { // Redraw full canvas if(rect.width == canvas.width) { // Redraw full canvas
ctx = getContext("2d"); Modules.Canvas.redraw()
reset(ctx)
drawGrille(ctx)
drawAxises(ctx)
drawLabels(ctx)
ctx.lineWidth = linewidth
for(var objType in Objects.currentObjects) {
for(var obj of Objects.currentObjects[objType]){
ctx.strokeStyle = obj.color
ctx.fillStyle = obj.color
if(obj.visible) obj.draw(canvas, ctx)
}
}
ctx.lineWidth = 1
} }
} }
@ -199,277 +162,4 @@ Canvas {
} }
}) })
} }
/*!
\qmlmethod void LogGraphCanvas::reset(var ctx)
Resets the canvas to a blank one with default setting using 2D \c ctx.
*/
function reset(ctx){
// Reset
ctx.fillStyle = "#FFFFFF"
ctx.strokeStyle = "#000000"
ctx.font = `${canvas.textsize}px sans-serif`
ctx.fillRect(0,0,width,height)
}
// Drawing the log based graph
/*!
\qmlmethod void LogGraphCanvas::drawGrille(var ctx)
Draws the grid using 2D \c ctx.
*/
function drawGrille(ctx) {
ctx.strokeStyle = "#C0C0C0"
if(logscalex) {
for(var xpow = -maxgradx; xpow <= maxgradx; xpow++) {
for(var xmulti = 1; xmulti < 10; xmulti++) {
drawXLine(ctx, Math.pow(10, xpow)*xmulti)
}
}
} else {
for(var x = 0; x < drawMaxX; x+=1) {
drawXLine(ctx, x*xaxisstep1)
drawXLine(ctx, -x*xaxisstep1)
}
}
for(var y = 0; y < drawMaxY; y+=1) {
drawYLine(ctx, y*yaxisstep1)
drawYLine(ctx, -y*yaxisstep1)
}
}
/*!
\qmlmethod void LogGraphCanvas::drawAxises(var ctx)
Draws the graph axises using 2D \c ctx.
*/
function drawAxises(ctx) {
ctx.strokeStyle = "#000000"
var axisypos = logscalex ? 1 : 0
drawXLine(ctx, axisypos)
drawYLine(ctx, 0)
var axisypx = x2px(axisypos) // X coordinate of Y axis
var axisxpx = y2px(0) // Y coordinate of X axis
// Drawing arrows
drawLine(ctx, axisypx, 0, axisypx-10, 10)
drawLine(ctx, axisypx, 0, axisypx+10, 10)
drawLine(ctx, canvasSize.width, axisxpx, canvasSize.width-10, axisxpx-10)
drawLine(ctx, canvasSize.width, axisxpx, canvasSize.width-10, axisxpx+10)
}
/*!
\qmlmethod void LogGraphCanvas::drawLabels(var ctx)
Draws all labels (graduation & axises labels) using 2D \c ctx.
*/
function drawLabels(ctx) {
var axisypx = x2px(logscalex ? 1 : 0) // X coordinate of Y axis
var axisxpx = y2px(0) // Y coordinate of X axis
// Labels
ctx.fillStyle = "#000000"
ctx.font = `${canvas.textsize}px sans-serif`
ctx.fillText(ylabel, axisypx+10, 24)
var textSize = ctx.measureText(xlabel).width
ctx.fillText(xlabel, canvasSize.width-14-textSize, axisxpx-5)
// Axis graduation labels
ctx.font = `${canvas.textsize-4}px sans-serif`
var txtMinus = ctx.measureText('-').width
if(showxgrad) {
if(logscalex) {
for(var xpow = -maxgradx; xpow <= maxgradx; xpow+=1) {
var textSize = ctx.measureText("10"+Utils.textsup(xpow)).width
if(xpow != 0)
drawVisibleText(ctx, "10"+Utils.textsup(xpow), x2px(Math.pow(10,xpow))-textSize/2, axisxpx+16+(6*(y==0)))
}
} else {
for(var x = 1; x < drawMaxX; x += 1) {
var drawX = x*xaxisstep1
var txtX = xaxisstepExpr.simplify(x)
var textSize = measureText(ctx, txtX, 6).height
drawVisibleText(ctx, txtX, x2px(drawX)-4, axisxpx+textsize/2+textSize)
drawVisibleText(ctx, '-'+txtX, x2px(-drawX)-4, axisxpx+textsize/2+textSize)
}
}
}
if(showygrad) {
for(var y = 0; y < drawMaxY; y += 1) {
var drawY = y*yaxisstep1
var txtY = yaxisstepExpr.simplify(y)
var textSize = ctx.measureText(txtY).width
drawVisibleText(ctx, txtY, axisypx-6-textSize, y2px(drawY)+4+(10*(y==0)))
if(y != 0)
drawVisibleText(ctx, '-'+txtY, axisypx-6-textSize-txtMinus, y2px(-drawY)+4)
}
}
ctx.fillStyle = "#FFFFFF"
}
/*!
\qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x)
Draws an horizontal line at \c x plot coordinate using 2D \c ctx.
*/
function drawXLine(ctx, x) {
if(visible(x, ymax)) {
drawLine(ctx, x2px(x), 0, x2px(x), canvasSize.height)
}
}
/*!
\qmlmethod void LogGraphCanvas::drawXLine(var ctx, double x)
Draws an vertical line at \c y plot coordinate using 2D \c ctx.
*/
function drawYLine(ctx, y) {
if(visible(xmin, y)) {
drawLine(ctx, 0, y2px(y), canvasSize.width, y2px(y))
}
}
/*!
\qmlmethod void LogGraphCanvas::drawVisibleText(var ctx, string text, double x, double y)
Writes multline \c text onto the canvas using 2D \c ctx.
\note The \c x and \c y properties here are relative to the canvas, not the plot.
*/
function drawVisibleText(ctx, text, x, y) {
if(x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height) {
text.toString().split("\n").forEach(function(txt, i){
ctx.fillText(txt, x, y+(canvas.textsize*i))
})
}
}
/*!
\qmlmethod void LogGraphCanvas::drawVisibleImage(var ctx, var image, double x, double y)
Draws an \c image onto the canvas using 2D \c ctx.
\note The \c x, \c y \c width and \c height properties here are relative to the canvas, not the plot.
*/
function drawVisibleImage(ctx, image, x, y, width, height) {
//console.log("Drawing image", isImageLoaded(image), isImageError(image))
markDirty(Qt.rect(x, y, width, height));
ctx.drawImage(image, x, y, width, height)
/*if(true || (x > 0 && x < canvasSize.width && y > 0 && y < canvasSize.height)) {
}*/
}
/*!
\qmlmethod var LogGraphCanvas::measureText(var ctx, string text)
Measures the wicth and height of a multiline \c text that would be drawn onto the canvas using 2D \c ctx.
Return format: dictionary {"width": width, "height": height}
*/
function measureText(ctx, text) {
let theight = 0
let twidth = 0
let defaultHeight = ctx.measureText("M").width // Approximate but good enough!
text.split("\n").forEach(function(txt, i){
theight += defaultHeight
if(ctx.measureText(txt).width > twidth) twidth = ctx.measureText(txt).width
})
return {'width': twidth, 'height': theight}
}
/*!
\qmlmethod double LogGraphCanvas::x2px(double x)
Converts an \c x coordinate to it's relative position on the canvas.
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
*/
function x2px(x) {
if(logscalex) {
var logxmin = Math.log(xmin)
return (Math.log(x)-logxmin)*xzoom
} else return (x - xmin)*xzoom
}
/*!
\qmlmethod double LogGraphCanvas::y2px(double y)
Converts an \c y coordinate to it's relative position on the canvas.
The y axis not supporting logarithmic scale, it only support linear convertion.
*/
function y2px(y) {
return (ymax-y)*yzoom
}
/*!
\qmlmethod double LogGraphCanvas::px2x(double px)
Converts an x \c px position on the canvas to it's corresponding coordinate on the plot.
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
*/
function px2x(px) {
if(logscalex) {
return Math.exp(px/xzoom+Math.log(xmin))
} else return (px/xzoom+xmin)
}
/*!
\qmlmethod double LogGraphCanvas::px2x(double px)
Converts an x \c px position on the canvas to it's corresponding coordinate on the plot.
It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
*/
function px2y(px) {
return -(px/yzoom-ymax)
}
/*!
\qmlmethod bool LogGraphCanvas::visible(double x, double y)
Checks whether a plot point (\c x, \c y) is visible or not on the canvas.
*/
function visible(x, y) {
return (x2px(x) >= 0 && x2px(x) <= canvasSize.width) && (y2px(y) >= 0 && y2px(y) <= canvasSize.height)
}
/*!
\qmlmethod bool LogGraphCanvas::drawLine(var ctx, double x1, double y1, double x2, double y2)
Draws a line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
*/
function drawLine(ctx, x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
/*!
\qmlmethod bool LogGraphCanvas::drawDashedLine2(var ctx, double x1, double y1, double x2, double y2)
Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
*/
function drawDashedLine2(ctx, x1, y1, x2, y2, dashPxSize = 5) {
ctx.setLineDash([dashPxSize, dashPxSize]);
drawLine(ctx, x1, y1, x2, y2)
ctx.setLineDash([]);
}
/*!
\qmlmethod bool LogGraphCanvas::drawDashedLine(var ctx, double x1, double y1, double x2, double y2)
Draws a dashed line from plot point (\c x1, \c y1) to plot point (\c x2, \¢ y2) using 2D \c ctx.
(Legacy slower method)
*/
function drawDashedLine(ctx, x1, y1, x2, y2, dashPxSize = 10) {
var distance = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2))
var progPerc = dashPxSize/distance
ctx.beginPath();
ctx.moveTo(x1, y1);
for(var i = 0; i < 1; i += progPerc) {
ctx.lineTo(x1-(x1-x2)*i, y1-(y1-y2)*i)
ctx.moveTo(x1-(x1-x2)*(i+progPerc/2), y1-(y1-y2)*(i+progPerc/2))
}
ctx.stroke();
}
/*!
\qmlmethod var LogGraphCanvas::renderLatexImage(string ltxText, color)
Renders latex markup \c ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
*/
function renderLatexImage(ltxText, color, callback) {
let [ltxSrc, ltxWidth, ltxHeight] = Latex.render(ltxText, textsize, color).split(",")
let imgData = {
"source": ltxSrc,
"width": parseFloat(ltxWidth),
"height": parseFloat(ltxHeight)
};
if(!isImageLoaded(ltxSrc) && !isImageLoading(ltxSrc)){
// Wait until the image is loaded to callback.
loadImage(ltxSrc)
imageLoaders[ltxSrc] = [callback, imgData]
} else {
// Callback directly
callback(canvas, ctx, imgData)
}
}
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,16 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQml 2.12 import QtQml
import QtQuick.Controls 2.12 import QtQuick.Controls
import eu.ad5001.MixedMenu 1.1 import eu.ad5001.MixedMenu 1.1
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import QtQuick 2.12 import QtQuick
// Auto loading all objects.
import "js/objs/autoload.js" as ALObjects // Auto loading all modules.
import "js/autoload.mjs" as ModulesAutoload
import "js/objects.js" as Objects
import "js/math/latex.js" as LatexJS
import eu.ad5001.LogarithmPlotter.History 1.0 import eu.ad5001.LogarithmPlotter.History 1.0
import eu.ad5001.LogarithmPlotter.ObjectLists 1.0 import eu.ad5001.LogarithmPlotter.ObjectLists 1.0
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
@ -45,16 +44,7 @@ ApplicationWindow {
color: sysPalette.window color: sysPalette.window
title: "LogarithmPlotter " + (settings.saveFilename != "" ? " - " + settings.saveFilename.split('/').pop() : "") + (history.saved ? "" : "*") title: "LogarithmPlotter " + (settings.saveFilename != "" ? " - " + settings.saveFilename.split('/').pop() : "") + (history.saved ? "" : "*")
SystemPalette { SystemPalette { id: sysPalette; colorGroup: SystemPalette.Active }
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 } SystemPalette { id: sysPaletteIn; colorGroup: SystemPalette.Disabled }
menuBar: appMenu.trueItem menuBar: appMenu.trueItem
@ -65,6 +55,8 @@ ApplicationWindow {
Popup.GreetScreen {} Popup.GreetScreen {}
Popup.Preferences {id: preferences}
Popup.Changelog {id: changelog} Popup.Changelog {id: changelog}
Popup.About {id: about} Popup.About {id: about}
@ -169,7 +161,7 @@ ApplicationWindow {
property bool firstDrawDone: false property bool firstDrawDone: false
onPainted: if(!firstDrawDone) { onPainted: if(!firstDrawDone) {
firstDrawDone = true; firstDrawDone = true;
console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms") console.info("First paint done in " + (new Date().getTime()-(StartTime*1000)) + "ms")
if(TestBuild == true) { if(TestBuild == true) {
@ -178,6 +170,13 @@ ApplicationWindow {
} }
} }
ViewPositionChangeOverlay {
id: viewPositionChanger
anchors.fill: parent
canvas: parent
settingsInstance: settings
}
PickLocationOverlay { PickLocationOverlay {
id: positionPicker id: positionPicker
anchors.fill: parent anchors.fill: parent
@ -185,130 +184,6 @@ ApplicationWindow {
} }
} }
/*!
\qmlmethod void LogarithmPlotter::saveDiagram(string filename)
Saves the diagram to a certain \c filename.
*/
function saveDiagram(filename) {
if(['lpf'].indexOf(filename.split('.')[filename.split('.').length-1]) == -1)
filename += '.lpf'
settings.saveFilename = filename
var objs = {}
for(var objType in Objects.currentObjects){
objs[objType] = []
for(var obj of Objects.currentObjects[objType]) {
objs[objType].push(obj.export())
}
}
Helper.write(filename, JSON.stringify({
"xzoom": settings.xzoom,
"yzoom": settings.yzoom,
"xmin": settings.xmin,
"ymax": settings.ymax,
"xaxisstep": settings.xaxisstep,
"yaxisstep": settings.yaxisstep,
"xaxislabel": settings.xlabel,
"yaxislabel": settings.ylabel,
"logscalex": settings.logscalex,
"linewidth": settings.linewidth,
"showxgrad": settings.showxgrad,
"showygrad": settings.showygrad,
"textsize": settings.textsize,
"history": history.serialize(),
"width": root.width,
"height": root.height,
"objects": objs,
"type": "logplotv1"
}))
alert.show(qsTr("Saved plot to '%1'.").arg(filename.split("/").pop()))
history.saved = true
}
/*!
\qmlmethod void LogarithmPlotter::saveDiagram(string filename)
Loads the diagram from a certain \c filename.
*/
function loadDiagram(filename) {
let basename = filename.split("/").pop()
alert.show(qsTr("Loading file '%1'.").arg(basename))
let data = JSON.parse(Helper.load(filename))
let error = "";
if(Object.keys(data).includes("type") && data["type"] == "logplotv1") {
history.clear()
// Importing settings
settings.saveFilename = filename
settings.xzoom = data["xzoom"]
settings.yzoom = data["yzoom"]
settings.xmin = data["xmin"]
settings.ymax = data["ymax"]
settings.xaxisstep = data["xaxisstep"]
settings.yaxisstep = data["yaxisstep"]
settings.xlabel = data["xaxislabel"]
settings.ylabel = data["yaxislabel"]
settings.logscalex = data["logscalex"]
if("showxgrad" in data)
settings.showxgrad = data["showxgrad"]
if("showygrad" in data)
settings.textsize = data["showygrad"]
if("linewidth" in data)
settings.linewidth = data["linewidth"]
if("textsize" in data)
settings.textsize = data["textsize"]
root.height = data["height"]
root.width = data["width"]
// Importing objects
Objects.currentObjects = {}
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 { Timer {
id: delayRefreshTimer id: delayRefreshTimer
repeat: false repeat: false
@ -323,10 +198,26 @@ ApplicationWindow {
onTriggered: Qt.quit() // Quit after paint on test build onTriggered: Qt.quit() // Quit after paint on test build
} }
onClosing: { onClosing: function(close) {
if(!history.saved) { if(!history.saved) {
close.accepted = false close.accepted = false
appMenu.showSaveUnsavedChangesDialog() 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()
} }
} }
@ -357,7 +248,7 @@ ApplicationWindow {
Action { Action {
text: qsTr("&Update LogarithmPlotter") text: qsTr("&Update LogarithmPlotter")
icon.name: 'update' icon.name: 'update'
onTriggered: Qt.openUrlExternally("https://dev.apps.ad5001.eu/logarithmplotter") onTriggered: Qt.openUrlExternally("https://apps.ad5001.eu/logarithmplotter/")
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,13 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../../js/objects.js" as Objects import "../../js/history/index.mjs" as HistoryLib
import "../../js/historylib.js" as HistoryLib import "../../js/utils.mjs" as Utils
import "../../js/utils.js" as Utils import "../../js/math/index.mjs" as MathLib
import "../../js/mathlib.js" as MathLib
/*! /*!
\qmltype CustomPropertyList \qmltype CustomPropertyList
@ -43,8 +43,13 @@ Repeater {
Object whose properties to list and edit. Object whose properties to list and edit.
*/ */
property var obj property var obj
/*!
\qmlproperty var CustomPropertyList::positionPicker
Reference to the global PositionPicker QML object.
*/
property var positionPicker
readonly property var textTypes: ['Domain', 'string', 'number'] readonly property var textTypes: ['Domain', 'string', 'number', 'int']
readonly property var comboBoxTypes: ['ObjectType', 'Enum'] readonly property var comboBoxTypes: ['ObjectType', 'Enum']
readonly property var listTypes: ['List', 'Dict'] readonly property var listTypes: ['List', 'Dict']
@ -97,14 +102,25 @@ Repeater {
height: 30 height: 30
label: propertyLabel label: propertyLabel
icon: `settings/custom/${propertyIcon}.svg` icon: `settings/custom/${propertyIcon}.svg`
isDouble: propertyType == 'number' min: propertyType == "int" ? 0 : -Infinity
isInt: propertyType == "int"
isDouble: propertyType == "number"
defValue: obj[propertyName] == null ? '' : obj[propertyName].toString() defValue: obj[propertyName] == null ? '' : obj[propertyName].toString()
category: {
return {
"Domain": "domain",
"string": "all",
"number": "all",
"int": "all",
}[propertyType]
}
onChanged: function(newValue) { onChanged: function(newValue) {
try { try {
var newValueParsed = { var newValueParsed = {
'Domain': () => MathLib.parseDomain(newValue), "Domain": () => MathLib.parseDomain(newValue),
'string': () => newValue, "string": () => newValue,
'number': () => parseFloat(newValue) "number": () => newValue,
"int": () => newValue
}[propertyType]() }[propertyType]()
// Ensuring old and new values are different to prevent useless adding to history. // Ensuring old and new values are different to prevent useless adding to history.
@ -118,20 +134,23 @@ Repeater {
} }
} catch(e) { } catch(e) {
// Error in expression or domain // Error in expression or domain
console.trace()
parsingErrorDialog.showDialog(propertyName, newValue, e.message) parsingErrorDialog.showDialog(propertyName, newValue, e.message)
} }
} }
// D.MessageDialog { Native.MessageDialog {
// id: parsingErrorDialog id: parsingErrorDialog
// title: qsTranslate("expression", "LogarithmPlotter - Parsing error") title: qsTranslate("expression", "LogarithmPlotter - Parsing error")
// text: "" text: ""
// function showDialog(propName, propValue, error) { function showDialog(propName, propValue, error) {
// text = qsTranslate("error", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3").arg(propName).arg(error).arg(propValue) text = qsTranslate("error", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3")
// open() .arg(qsTranslate('prop', propName))
// } .arg(error).arg(propValue)
// } open()
}
}
} }
} }
@ -175,8 +194,8 @@ Repeater {
// Base, untranslated version of the model. // Base, untranslated version of the model.
property var baseModel: selectObjMode ? property var baseModel: selectObjMode ?
Objects.getObjectsName(propertyType.objType).concat( Modules.Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] : []) isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] : [])
: propertyType.values : propertyType.values
// Translated version of the model. // Translated version of the model.
model: selectObjMode ? baseModel : propertyType.translatedValues model: selectObjMode ? baseModel : propertyType.translatedValues
@ -186,20 +205,20 @@ Repeater {
if(selectObjMode) { if(selectObjMode) {
// This is only done when what we're selecting are Objects. // This is only done when what we're selecting are Objects.
// Setting object property. // Setting object property.
var selectedObj = Objects.currentObjectsByName[baseModel[newIndex]] var selectedObj = Modules.Objects.currentObjectsByName[baseModel[newIndex]]
if(newIndex != 0) { if(newIndex != 0) {
// Make sure we don't set the object to null. // Make sure we don't set the object to null.
if(selectedObj == null) { if(selectedObj == null) {
// Creating new object. // Creating new object.
selectedObj = Objects.createNewRegisteredObject(propertyType.objType) selectedObj = Modules.Objects.createNewRegisteredObject(propertyType.objType)
history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export())) history.addToHistory(new HistoryLib.CreateNewObject(selectedObj.name, propertyType.objType, selectedObj.export()))
baseModel = Objects.getObjectsName(propertyType.objType).concat( baseModel = Modules.Objects.getObjectsName(propertyType.objType).concat(
isRealObject ? [qsTr("+ Create new %1").arg(Objects.types[propertyType.objType].displayType())] : isRealObject ? [qsTr("+ Create new %1").arg(Modules.Objects.types[propertyType.objType].displayType())] :
[]) [])
currentIndex = baseModel.indexOf(selectedObj.name) currentIndex = baseModel.indexOf(selectedObj.name)
} }
selectedObj.requiredBy.push(Objects.currentObjects[objType][objIndex]) selectedObj.requiredBy.push(Modules.Objects.currentObjects[objType][objIndex])
//Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name) //Modules.Objects.currentObjects[objType][objIndex].requiredBy = obj[propertyName].filter((obj) => obj.name != obj.name)
} }
obj.requiredBy = obj.requiredBy.filter((obj) => obj.name != obj.name) obj.requiredBy = obj.requiredBy.filter((obj) => obj.name != obj.name)
history.addToHistory(new HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
@ -243,7 +262,7 @@ Repeater {
obj.name, objType, propertyName, obj.name, objType, propertyName,
obj[propertyName], exported obj[propertyName], exported
)) ))
//Objects.currentObjects[objType][objIndex][propertyName] = exported //Modules.Objects.currentObjects[objType][objIndex][propertyName] = exported
obj[propertyName] = exported obj[propertyName] = exported
root.changed() root.changed()
} }
@ -255,29 +274,68 @@ Repeater {
} }
delegate: Component { delegate: Component {
Loader { Row {
//height: customPropComment.height + customPropText.height + customPropCheckBox.height + customPropCombo.height + customPropListDict.height
width: dlgProperties.width width: dlgProperties.width
property string propertyName: modelData[0] spacing: 5
property var propertyType: modelData[1]
property string propertyLabel: qsTranslate('prop',propertyName)
property string propertyIcon: Utils.camelCase2readable(propertyName)
sourceComponent: { Loader {
if(propertyName.startsWith('comment')) id: propertyEditor
return commentComponent width: dlgProperties.width - pointerButton.width
else if(propertyType == 'boolean') property string propertyName: modelData[0]
return checkboxComponent property var propertyType: modelData[1]
else if(paramTypeIn(propertyType, ['Expression'])) property string propertyLabel: qsTranslate('prop',propertyName)
return expressionEditorComponent property string propertyIcon: Utils.camelCase2readable(propertyName)
else if(paramTypeIn(propertyType, textTypes))
return textEditorComponent sourceComponent: {
else if(paramTypeIn(propertyType, comboBoxTypes)) if(propertyName.startsWith('comment'))
return comboBoxComponent return commentComponent
else if(paramTypeIn(propertyType, listTypes)) else if(propertyType == 'boolean')
return listDictEditorComponent return checkboxComponent
else else if(paramTypeIn(propertyType, ['Expression']))
return {} return expressionEditorComponent
else if(paramTypeIn(propertyType, textTypes))
return textEditorComponent
else if(paramTypeIn(propertyType, comboBoxTypes))
return comboBoxComponent
else if(paramTypeIn(propertyType, listTypes))
return listDictEditorComponent
else
return {}
}
}
Button {
id: pointerButton
height: parent.height
width: visible ? height : 0
anchors.verticalCenter: parent.verticalCenter
property bool isXProp: ['labelX', 'x'].includes(propertyEditor.propertyName)
property bool isYProp: ['y'].includes(propertyEditor.propertyName)
visible: isXProp || isYProp
ToolTip.visible: hovered
ToolTip.text: qsTr("Pick on graph")
Setting.Icon {
id: icon
width: 18
height: 18
anchors.centerIn: parent
color: sysPalette.windowText
source: '../icons/common/position.svg'
}
onClicked: {
positionPicker.objType = objType
positionPicker.objName = obj.name
positionPicker.pickX = isXProp
positionPicker.pickY = isYProp
positionPicker.propertyX = propertyEditor.propertyName
positionPicker.propertyY = propertyEditor.propertyName
positionPicker.visible = true
objEditor.close()
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,15 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
import QtQuick.Dialogs 1.3 as D import QtQuick.Dialogs as D
import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../../js/objects.js" as Objects import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
import "../../js/objs/common.js" as ObjectsCommons import "../../js/history/index.mjs" as HistoryLib
import "../../js/historylib.js" as HistoryLib import "../../js/utils.mjs" as Utils
import "../../js/utils.js" as Utils import "../../js/math/index.mjs" as MathLib
import "../../js/mathlib.js" as MathLib
/*! /*!
\qmltype Dialog \qmltype Dialog
@ -36,7 +36,7 @@ import "../../js/mathlib.js" as MathLib
\sa Loader, ObjectLists \sa Loader, ObjectLists
*/ */
D.Dialog { Popup.BaseDialog {
id: objEditor id: objEditor
/*! /*!
\qmlproperty string EditorDialog::objType \qmlproperty string EditorDialog::objType
@ -52,103 +52,119 @@ D.Dialog {
\qmlproperty var EditorDialog::obj \qmlproperty var EditorDialog::obj
Instance of the object being edited. 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.
*/
property var posPicker
title: "LogarithmPlotter" title: "LogarithmPlotter"
width: 350 width: 350
height: 400 minimumHeight: Math.max(450,dlgProperties.height + margin*4 + 30)
maximumHeight: minimumHeight
// Disable closing on return/enter, causing issues with autocomplete. Item {
onActionChosen: if(action.key == Qt.Key_Enter || action.key == Qt.Key_Return) action.accepted = false anchors {
top: parent.top;
Label { left: parent.left;
id: dlgTitle bottom: parent.bottom;
anchors.left: parent.left right: parent.right;
anchors.top: parent.top topMargin: margin;
verticalAlignment: TextInput.AlignVCenter leftMargin: margin;
text: qsTr("Edit properties of %1 %2").arg(Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name) bottomMargin: margin + 30;
font.pixelSize: 20 rightMargin: margin;
color: sysPalette.windowText
}
Column {
id: dlgProperties
anchors.top: dlgTitle.bottom
width: objEditor.width - 20
spacing: 10
D.MessageDialog {
id: invalidNameDialog
title: qsTr("LogarithmPlotter - Invalid object name")
text: ""
function showDialog(objectName) {
text = qsTr("An object with the name '%1' already exists.").arg(objectName)
open()
}
} }
Setting.TextSetting { Column {
id: nameProperty id: dlgProperties
height: 30 anchors.top: parent.top
label: qsTr("Name") width: objEditor.width - 20
icon: "common/label.svg" spacing: 10
width: dlgProperties.width
value: objEditor.obj.name Label {
onChanged: function(newValue) { id: dlgTitle
let newName = Utils.parseName(newValue) verticalAlignment: TextInput.AlignVCenter
if(newName != '' && objEditor.obj.name != newName) { text: qsTr("Edit properties of %1 %2").arg(Modules.Objects.types[objEditor.objType].displayType()).arg(objEditor.obj.name)
if(newName in Objects.currentObjectsByName) { font.pixelSize: 20
invalidNameDialog.showDialog(newName) color: sysPalette.windowText
} else { }
history.addToHistory(new HistoryLib.NameChanged(
objEditor.obj.name, objEditor.objType, newName Native.MessageDialog {
)) id: invalidNameDialog
Objects.renameObject(obj.name, newName) title: qsTr("LogarithmPlotter - Invalid object name")
objEditor.obj = Objects.currentObjects[objEditor.objType][objEditor.objIndex] text: ""
function showDialog(objectName) {
text = qsTr("An object with the name '%1' already exists.").arg(objectName)
open()
}
}
Setting.TextSetting {
id: nameProperty
height: 30
label: qsTr("Name")
icon: "common/label.svg"
category: "name"
width: dlgProperties.width
value: objEditor.obj.name
onChanged: function(newValue) {
let newName = Utils.parseName(newValue)
if(newName != '' && objEditor.obj.name != newName) {
if(newName in Modules.Objects.currentObjectsByName) {
invalidNameDialog.showDialog(newName)
} else {
history.addToHistory(new HistoryLib.NameChanged(
objEditor.obj.name, objEditor.objType, newName
))
Modules.Objects.renameObject(obj.name, newName)
objEditor.obj = Modules.Objects.currentObjects[objEditor.objType][objEditor.objIndex]
objectListList.update()
}
}
}
}
Setting.ComboBoxSetting {
id: labelContentProperty
height: 30
width: dlgProperties.width
label: qsTr("Label content")
model: [qsTr("null"), qsTr("name"), qsTr("name + value")]
property var idModel: ["null", "name", "name + value"]
icon: "common/label.svg"
currentIndex: idModel.indexOf(objEditor.obj.labelContent)
onActivated: function(newIndex) {
if(idModel[newIndex] != objEditor.obj.labelContent) {
objEditor.obj.labelContent = idModel[newIndex]
objEditor.obj.update()
objectListList.update() objectListList.update()
} }
} }
} }
}
Setting.ComboBoxSetting { // Dynamic properties
id: labelContentProperty CustomPropertyList {
height: 30 id: dlgCustomProperties
width: dlgProperties.width obj: objEditor.obj
label: qsTr("Label content") positionPicker: posPicker
model: [qsTr("null"), qsTr("name"), qsTr("name + value")]
property var idModel: ["null", "name", "name + value"] onChanged: {
icon: "common/label.svg" obj.update()
currentIndex: idModel.indexOf(objEditor.obj.labelContent)
onActivated: function(newIndex) {
if(idModel[newIndex] != objEditor.obj.labelContent) {
objEditor.obj.labelContent = idModel[newIndex]
objEditor.obj.update()
objectListList.update() objectListList.update()
} }
} }
} }
// Dynamic properties
CustomPropertyList {
id: dlgCustomProperties
obj: objEditor.obj
onChanged: {
obj.update()
objectListList.update()
}
}
} }
/*! /*!
\qmlmethod void EditorDialog::show() \qmlmethod void EditorDialog::open()
Shows the editor after the object to be edited is set. Shows the editor after the object to be edited is set.
*/ */
function show() { function open() {
dlgCustomProperties.model = [] // Reset 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. dlgCustomProperties.model = Object.keys(objProps).map(prop => [prop, objProps[prop]]) // Converted to 2-dimentional array.
objEditor.open() objEditor.show()
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,10 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
import "../js/objects.js" as Objects import "../js/history/index.mjs" as HistoryLib
import "../js/historylib.js" as HistoryLib
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
@ -34,6 +33,21 @@ Column {
id: createRow id: createRow
property var objectEditor property var objectEditor
property var objectLists property var objectLists
property var posPicker
/*!
\qmlmethod int ObjectCreationGrid::openEditorDialog(var obj)
Opens the editor dialog for an object \c obj.
*/
function openEditorDialog(obj) {
// Open editor
objectEditor.obj = obj
objectEditor.objType = obj.type
objectEditor.objIndex = Modules.Objects.currentObjects[obj.type].indexOf(obj)
objectEditor.open()
// Disconnect potential link
posPicker.picked.disconnect(openEditorDialog)
}
Label { Label {
id: createTitle id: createTitle
@ -46,12 +60,12 @@ Column {
width: parent.width width: parent.width
columns: 3 columns: 3
Repeater { Repeater {
model: Object.keys(Objects.types) model: Object.keys(Modules.Objects.types)
Button { Button {
id: createBtn id: createBtn
width: 96 width: 96
visible: Objects.types[modelData].createable() visible: Modules.Objects.types[modelData].createable()
height: visible ? width*0.8 : 0 height: visible ? width*0.8 : 0
// The KDE SDK is kinda buggy, so it respects neither specified color nor display propreties. // The KDE SDK is kinda buggy, so it respects neither specified color nor display propreties.
//display: AbstractButton.TextUnderIcon //display: AbstractButton.TextUnderIcon
@ -79,7 +93,7 @@ Column {
anchors.rightMargin: 4 anchors.rightMargin: 4
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
font.pixelSize: 14 font.pixelSize: 14
text: Objects.types[modelData].displayType() text: Modules.Objects.types[modelData].displayType()
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
clip: true clip: true
} }
@ -89,13 +103,26 @@ Column {
ToolTip.text: label.text ToolTip.text: label.text
onClicked: { onClicked: {
var newObj = Objects.createNewRegisteredObject(modelData) let newObj = Modules.Objects.createNewRegisteredObject(modelData)
history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export())) history.addToHistory(new HistoryLib.CreateNewObject(newObj.name, modelData, newObj.export()))
objectLists.update() objectLists.update()
objectEditor.obj = Objects.currentObjects[modelData][Objects.currentObjects[modelData].length - 1]
objectEditor.objType = modelData let hasXProp = newObj.constructor.properties().hasOwnProperty('x')
objectEditor.objIndex = Objects.currentObjects[modelData].length - 1 let hasYProp = newObj.constructor.properties().hasOwnProperty('y')
objectEditor.show() if(hasXProp || hasYProp) {
// Open picker
posPicker.objType = newObj.type
posPicker.objName = newObj.name
posPicker.pickX = hasXProp
posPicker.pickY = hasYProp
posPicker.propertyX = 'x'
posPicker.propertyY = 'y'
posPicker.visible = true
posPicker.picked.connect(openEditorDialog)
} else {
// Open editor
openEditorDialog(newObj)
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,12 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Dialogs 1.3 as D // import QtQuick.Dialogs 1.3 as D
import QtQuick.Controls 2.12 import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import eu.ad5001.LogarithmPlotter.ObjectLists.Editor 1.0 as Editor import eu.ad5001.LogarithmPlotter.ObjectLists.Editor 1.0 as Editor
import "../js/objects.js" as Objects
/*! /*!
\qmltype ObjectLists \qmltype ObjectLists
@ -47,7 +46,7 @@ ScrollView {
ListView { ListView {
id: objectsListView id: objectsListView
model: Object.keys(Objects.types) model: Object.keys(Modules.Objects.types)
//width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0) //width: implicitWidth //objectListList.width - (implicitHeight > objectListList.parent.height ? 20 : 0)
implicitHeight: contentItem.childrenRect.height + footerItem.height + 10 implicitHeight: contentItem.childrenRect.height + footerItem.height + 10
@ -55,7 +54,7 @@ ScrollView {
id: objTypeList id: objTypeList
property string objType: objectsListView.model[index] property string objType: objectsListView.model[index]
property var editingRows: [] property var editingRows: []
model: Objects.currentObjects[objType] model: Modules.Objects.currentObjects[objType]
width: objectsListView.width width: objectsListView.width
implicitHeight: contentItem.childrenRect.height implicitHeight: contentItem.childrenRect.height
visible: model != undefined && model.length > 0 visible: model != undefined && model.length > 0
@ -70,21 +69,23 @@ ScrollView {
CheckBox { CheckBox {
id: typeVisibilityCheckBox 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: { onClicked: {
for(var obj of Objects.currentObjects[objType]) obj.visible = this.checked for(const obj of Modules.Objects.currentObjects[objType]) obj.visible = this.checked
for(var obj of objTypeList.editingRows) obj.objVisible = this.checked for(const obj of objTypeList.editingRows) obj.objVisible = this.checked
objectListList.changed() objectListList.changed()
} }
ToolTip.visible: hovered 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 { Label {
id: typeHeaderText id: typeHeaderText
verticalAlignment: TextInput.AlignVCenter 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 font.pixelSize: 20
} }
} }
@ -92,11 +93,11 @@ ScrollView {
delegate: ObjectRow { delegate: ObjectRow {
id: controlRow id: controlRow
width: objTypeList.width width: objTypeList.width
obj: Objects.currentObjects[objType][index] obj: Modules.Objects.currentObjects[objType][index]
posPicker: positionPicker posPicker: positionPicker
onChanged: { onChanged: {
obj = Objects.currentObjects[objType][index] obj = Modules.Objects.currentObjects[objType][index]
objectListList.update() objectListList.update()
} }
@ -110,12 +111,15 @@ ScrollView {
width: objectsListView.width width: objectsListView.width
objectEditor: objEditor objectEditor: objEditor
objectLists: objectListList objectLists: objectListList
posPicker: positionPicker
} }
} }
// Object editor // Object editor
Editor.Dialog { Editor.Dialog {
id: objEditor id: objEditor
posPicker: positionPicker
} }
/*! /*!
@ -125,7 +129,7 @@ ScrollView {
function update() { function update() {
objectListList.changed() objectListList.changed()
for(var objType in objectListList.listViews) { for(var objType in objectListList.listViews) {
objectListList.listViews[objType].model = Objects.currentObjects[objType] objectListList.listViews[objType].model = Modules.Objects.currentObjects[objType]
} }
} }

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
import "js/objects.js" as Objects import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "js/mathlib.js" as MathLib import "js/math/index.mjs" as MathLib
import "js/historylib.js" as HistoryLib import "js/history/index.mjs" as HistoryLib
/*! /*!
\qmltype PickLocationOverlay \qmltype PickLocationOverlay
@ -36,6 +36,15 @@ import "js/historylib.js" as HistoryLib
Item { Item {
id: pickerRoot id: pickerRoot
visible: false visible: false
clip: true
/*!
\qmlsignal PickLocationOverlay::picked(var obj)
Emitted when a location has been picked
The corresponding handler is \c onPicked.
*/
signal picked(var obj)
/*! /*!
\qmlproperty var PickLocationOverlay::canvas \qmlproperty var PickLocationOverlay::canvas
@ -54,12 +63,12 @@ Item {
property string objName: 'A' property string objName: 'A'
/*! /*!
\qmlproperty bool PickLocationOverlay::pickX \qmlproperty bool PickLocationOverlay::pickX
true if the user should be picking a position on the x axis. true if the property in propertyX is pickable.
*/ */
property bool pickX: true property bool pickX: true
/*! /*!
\qmlproperty bool PickLocationOverlay::pickY \qmlproperty bool PickLocationOverlay::pickY
true if the user should be picking a position on the y axis. true if the property in propertyY is pickable.
*/ */
property bool pickY: true property bool pickY: true
/*! /*!
@ -77,6 +86,16 @@ Item {
Precision of the picked value (post-dot precision). Precision of the picked value (post-dot precision).
*/ */
property alias precision: precisionSlider.value property alias precision: precisionSlider.value
/*!
\qmlproperty bool PickLocationOverlay::userPickX
true if the user can and wants to be picking a position on the x axis.
*/
readonly property bool userPickX: pickX && pickXCheckbox.checked
/*!
\qmlproperty bool PickLocationOverlay::userPickY
true if the user can and wants to be picking a position on the y axis.
*/
readonly property bool userPickY: pickY && pickYCheckbox.checked
Rectangle { Rectangle {
color: sysPalette.window color: sysPalette.window
@ -90,61 +109,149 @@ Item {
hoverEnabled: parent.visible hoverEnabled: parent.visible
cursorShape: Qt.CrossCursor cursorShape: Qt.CrossCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: { onClicked: function(mouse) {
if(mouse.button == Qt.LeftButton) { // Validate if(mouse.button == Qt.LeftButton) { // Validate
let newValueX = !parent.pickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX) let newValueX = !parent.userPickX ? null : parseValue(picked.mouseX.toString(), objType, propertyX)
let newValueY = !parent.pickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY) let newValueY = !parent.userPickY ? null : parseValue(picked.mouseY.toString(), objType, propertyY)
let obj = Objects.currentObjectsByName[objName] let obj = Modules.Objects.currentObjectsByName[objName]
// Set values // Set values
if(parent.pickX && parent.pickY) { if(parent.userPickX && parent.userPickY) {
history.addToHistory(new HistoryLib.EditedPosition( history.addToHistory(new HistoryLib.EditedPosition(
objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY objName, objType, obj[propertyX], newValueX, obj[propertyY], newValueY
)) ))
obj[propertyX] = newValueX obj[propertyX] = newValueX
obj[propertyY] = newValueY obj[propertyY] = newValueY
} else if(parent.pickX) { obj.update()
objectLists.update()
pickerRoot.picked(obj)
} else if(parent.userPickX) {
history.addToHistory(new HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
objName, objType, propertyX, obj[propertyX], newValueX objName, objType, propertyX, obj[propertyX], newValueX
)) ))
obj[propertyX] = newValueX obj[propertyX] = newValueX
} else if(parent.pickY) { obj.update()
objectLists.update()
pickerRoot.picked(obj)
} else if(parent.userPickY) {
history.addToHistory(new HistoryLib.EditedProperty( history.addToHistory(new HistoryLib.EditedProperty(
objName, objType, propertyY, obj[propertyY], newValueY objName, objType, propertyY, obj[propertyY], newValueY
)) ))
obj[propertyY] = newValueY obj[propertyY] = newValueY
obj.update()
objectLists.update()
pickerRoot.picked(obj)
} }
obj.update()
objectLists.update()
} }
pickerRoot.visible = false; pickerRoot.visible = false;
} }
} }
Row {
height: precisionSlider.height
Text {
text: " "+ qsTr("Pointer precision:") + " "
color: 'black'
anchors.verticalCenter: parent.verticalCenter
}
Slider {
id: precisionSlider Rectangle {
from: 0 id: pickerSettings
value: 2 radius: 15
to: 10 color: sysPalette.window
stepSize: 1 width: pickerSettingsColumn.width + 30;
ToolTip { height: pickerSettingsColumn.height + 20
parent: precisionSlider.handle property bool folded: false;
visible: precisionSlider.pressed x: -15 - ((width-55) * folded);
text: precisionSlider.value.toFixed(0) y: 10
z: 2
Row {
id: pickerSettingsColumn
anchors {
left: parent.left
top: parent.top
leftMargin: 20
topMargin: 10
} }
} spacing: 15
property int cellHeight: 15
CheckBox { Column {
id: snapToGridCheckbox spacing: 5
text: qsTr("Snap to grid") // width: 100
checked: false
Text {
text: qsTr("Pointer precision:")
color: sysPalette.windowText
verticalAlignment: Text.AlignVCenter
height: pickerSettingsColumn.cellHeight
}
Text {
text: qsTr("Snap to grid:")
color: sysPalette.windowText
verticalAlignment: Text.AlignVCenter
height: pickerSettingsColumn.cellHeight
}
CheckBox {
id: pickXCheckbox
height: pickerSettingsColumn.cellHeight
text: qsTr("Pick X")
checked: pickX
visible: pickX
}
}
Column {
spacing: 5
Slider {
id: precisionSlider
from: 0
value: 2
to: 10
stepSize: 1
height: pickerSettingsColumn.cellHeight
ToolTip {
parent: precisionSlider.handle
visible: precisionSlider.pressed
text: precisionSlider.value.toFixed(0)
}
}
CheckBox {
id: snapToGridCheckbox
height: pickerSettingsColumn.cellHeight
// text: qsTr("Snap to grid")
checked: false
}
CheckBox {
id: pickYCheckbox
height: pickerSettingsColumn.cellHeight
text: qsTr("Pick Y")
checked: pickY
visible: pickY
}
}
Button {
width: 24
anchors.top: parent.top
anchors.bottom: parent.bottom
flat: true
onClicked: pickerSettings.folded = !pickerSettings.folded
ToolTip.visible: hovered
ToolTip.delay: 200
ToolTip.text: pickerSettings.folded ? qsTr("Open picker settings") : qsTr("Hide picker settings")
Setting.Icon {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: 18
height: 18
color: sysPalette.windowText
source: `../icons/common/settings.svg`
}
}
} }
} }
@ -155,8 +262,8 @@ Item {
color: 'black' color: 'black'
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: canvas.x2px(picked.mouseX) anchors.leftMargin: Modules.Canvas.x2px(picked.mouseX)
visible: parent.pickX visible: parent.userPickX
} }
Rectangle { Rectangle {
@ -166,45 +273,48 @@ Item {
color: 'black' color: 'black'
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: canvas.y2px(picked.mouseY) anchors.topMargin: Modules.Canvas.y2px(picked.mouseY)
visible: parent.pickY visible: parent.userPickY
} }
Text { Text {
id: picked id: picked
x: picker.mouseX - width - 5 x: picker.mouseX - width - 5
y: picker.mouseY - height - 5 y: picker.mouseY - height - 5
property double axisX: canvas.xaxisstep1 color: 'black'
property double mouseX: { 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(snapToGridCheckbox.checked) {
if(canvas.logscalex) { if(canvas.logscalex) {
// Calculate the logged power // Calculate the logged power
let pow = Math.pow(10, Math.floor(Math.log10(xpos))) let pow = Math.pow(10, Math.floor(Math.log10(xpos)))
return pow*Math.round(xpos/pow) return pow*Math.round(xpos/pow)
} else { } else {
return canvas.xaxisstep1*Math.round(xpos/canvas.xaxisstep1) return axisX*Math.round(xpos/axisX)
} }
} else { } else {
return xpos.toFixed(parent.precision) return xpos.toFixed(parent.precision)
} }
} }
property double mouseY: { 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) { if(snapToGridCheckbox.checked) {
return canvas.yaxisstep1*Math.round(ypos/canvas.yaxisstep1) return axisY*Math.round(ypos/axisY)
} else { } else {
return ypos.toFixed(parent.precision) return ypos.toFixed(parent.precision)
} }
} }
color: 'black'
text: { text: {
if(parent.pickX && parent.pickY) if(parent.userPickX && parent.userPickY)
return `(${mouseX}, ${mouseY})` return `(${mouseX}, ${mouseY})`
if(parent.pickX) else if(parent.userPickX)
return `X = ${mouseX}` return `X = ${mouseX}`
if(parent.pickY) else if(parent.userPickY)
return `Y = ${mouseY}` return `Y = ${mouseY}`
else
return qsTr('(no pick selected)')
} }
} }
@ -214,7 +324,7 @@ Item {
Parses a given \c value as an expression or a number depending on the type of \c propertyName of all \c objType. Parses a given \c value as an expression or a number depending on the type of \c propertyName of all \c objType.
*/ */
function parseValue(value, objType, propertyName) { function parseValue(value, objType, propertyName) {
if(Objects.types[objType].properties()[propertyName] == 'number') if(Modules.Objects.types[objType].properties()[propertyName] == 'number')
return parseFloat(value) return parseFloat(value)
else else
return new MathLib.Expression(value) return new MathLib.Expression(value)

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Dialogs 1.3 as D import QtQuick.Controls
import QtQuick.Controls 2.12
/*! /*!
\qmltype About \qmltype About
@ -27,98 +26,112 @@ import QtQuick.Controls 2.12
\sa LogarithmPlotter \sa LogarithmPlotter
*/ */
D.Dialog { BaseDialog {
id: about id: about
title: qsTr("About LogarithmPlotter") title: qsTr("About LogarithmPlotter")
width: 400 width: 400
height: 600 minimumHeight: 600
Image { Item {
id: logo anchors {
source: "../icons/logarithmplotter.svg" top: parent.top;
sourceSize.width: 64 left: parent.left;
sourceSize.height: 64 bottom: parent.bottom;
width: 64 right: parent.right;
height: 64 topMargin: margin;
anchors.horizontalCenter: parent.horizontalCenter leftMargin: margin;
anchors.rightMargin: width/2 bottomMargin: margin;
anchors.top: parent.top rightMargin: margin;
anchors.topMargin: 10 }
}
Label { Image {
id: appName id: logo
anchors.top: logo.bottom source: "../icons/logarithmplotter.svg"
anchors.left: parent.left sourceSize.width: 64
anchors.topMargin: 10 sourceSize.height: 64
horizontalAlignment: Text.AlignHCenter width: 64
width: parent.width height: 64
wrapMode: Text.WordWrap anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 25 anchors.rightMargin: width/2
text: qsTr("LogarithmPlotter v%1").arg(Helper.getVersion()) anchors.top: parent.top
} anchors.topMargin: 10
}
Label { Label {
id: description id: appName
anchors.top: appName.bottom anchors.top: logo.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: 10 anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 18 font.pixelSize: 25
text: qsTr("2D plotter software to make BODE plots, sequences and repartition functions.") text: qsTr("LogarithmPlotter v%1").arg(Helper.getVersion())
} }
Label { Label {
id: debugInfos id: description
anchors.top: description.bottom anchors.top: appName.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: 10 anchors.topMargin: 10
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
width: parent.width width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 14 font.pixelSize: 18
text: Helper.getDebugInfos() text: qsTr("2D plotter software to make BODE plots, sequences and repartition functions.")
} }
Label { Label {
id: copyrightInfos id: debugInfos
anchors.top: debugInfos.bottom anchors.top: description.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.left: parent.left
anchors.topMargin: 10 anchors.topMargin: 10
width: Math.min(410, parent.width) horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap width: parent.width
textFormat: Text.RichText wrapMode: Text.WordWrap
font.pixelSize: 13 font.pixelSize: 14
text: "Copyright © 2022 Ad5001 &lt;mail@ad5001.eu&gt;<br> text: Helper.getDebugInfos()
}
Label {
id: copyrightInfos
anchors.top: debugInfos.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
width: Math.min(410, parent.width)
wrapMode: Text.WordWrap
textFormat: Text.RichText
font.pixelSize: 13
text: "Copyright © 2021-2024 Ad5001 &lt;mail@ad5001.eu&gt;<br>
<br> <br>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.<br> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.<br>
<br> <br>
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.<br> This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.<br>
<br> <br>
You should have received a copy of the GNU General Public License along with this program. If not, see <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>." You should have received a copy of the GNU General Public License along with this program. If not, see <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>."
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
}
Row {
anchors.top: copyrightInfos.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 10
spacing: 5
Button {
id: openIssueButton
text: qsTr('Report a bug')
icon.name: 'tools-report-bug'
onClicked: Qt.openUrlExternally('https://git.ad5001.eu/Ad5001/LogarithmPlotter')
} }
Button { Row {
id: officialWebsiteButton id: buttonsRow
text: qsTr('Official website') anchors.top: copyrightInfos.bottom
icon.name: 'web-browser' anchors.horizontalCenter: parent.horizontalCenter
onClicked: Qt.openUrlExternally('https://apps.ad5001.eu/logarithmplotter/') anchors.topMargin: 10
spacing: 5
Button {
id: openIssueButton
text: qsTr('Report a bug')
icon.name: 'tools-report-bug'
onClicked: Qt.openUrlExternally('https://git.ad5001.eu/Ad5001/LogarithmPlotter')
}
Button {
id: officialWebsiteButton
text: qsTr('Official website')
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally('https://apps.ad5001.eu/logarithmplotter/')
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
/*! /*!
\qmltype Alert \qmltype Alert
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup \inqmlmodule eu.ad5001.LogarithmPlotter.Popup

View file

@ -0,0 +1,59 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
/*!
\qmltype BaseDialog
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
\brief Base dialog window in replacement of Dialog Popup from Qt 5.
\sa LogarithmPlotter
*/
Window {
id: base
color: sysPalette.window
visible: false;
flags: Qt.Dialog | Qt.Popup | Qt.MSWindowsFixedSizeDialogHint
modality: Qt.WindowModal
minimumWidth: width
maximumWidth: width
height: minimumHeight
property int margin: 10
Button {
id: closeButton
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottomMargin: margin
anchors.rightMargin: margin
text: qsTr('Close')
onClicked: close()
}
Shortcut {
sequence: "Esc"
onActivated: base.close()
}
function open() {
show()
}
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
/*! /*!
\qmltype Changelog \qmltype Changelog
@ -32,7 +32,7 @@ Popup {
id: changelogPopup id: changelogPopup
x: (parent.width-width)/2 x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2) y: Math.max(20, (parent.height-height)/2)
width: changelog.width+40 width: 800
height: Math.min(parent.height-40, 500) height: Math.min(parent.height-40, 500)
modal: true modal: true
focus: true focus: true
@ -44,42 +44,62 @@ Popup {
*/ */
property bool changelogNeedsFetching: true property bool changelogNeedsFetching: true
onAboutToShow: if(changelogNeedsFetching) Helper.fetchChangelog() onAboutToShow: if(changelogNeedsFetching) {
Helper.fetchChangelog()
}
Connections { Connections {
target: Helper target: Helper
function onChangelogFetched(chl) { function onChangelogFetched(chl) {
changelogNeedsFetching = false; changelogNeedsFetching = false;
changelog.text = chl changelog.text = chl
changelogView.contentItem.implicitHeight = changelog.height
// console.log(changelog.height, changelogView.contentItem.implicitHeight)
} }
} }
ScrollView { ScrollView {
id: changelogView
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 10 anchors.topMargin: 10
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 10 anchors.leftMargin: 10
anchors.right: parent.right
anchors.rightMargin: 10
anchors.bottom: doneBtn.top anchors.bottom: doneBtn.top
anchors.bottomMargin: 10 anchors.bottomMargin: 10
clip: true clip: true
Label { Label {
id: changelog id: changelog
color: sysPalette.windowText color: sysPalette.windowText
width: 760
wrapMode: Text.WordWrap
textFormat: TextEdit.MarkdownText textFormat: TextEdit.MarkdownText
text: qsTr("Fetching changelog...") text: qsTr("Fetching changelog...")
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
} }
} }
Rectangle {
id: bottomSeparator
opacity: 0.3
color: sysPalette.windowText
width: parent.width * 2 / 3
height: 1
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: doneBtn.top
anchors.bottomMargin: 7
}
Button { Button {
id: doneBtn id: doneBtn
text: qsTr("Done") text: qsTr("Close")
font.pixelSize: 18 font.pixelSize: 18
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: 10 anchors.bottomMargin: 7
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
onClicked: changelogPopup.close() onClicked: changelogPopup.close()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Dialogs 1.3 as D import Qt.labs.platform
/*! /*!
\qmltype FileDialog \qmltype FileDialog
@ -25,7 +25,7 @@ import QtQuick.Dialogs 1.3 as D
\sa LogarithmPlotter, Settings \sa LogarithmPlotter, Settings
*/ */
D.FileDialog { FileDialog {
id: fileDialog id: fileDialog
property bool exportMode: false property bool exportMode: false
@ -33,6 +33,6 @@ D.FileDialog {
title: exportMode ? qsTr("Export Logarithm Plot file") : qsTr("Import Logarithm Plot file") title: exportMode ? qsTr("Export Logarithm Plot file") : qsTr("Import Logarithm Plot file")
nameFilters: ["Logarithm Plot File (*.lpf)", "All files (*)"] nameFilters: ["Logarithm Plot File (*.lpf)", "All files (*)"]
folder: shortcuts.documents defaultSuffix: 'lpf'
selectExisting: !exportMode fileMode: exportMode ? FileDialog.SaveFile : FileDialog.OpenFile
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
import "../js/math/latex.js" as Latex import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
/*! /*!
\qmltype GreetScreen \qmltype GreetScreen
@ -33,175 +33,122 @@ Popup {
id: greetingPopup id: greetingPopup
x: (parent.width-width)/2 x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2) y: Math.max(20, (parent.height-height)/2)
width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20 width: greetingLayout.width+20
height: Math.min(parent.height-40, 500) height: Math.min(parent.height-40, 700)
modal: true modal: true
focus: true focus: true
clip: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
Item { Column {
id: welcome id: greetingLayout
height: logo.height width: 600
width: logo.width + 10 + welcomeText.width spacing: 10
anchors.top: parent.top clip: true
anchors.topMargin: (parent.width-width)/2 topPadding: 35
anchors.horizontalCenter: parent.horizontalCenter
Image { Row {
id: logo id: welcome
source: "../icons/logarithmplotter.svg" height: logo.height
sourceSize.width: 48 spacing: 10
sourceSize.height: 48 anchors.horizontalCenter: parent.horizontalCenter
width: 48
height: 48 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 { Label {
id: welcomeText id: versionText
anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.left: logo.right
anchors.leftMargin: 10
//width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 32 width: implicitWidth
text: qsTr("Welcome to LogarithmPlotter") font.pixelSize: 18
font.italic: true
text: qsTr("Version %1").arg(Helper.getVersion())
} }
} }
Label { Grid {
id: versionText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: welcome.bottom anchors.top: greetingLayout.bottom
anchors.topMargin: 10 anchors.topMargin: 50
//width: parent.width columns: 2
wrapMode: Text.WordWrap
width: implicitWidth
font.pixelSize: 18
font.italic: true
text: qsTr("Version %1").arg(Helper.getVersion())
}
Label {
id: helpText
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: versionText.bottom
anchors.topMargin: 40
//width: parent.width
wrapMode: Text.WordWrap
font.pixelSize: 14
width: parent.width - 50
text: qsTr("Take a few seconds to configure LogarithmPlotter.\nThese settings can be changed at any time from the \"Settings\" menu.")
}
CheckBox {
id: checkForUpdatesSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: helpText.bottom
anchors.topMargin: 10
checked: Helper.getSettingBool("check_for_updates")
text: qsTr('Check for updates on startup (requires online connectivity)')
onClicked: {
Helper.setSettingBool("check_for_updates", checked)
// Set in the menu bar
appMenu.settings.children[0].checked = checked
}
}
CheckBox {
id: resetRedoStackSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: checkForUpdatesSetting.bottom
checked: Helper.getSettingBool("reset_redo_stack")
text: qsTr('Reset redo stack when a new action is added to history')
onClicked: {
Helper.setSettingBool("reset_redo_stack", checked)
appMenu.settings.children[1].checked = checked
}
}
CheckBox {
id: enableLatexSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: resetRedoStackSetting.bottom
checked: Helper.getSettingBool("enable_latex")
text: qsTr('Enable LaTeX rendering')
onClicked: {
Helper.setSettingBool("enable_latex", checked)
appMenu.settings.children[2].checked = checked
}
}
CheckBox {
id: autocloseFormulaSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: enableLatexSetting.bottom
checked: Helper.getSettingBool("expression_editor.autoclose")
text: qsTr('Automatically close parenthesises and brackets in expressions')
onClicked: {
Helper.setSettingBool("expression_editor.autoclose", checked)
appMenu.settings.children[3].children[0].checked = checked
}
}
CheckBox {
id: colorizeFormulaSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: autocloseFormulaSetting.bottom
checked: Helper.getSettingBool("expression_editor.colorize")
text: qsTr('Enable syntax highlighting for expressions')
onClicked: {
Helper.setSettingBool("expression_editor.colorize", checked)
appMenu.settings.children[3].children[1].checked = checked
}
}
CheckBox {
id: autocompleteFormulaSetting
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
anchors.top: colorizeFormulaSetting.bottom
checked: Helper.getSettingBool("autocompletion.enabled")
text: qsTr('Enable autocompletion interface in expression editor')
onClicked: {
Helper.setSettingBool("autocompletion.enabled", checked)
appMenu.settings.children[3].children[2].checked = checked
}
}
Row {
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
spacing: 10 spacing: 10
anchors.horizontalCenter: parent.horizontalCenter
Button { Repeater {
id: userManualBtn model: [{
text: qsTr("User manual") name: qsTr("Changelog"),
font.pixelSize: 18 icon: 'common/new.svg',
onClicked: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar") 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 { Button {
id: changelogBtn id: createBtn
text: qsTr("Changelog") width: 96
font.pixelSize: 18 height: 96
onClicked: changelog.open() onClicked: modelData.onClicked()
}
Button { Setting.Icon {
id: doneBtn id: icon
text: qsTr("Done") width: 24
font.pixelSize: 18 height: 24
onClicked: greetingPopup.close() anchors {
left: parent.left
leftMargin: (parent.width-width)/2
top: parent.top
topMargin: (label.y-height)/2
}
color: sysPalette.windowText
source: '../icons/' + modelData.icon
}
Label {
id: label
anchors {
bottom: parent.bottom
bottomMargin: 5
left: parent.left
leftMargin: 4
right: parent.right
rightMargin: 4
}
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 14
text: modelData.name
wrapMode: Text.WordWrap
clip: true
}
}
} }
} }
Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()) { Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()+1) {
greetingPopup.open() greetingPopup.open()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls 2.12 import QtQuick.Controls
import QtQuick 2.12 import QtQuick
/*! /*!
\qmltype InsertCharacter \qmltype InsertCharacter
@ -31,8 +31,19 @@ Popup {
signal selected(string character) signal selected(string character)
/*!
\qmlproperty string InsertCharacter::category
Type of special character to insert.
Possible values:
- expression
- domain
- name
- all
*/
property string category: 'all'
width: 280 width: 280
height: insertGrid.insertChars.length/insertGrid.columns*(width/insertGrid.columns) height: Math.ceil(insertGrid.insertChars.length/insertGrid.columns)*(width/insertGrid.columns)+5
modal: true modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
@ -41,17 +52,40 @@ Popup {
width: parent.width width: parent.width
columns: 7 columns: 7
property var insertChars: [ property var insertCharsExpression: [
"∞","π","¹","²","³","⁴","⁵",
"⁶","⁷","⁸","⁹","⁰"
]
property var insertCharsDomain: [
"∅","","∩","","","","",
"⁺","⁻",...insertCharsExpression
]
property var insertCharsName: [
"α","β","γ","δ","ε","ζ","η", "α","β","γ","δ","ε","ζ","η",
"π","θ","κ","λ","μ","ξ","ρ", "π","θ","κ","λ","μ","ξ","ρ",
"ς","σ","τ","φ","χ","ψ","ω", "ς","σ","τ","φ","χ","ψ","ω",
"Γ","Δ","Θ","Λ","Ξ","Π","Σ", "Γ","Δ","Θ","Λ","Ξ","Π","Σ",
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ", "Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ", "ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","¹","²","³","⁴","⁵","⁶", "ₜ","₁","₂","₃","₄","₅","₆",
"⁷","⁸","⁹","⁰","₁","₂","₃", "₇","₈","₉","₀"
"₄","₅","₆","₇","₈","₉","₀"
] ]
property var insertCharsAll: [
...insertCharsName, ...insertCharsDomain
]
property var insertChars: {
return {
"expression": insertCharsExpression,
"domain": insertCharsDomain,
"name": insertCharsName,
"all": insertCharsAll
}[insertPopup.category]
}
Repeater { Repeater {
model: parent.insertChars.length model: parent.insertChars.length

View file

@ -0,0 +1,256 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "../js/preferences/common.mjs" as S
import "../js/utils.mjs" as Utils
/*!
\qmltype Preferences
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
\brief Popup to change global application preferences.
\sa LogarithmPlotter, GreetScreen
*/
Popup {
id: preferencesPopup
x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2)
width: settingPopupRow.width + 30
height: settingPopupRow.height + 20
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
// Components for the preferences
Component {
id: boolSettingComponent
CheckBox {
height: 20
text: setting.name
checked: setting.value()
onClicked: setting.set(this.checked)
}
}
Component {
id: enumIntSettingComponent
// Setting when selecting data from an enum, or an object of a certain type.
Setting.ComboBoxSetting {
height: 30
label: setting.name
icon: `settings/${setting.icon}.svg`
currentIndex: setting.value()
model: setting.values
onActivated: function(newIndex) { setting.set(newIndex) }
}
}
Component {
id: stringSettingComponent
Setting.ComboBoxSetting {
height: 30
label: setting.name
icon: `settings/${setting.icon}.svg`
editable: true
currentIndex: find(setting.value())
model: setting.defaultValues
onAccepted: function() {
editText = Utils.parseName(editText, false)
if(find(editText) === -1) model.append(editText)
setting.set(editText)
}
onActivated: function(selectedId) {
setting.set(model[selectedId])
}
Component.onCompleted: editText = setting.value()
}
}
Component {
id: numberSettingComponent
Setting.TextSetting {
height: 30
isDouble: true
label: setting.name
min: setting.min()
icon: `settings/${setting.icon}.svg`
value: setting.value()
onChanged: function(newValue) {
if(newValue < setting.max())
setting.set(newValue)
else {
value = setting.max()
setting.set(setting.max())
}
}
}
}
Component {
id: expressionSettingComponent
Setting.ExpressionEditor {
height: 30
label: setting.name
icon: `settings/${setting.icon}.svg`
defValue: Utils.simplifyExpression(setting.value())
variables: setting.variables
allowGraphObjects: false
property string propertyName: setting.name
onChanged: function(newExpr) {
try {
setting.set(newExpr)
} catch(e) {
errorDialog.showDialog(propertyName, newExpr, e.message)
}
}
}
}
Row {
id: settingPopupRow
height: 300
width: categories.width + categorySeparator.width + settingView.width + 70
spacing: 15
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
right: parent.right
topMargin: 10
bottomMargin: 10
rightMargin: 15
leftMargin: 15
}
ColumnLayout {
id: categories
width: 150
height: parent.height
spacing: 0
clip: true
Repeater {
model: Object.keys(Modules.Preferences.categories)
Button {
// width: 150
Layout.fillWidth: true
text: qsTranslate('settingCategory', modelData)
onClicked: {
settingView.modelName = modelData
}
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
Button {
id: closeButton
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
text: qsTr('Close')
onClicked: preferencesPopup.close()
}
}
}
Rectangle {
id: categorySeparator
anchors {
top: parent.top
topMargin: 5
}
opacity: 0.3
color: sysPalette.windowText
height: parent.height - 10
width: 1
}
ListView {
id: settingView
clip: true
width: 500
spacing: 10
model: Modules.Preferences.categories[modelName]
anchors {
top: parent.top
bottom: parent.bottom
}
ScrollBar.vertical: ScrollBar { }
property string modelName: 'general'
header: Text {
id: settingCategoryName
font.pixelSize: 32
height: 48
color: sysPalette.windowText
text: qsTranslate('settingCategory', settingView.modelName)
Rectangle {
id: bottomSeparator
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
opacity: 0.3
color: sysPalette.windowText
width: settingView.width
height: 1
}
}
delegate: Component {
Loader {
width: settingView.width - 20
property var setting: Modules.Preferences.categories[settingView.modelName][index]
sourceComponent: {
if(setting.type === "bool")
return boolSettingComponent
else if(setting.type === "enum")
return enumIntSettingComponent
else if(setting.type === "number")
return numberSettingComponent
else if(setting.type === "expression")
return expressionSettingComponent
else if(setting.type === "string")
return stringSettingComponent
else
console.log('Unknown setting type!', setting.constructor.nameInConfig, setting.constructor)
}
}
}
}
}
// Component.onCompleted: open()
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Dialogs 1.3 as D import QtQuick.Dialogs
import QtQuick.Controls 2.12 import QtQuick.Controls
/*! /*!
\qmltype ThanksTo \qmltype ThanksTo
@ -27,289 +27,326 @@ import QtQuick.Controls 2.12
\sa LogarithmPlotter \sa LogarithmPlotter
*/ */
D.Dialog { BaseDialog {
id: about id: thanks
title: qsTr("Thanks and Contributions - LogarithmPlotter") title: qsTr("Thanks and Contributions - LogarithmPlotter")
width: 400 width: 450
height: 600 minimumHeight: 600
Column { ScrollView {
anchors.fill: parent
spacing: 10
ListView { anchors {
id: librariesListView top: parent.top;
anchors.left: parent.left left: parent.left;
width: parent.width bottom: parent.bottom;
//height: parent.height right: parent.right;
implicitHeight: contentItem.childrenRect.height topMargin: margin;
leftMargin: margin;
bottomMargin: margin+30;
}
model: ListModel { Column {
Component.onCompleted: {
append({ anchors {
libName: 'expr-eval', left: parent.left;
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 { width: thanks.width - 2*margin
id: librariesUsedHeader spacing: 10
wrapMode: Text.WordWrap
font.pixelSize: 25
text: qsTr("Libraries included")
height: implicitHeight + 10
}
delegate: Column { ListView {
id: libClmn id: librariesListView
width: librariesListView.width anchors.left: parent.left
spacing: 10 width: parent.width
//height: parent.height
implicitHeight: contentItem.childrenRect.height
interactive: false
Item { model: ListModel {
height: libraryHeader.height Component.onCompleted: {
width: parent.width append({
libName: 'expr-eval',
Label { license: 'MIT',
id: libraryHeader licenseLink: 'https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt',
anchors.left: parent.left linkName: qsTr('Source code'),
wrapMode: Text.WordWrap link: 'https://github.com/silentmatt/expr-eval',
font.pixelSize: 18 authors: [{
text: libName 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',
Row { websiteName: qsTr('Source')
anchors.right: parent.right }, {
height: parent.height authorLine: qsTr('Ported to Javascript by Matthew Crumley'),
spacing: 10 email: 'email@matthewcrumley.com',
website: 'https://silentmatt.com/',
Button { websiteName: qsTr('Website')
height: parent.height }, {
text: license authorLine: qsTr('Ported to QMLJS by Ad5001'),
icon.name: 'license' email: 'mail@ad5001.eu',
onClicked: Qt.openUrlExternally(licenseLink) website: 'https://ad5001.eu/',
} websiteName: qsTr('Website')
}]
Button { })
height: parent.height
text: linkName
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally(link)
}
} }
} }
ListView { header: Label {
id: libAuthors id: librariesUsedHeader
anchors.left: parent.left wrapMode: Text.WordWrap
anchors.leftMargin: 10 font.pixelSize: 25
model: authors text: qsTr("Libraries included")
width: parent.width - 10 height: implicitHeight + 10
implicitHeight: contentItem.childrenRect.height }
delegate: Item { delegate: Column {
id: libAuthor id: libClmn
width: librariesListView.width - 10 width: librariesListView.width
height: 50 spacing: 10
Item {
height: libraryHeader.height
width: parent.width
Label { Label {
id: libAuthorName id: libraryHeader
anchors.left: parent.left anchors.left: parent.left
anchors.right: buttons.left
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 14 font.pixelSize: 18
text: authorLine text: libName
} }
Row { Row {
id: buttons
anchors.right: parent.right anchors.right: parent.right
height: parent.height height: parent.height
spacing: 10 spacing: 10
Button { Button {
anchors.verticalCenter: parent.verticalCenter height: parent.height
text: websiteName text: license
icon.name: 'web-browser' icon.name: 'license'
height: parent.height - 10 onClicked: Qt.openUrlExternally(licenseLink)
onClicked: Qt.openUrlExternally(website)
} }
Button { Button {
anchors.verticalCenter: parent.verticalCenter height: parent.height
text: qsTr('Email') text: linkName
icon.name: 'email' icon.name: 'web-browser'
height: parent.height - 10 onClicked: Qt.openUrlExternally(link)
onClicked: Qt.openUrlExternally('mailto:' + email)
} }
} }
} }
}
Rectangle { ListView {
id: libSeparator id: libAuthors
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
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.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 10
wrapMode: Text.WordWrap model: authors
font.pixelSize: 18 width: parent.width - 10
text: tranName implicitHeight: contentItem.childrenRect.height
} interactive: false
Row { delegate: Item {
anchors.right: parent.right id: libAuthor
anchors.verticalCenter: parent.verticalCenter width: librariesListView.width - 10
height: 30 height: 50
spacing: 10
Button { Label {
height: parent.height id: libAuthorName
text: qsTr('Improve') anchors.left: parent.left
icon.name: 'web-browser' anchors.right: buttons.left
onClicked: Qt.openUrlExternally(link) 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: ''
}
}
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]
})
}
} }
ListView { header: Label {
id: tranAuthors id: translationsHeader
anchors.left: parent.left wrapMode: Text.WordWrap
anchors.leftMargin: 10 font.pixelSize: 25
model: authors text: qsTr("Translations included")
width: parent.width - 10 height: implicitHeight + 10
implicitHeight: contentItem.childrenRect.height }
delegate: Item { delegate: Column {
id: tranAuthor id: tranClmn
width: tranAuthors.width width: translationsListView.width
height: 40
Item {
width: parent.width
height: translationHeader.height + 10
Label { Label {
id: tranAuthorName id: translationHeader
anchors.left: parent.left anchors.left: parent.left
anchors.right: buttons.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
font.pixelSize: 14 font.pixelSize: 18
text: authorLine text: tranName
} }
Row { Row {
id: buttons
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: 30 height: 30
spacing: 10 spacing: 10
Button { Button {
text: websiteName
icon.name: 'web-browser'
height: parent.height height: parent.height
onClicked: Qt.openUrlExternally(website) 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)
}
} }
} }
} }

View file

@ -1,9 +1,11 @@
module eu.ad5001.LogarithmPlotter.Popup module eu.ad5001.LogarithmPlotter.Popup
About 1.0 About.qml
Alert 1.0 Alert.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 FileDialog 1.0 FileDialog.qml
GreetScreen 1.0 GreetScreen.qml GreetScreen 1.0 GreetScreen.qml
Changelog 1.0 Changelog.qml
ThanksTo 1.0 ThanksTo.qml
InsertCharacter 1.0 InsertCharacter.qml InsertCharacter 1.0 InsertCharacter.qml
Preferences 1.0 Preferences.qml
ThanksTo 1.0 ThanksTo.qml

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
/*! /*!
\qmltype AutocompletionCategory \qmltype AutocompletionCategory
@ -84,11 +84,12 @@ ListView {
Text { Text {
leftPadding: 5 leftPadding: 5
text: listFiltered.category text: listFiltered.category
color: sysPalette.windowText
} }
Rectangle { Rectangle {
height: 1 height: 1
color: 'black' color: 'gray'
width: parent.width width: parent.width
} }
} }
@ -112,9 +113,11 @@ ListView {
Text { Text {
id: annotationText id: annotationText
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
topPadding: 2 topPadding: 2
bottomPadding: 2 bottomPadding: 2
rightPadding: 15 rightPadding: 15
font.pixelSize: autocompleteText.font.pixelSize - 2
text: listFiltered.model[index].annotation text: listFiltered.model[index].annotation
color: parent.selected ? sysPaletteIn.highlightedText : sysPaletteIn.windowText color: parent.selected ? sysPaletteIn.highlightedText : sysPaletteIn.windowText
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
/*! /*!
\qmltype ComboBoxSetting \qmltype ComboBoxSetting
@ -114,6 +114,7 @@ Item {
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
height: 30 height: 30
width: Math.max(85, implicitWidth)
anchors.top: parent.top anchors.top: parent.top
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
text: qsTranslate("control", "%1: ").arg(control.label) text: qsTranslate("control", "%1: ").arg(control.label)

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,14 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls 2.12 import QtQuick.Controls
import QtQuick 2.12 import QtQuick
import QtQuick.Dialogs 1.3 as D import Qt.labs.platform as Native
import eu.ad5001.LogarithmPlotter.Popup 1.0 as P import eu.ad5001.LogarithmPlotter.Popup 1.0 as P
import "../js/mathlib.js" as MathLib import "../js/math/index.mjs" as MathLib
import "../js/utils.js" as Utils import "../js/utils.mjs" as Utils
import "../js/objects.js" as Objects import "../js/parsing/parsing.mjs" as Parsing
import "../js/parsing/parsing.js" as Parsing
/*! /*!
@ -81,6 +80,17 @@ Item {
Icon path of the editor. Icon path of the editor.
*/ */
property string icon: "" property string icon: ""
/*!
\qmlproperty bool ExpressionEditor::allowGraphObjects
If true, allows graph objects to be used as part of the expression.
*/
property bool allowGraphObjects: true
/*!
\qmlproperty var ExpressionEditor::errorDialog
Allows to summon the error dialog when using additional external parsing.
*/
readonly property alias errorDialog: parsingErrorDialog
/*! /*!
\qmlproperty string ExpressionEditor::openAndCloseMatches \qmlproperty string ExpressionEditor::openAndCloseMatches
@ -95,19 +105,74 @@ Item {
} }
/*! /*!
\qmlproperty string ExpressionEditor::colorScheme \qmlproperty string ExpressionEditor::colorSchemes
Color scheme of the editor, currently based on Breeze Light. Color schemes of the editor.
TODO: Make it configurable.
*/ */
readonly property var colorScheme: { readonly property var colorSchemes: [
'NORMAL': "#1F1C1B", { // Breeze Light
'VARIABLE': "#0057AE", 'NORMAL': "#1F1C1B",
'CONSTANT': "#5E2F00", 'VARIABLE': "#0057AE",
'FUNCTION': "#644A9B", 'CONSTANT': "#006E28",
'OPERATOR': "#A44EA4", 'FUNCTION': "#644A9B",
'STRING': "#9C0E0E", 'OPERATOR': "#CA60CA",
'NUMBER': "#805C00" 'STRING': "#BF0303",
} 'NUMBER': "#B08000"
},
{ // Breeze Dark
'NORMAL': "#CFCFC2",
'VARIABLE': "#2980B9",
'CONSTANT': "#27AE60",
'FUNCTION': "#8E44AD",
'OPERATOR': "#A44EA4",
'STRING': "#F44F4F",
'NUMBER': "#F67400"
},
{ // Solarized
'NORMAL': "#839496",
'VARIABLE': "#B58900",
'CONSTANT': "#859900",
'FUNCTION': "#268BD2",
'OPERATOR': "#859900",
'STRING': "#2AA198",
'NUMBER': "#2AA198"
},
{ // GitHub Light
'NORMAL': "#24292E",
'VARIABLE': "#D73A49",
'CONSTANT': "#6F42C1",
'FUNCTION': "#6F42C1",
'OPERATOR': "#24292E",
'STRING': "#032F62",
'NUMBER': "#005CC5"
},
{ // GitHub Dark
'NORMAL': "#E1E4E8",
'VARIABLE': "#F97583",
'CONSTANT': "#B392f0",
'FUNCTION': "#B392f0",
'OPERATOR': "#E1E4E8",
'STRING': "#9ECBFF",
'NUMBER': "#79B8FF"
},
{ // Nord
'NORMAL': "#D8DEE9",
'VARIABLE': "#81A1C1",
'CONSTANT': "#8FBCBB",
'FUNCTION': "#88C0D0",
'OPERATOR': "#81A1C1",
'STRING': "#A3BE8C",
'NUMBER': "#B48EAD"
},
{ // Monokai
'NORMAL': "#F8F8F2",
'VARIABLE': "#66D9EF",
'CONSTANT': "#F92672",
'FUNCTION': "#A6E22E",
'OPERATOR': "#F8F8F2",
'STRING': "#E6DB74",
'NUMBER': "#AE81FF"
}
]
Icon { Icon {
id: iconLabel id: iconLabel
@ -123,25 +188,27 @@ Item {
id: labelItem id: labelItem
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
height: parent.height
anchors.top: parent.top anchors.top: parent.top
height: parent.height
width: Math.max(85, implicitWidth)
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText //color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : "" text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
visible: control.label != "" visible: control.label != ""
} }
D.MessageDialog { Native.MessageDialog {
id: parsingErrorDialog id: parsingErrorDialog
title: qsTranslate("expression", "LogarithmPlotter - Parsing error") title: qsTranslate("expression", "LogarithmPlotter - Parsing error")
text: "" text: ""
function showDialog(propName, propValue, error) { function showDialog(propName, propValue, error) {
text = qsTranslate("expression", "Error while parsing expression for property %1:\n%2\n\nEvaluated expression: %3").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() open()
} }
} }
TextField { TextField {
id: editor id: editor
anchors.top: parent.top anchors.top: parent.top
@ -152,7 +219,7 @@ Item {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
text: control.defValue text: control.defValue
color: syntaxHighlightingEnabled ? "transparent" : sysPalette.windowText color: syntaxHighlightingEnabled ? sysPalette.window : sysPalette.windowText
focus: true focus: true
selectByMouse: true selectByMouse: true
@ -175,8 +242,6 @@ Item {
} }
} }
//onTextEdited: acPopupContent.itemSelected = 0
onActiveFocusChanged: { onActiveFocusChanged: {
if(activeFocus && autocompleteEnabled) if(activeFocus && autocompleteEnabled)
autocompletePopup.open() autocompletePopup.open()
@ -184,6 +249,12 @@ Item {
autocompletePopup.close() autocompletePopup.close()
} }
cursorDelegate: Rectangle {
visible: editor.cursorVisible
color: sysPalette.windowText
width: editor.cursorRectangle.width
}
Keys.onUpPressed: function(event) { Keys.onUpPressed: function(event) {
if(autocompleteEnabled) if(autocompleteEnabled)
if(acPopupContent.itemSelected == 0) if(acPopupContent.itemSelected == 0)
@ -204,16 +275,11 @@ Item {
Keys.onPressed: function(event) { Keys.onPressed: function(event) {
// Autocomplete popup events // Autocomplete popup events
//console.log(acPopupContent.currentToken.dot, acPopupContent.previousToken.dot, "@", acPopupContent.currentToken.identifier, acPopupContent.previousToken.identifier, acPopupContent.previousToken2.identifier, objectPropertiesList.objectName, JSON.stringify(objectPropertiesList.baseText), objectPropertiesList.model.length, JSON.stringify(objectPropertiesList.categoryItems))
//console.log("Pressed key:", event.key, Qt.Key_Return, Qt.Key_Enter, event.text, acPopupContent.itemCount)
if(autocompleteEnabled && (event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) { if(autocompleteEnabled && (event.key == Qt.Key_Enter || event.key == Qt.Key_Return) && acPopupContent.itemCount > 0) {
acPopupContent.autocomplete() acPopupContent.autocomplete()
event.accepted = true event.accepted = true
} else } else
acPopupContent.itemSelected = 0 acPopupContent.itemSelected = 0
/*if(event.key == Qt.Key_Left) { // TODO: Don't reset the position when the key moved is still on the same word
if(!acPopupContent.identifierTokenTypes.includes())
}*/
if(event.text in openAndCloseMatches && autoClosing) { if(event.text in openAndCloseMatches && autoClosing) {
@ -328,7 +394,7 @@ Item {
id: objectPropertiesList id: objectPropertiesList
category: qsTr("Object Properties") category: qsTr("Object Properties")
visbilityCondition: doesObjectExist visbilityCondition: control.allowGraphObjects && doesObjectExist
itemStartIndex: 0 itemStartIndex: 0
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
property bool isEnteringProperty: ( property bool isEnteringProperty: (
@ -339,9 +405,9 @@ Item {
property string objectName: isEnteringProperty ? property string objectName: isEnteringProperty ?
(parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value) (parent.currentToken.dot ? parent.previousToken.value : parent.previousToken2.value)
: "" : ""
property bool doesObjectExist: isEnteringProperty && objectName in Objects.currentObjectsByName property bool doesObjectExist: isEnteringProperty && (objectName in Modules.Objects.currentObjectsByName)
property var objectProperties: doesObjectExist ? property var objectProperties: doesObjectExist ?
Objects.currentObjectsByName[objectName].constructor.properties() : Modules.Objects.currentObjectsByName[objectName].constructor.properties() :
{} {}
categoryItems: Object.keys(objectProperties) categoryItems: Object.keys(objectProperties)
autocompleteGenerator: (item) => { autocompleteGenerator: (item) => {
@ -380,7 +446,7 @@ Item {
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Parsing.CONSTANTS_LIST categoryItems: Parsing.CONSTANTS_LIST
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': '', 'text': item, 'annotation': Parsing.CONSTANTS[item],
'autocomplete': item + " ", 'cursorFinalOffset': 0 'autocomplete': item + " ", 'cursorFinalOffset': 0
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -395,7 +461,7 @@ Item {
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Parsing.FUNCTIONS_LIST categoryItems: Parsing.FUNCTIONS_LIST
autocompleteGenerator: (item) => {return { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': '', 'text': item, 'annotation': Parsing.FUNCTIONS_USAGE[item].join(', '),
'autocomplete': item+'()', 'cursorFinalOffset': -1 'autocomplete': item+'()', 'cursorFinalOffset': -1
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -405,12 +471,12 @@ Item {
id: executableObjectsList id: executableObjectsList
category: qsTr("Executable Objects") 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 itemStartIndex: functionsList.itemStartIndex + functionsList.model.length
itemSelected: parent.itemSelected itemSelected: parent.itemSelected
categoryItems: Objects.getObjectsName("ExecutableObject").filter(obj => obj != self) categoryItems: Modules.Objects.getObjectsName("ExecutableObject").filter(obj => obj != self)
autocompleteGenerator: (item) => {return { 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 'autocomplete': item+'()', 'cursorFinalOffset': -1
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -420,12 +486,12 @@ Item {
id: objectsList id: objectsList
category: qsTr("Objects") 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 itemStartIndex: executableObjectsList.itemStartIndex + executableObjectsList.model.length
itemSelected: parent.itemSelected 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 { autocompleteGenerator: (item) => {return {
'text': item, 'annotation': `${Objects.currentObjectsByName[item].constructor.displayType()}`, 'text': item, 'annotation': `${Modules.Objects.currentObjectsByName[item].constructor.displayType()}`,
'autocomplete': item+'.', 'cursorFinalOffset': 0 'autocomplete': item+'.', 'cursorFinalOffset': 0
}} }}
baseText: parent.visible ? parent.currentToken.value : "" baseText: parent.visible ? parent.currentToken.value : ""
@ -454,6 +520,8 @@ Item {
x: Math.round((parent.width - width) / 2) x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2) y: Math.round((parent.height - height) / 2)
category: "expression"
onSelected: function(c) { onSelected: function(c) {
editor.insert(editor.cursorPosition, c) editor.insert(editor.cursorPosition, c)
insertPopup.close() insertPopup.close()
@ -483,8 +551,8 @@ Item {
throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.')) throw new Error(qsTranslate('error', 'Object cannot be dependent on itself.'))
// Recursive dependencies // Recursive dependencies
let dependentOnSelfObjects = expr.requiredObjects().filter( let dependentOnSelfObjects = expr.requiredObjects().filter(
(obj) => Objects.currentObjectsByName[obj].getDependenciesList() (obj) => Modules.Objects.currentObjectsByName[obj].getDependenciesList()
.includes(Objects.currentObjectsByName[control.self]) .includes(Modules.Objects.currentObjectsByName[control.self])
) )
if(dependentOnSelfObjects.length == 1) if(dependentOnSelfObjects.length == 1)
throw new Error(qsTranslate('error', 'Circular dependency detected. Object %1 depends on %2.').arg(dependentOnSelfObjects[0].toString()).arg(control.self)) throw new Error(qsTranslate('error', 'Circular dependency detected. Object %1 depends on %2.').arg(dependentOnSelfObjects[0].toString()).arg(control.self))
@ -534,25 +602,26 @@ Item {
*/ */
function colorize(tokenList) { function colorize(tokenList) {
let parsedText = "" let parsedText = ""
let scheme = colorSchemes[Helper.getSettingInt("expression_editor.color_scheme")]
for(let token of tokenList) { for(let token of tokenList) {
switch(token.type) { switch(token.type) {
case Parsing.TokenType.VARIABLE: case Parsing.TokenType.VARIABLE:
parsedText += `<font color="${colorScheme.VARIABLE}">${token.value}</font>` parsedText += `<font color="${scheme.VARIABLE}">${token.value}</font>`
break; break;
case Parsing.TokenType.CONSTANT: case Parsing.TokenType.CONSTANT:
parsedText += `<font color="${colorScheme.CONSTANT}">${token.value}</font>` parsedText += `<font color="${scheme.CONSTANT}">${token.value}</font>`
break; break;
case Parsing.TokenType.FUNCTION: case Parsing.TokenType.FUNCTION:
parsedText += `<font color="${colorScheme.FUNCTION}">${Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${scheme.FUNCTION}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case Parsing.TokenType.OPERATOR: case Parsing.TokenType.OPERATOR:
parsedText += `<font color="${colorScheme.OPERATOR}">${Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${scheme.OPERATOR}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case Parsing.TokenType.NUMBER: case Parsing.TokenType.NUMBER:
parsedText += `<font color="${colorScheme.NUMBER}">${Utils.escapeHTML(token.value)}</font>` parsedText += `<font color="${scheme.NUMBER}">${Utils.escapeHTML(token.value)}</font>`
break; break;
case Parsing.TokenType.STRING: case Parsing.TokenType.STRING:
parsedText += `<font color="${colorScheme.STRING}">${token.limitator}${Utils.escapeHTML(token.value)}${token.limitator}</font>` parsedText += `<font color="${scheme.STRING}">${token.limitator}${Utils.escapeHTML(token.value)}${token.limitator}</font>`
break; break;
case Parsing.TokenType.WHITESPACE: case Parsing.TokenType.WHITESPACE:
case Parsing.TokenType.PUNCT: case Parsing.TokenType.PUNCT:

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -15,8 +15,9 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.7 import QtQuick
import QtGraphicalEffects 1.0 import QtQuick.Window
import QtQuick.Controls.impl
/*! /*!
\qmltype Icon \qmltype Icon
@ -40,20 +41,16 @@ Item {
\qmlproperty string Icon::source \qmlproperty string Icon::source
Path of the icon image source. Path of the icon image source.
*/ */
property alias sourceSize: img.sourceSize.width property alias sourceSize: img.sourceS
Image { ColorImage {
id: img id: img
height: parent.height height: parent.height
width: parent.width width: parent.width
//smooth: true // visible: false
visible: false property int sourceS: width*Screen.devicePixelRatio
sourceSize.width: width*2 sourceSize.width: sourceS
sourceSize.height: width*2 sourceSize.height: sourceS
}
ColorOverlay {
anchors.fill: img
source: img
color: parent.color color: parent.color
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick 2.12 import QtQuick
import QtQuick.Controls 2.12 import QtQuick.Controls
import QtQml.Models 2.12 import QtQml.Models
/*! /*!
\qmltype ListSetting \qmltype ListSetting
@ -140,8 +140,8 @@ Column {
visible: control.dictionaryMode visible: control.dictionaryMode
height: parent.height height: parent.height
width: visible ? 50 : 0 width: visible ? 50 : 0
validator: RegExpValidator { validator: RegularExpressionValidator {
regExp: control.keyRegexp regularExpression: control.keyRegexp
} }
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter
@ -180,8 +180,8 @@ Column {
id: valueInput id: valueInput
height: parent.height height: parent.height
width: parent.width - x - deleteButton.width - 5 width: parent.width - x - deleteButton.width - 5
validator: RegExpValidator { validator: RegularExpressionValidator {
regExp: control.valueRegexp regularExpression: control.valueRegexp
} }
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignHCenter horizontalAlignment: TextInput.AlignHCenter

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import QtQuick.Controls 2.12 import QtQuick.Controls
import QtQuick 2.12 import QtQuick
import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup import eu.ad5001.LogarithmPlotter.Popup 1.0 as Popup
/*! /*!
@ -49,6 +49,12 @@ Item {
If true, the input is being parsed an double before being emitting the \a changed signal. If true, the input is being parsed an double before being emitting the \a changed signal.
*/ */
property bool isDouble: false property bool isDouble: false
/*!
\qmlproperty bool TextSetting::category
Type of special character to insert from the popup.
\sa InsertCharacter::category
*/
property alias category: insertPopup.category
/*! /*!
\qmlproperty double TextSetting::min \qmlproperty double TextSetting::min
Minimum value for numbers that can be entered into the input. Minimum value for numbers that can be entered into the input.
@ -94,15 +100,15 @@ Item {
id: labelItem id: labelItem
anchors.left: iconLabel.right anchors.left: iconLabel.right
anchors.leftMargin: icon == "" ? 0 : 5 anchors.leftMargin: icon == "" ? 0 : 5
height: parent.height
anchors.top: parent.top anchors.top: parent.top
height: parent.height
width: visible ? Math.max(85, implicitWidth) : 0
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
//color: sysPalette.windowText //color: sysPalette.windowText
text: visible ? qsTranslate("control", "%1: ").arg(control.label) : "" text: visible ? qsTranslate("control", "%1: ").arg(control.label) : ""
visible: control.label != "" visible: control.label != ""
} }
TextField { TextField {
id: input id: input
anchors.top: parent.top anchors.top: parent.top
@ -113,18 +119,23 @@ Item {
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter horizontalAlignment: control.label == "" ? TextInput.AlignLeft : TextInput.AlignHCenter
color: sysPalette.windowText color: sysPalette.windowText
validator: RegExpValidator { validator: RegularExpressionValidator {
regExp: control.isInt ? /-?[0-9]+/ : control.isDouble ? /-?[0-9]+(\.[0-9]+)?/ : /.+/ regularExpression: control.isInt ? /-?[0-9]+/ : control.isDouble ? /-?[0-9]+(\.[0-9]+)?/ : /.+/
} }
focus: true focus: true
text: control.defValue text: control.defValue
selectByMouse: true selectByMouse: true
onEditingFinished: { onEditingFinished: function() {
if(insertButton.focus || insertPopup.focus) return if(insertButton.focus || insertPopup.focus) return
var value = text let value = text
if(control.isInt) value = Math.max(control.min,parseInt(value).toString()=="NaN"?control.min:parseInt(value)) if(control.isInt) {
if(control.isDouble) value = Math.max(control.min,parseFloat(value).toString()=="NaN"?control.min:parseFloat(value)) let parsed = parseInt(value)
if(value != "" && value.toString() != defValue) { value = isNaN(parsed) ? control.min : Math.max(control.min,parsed)
} else if(control.isDouble) {
let parsed = parseFloat(value)
value = isNaN(parsed) ? control.min : Math.max(control.min,parsed)
}
if(value !== "" && value.toString() != defValue) {
control.changed(value) control.changed(value)
defValue = value.toString() defValue = value.toString()
} }

View file

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

View file

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

View file

@ -0,0 +1,160 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import eu.ad5001.LogarithmPlotter.Setting 1.0 as Setting
import "js/math/index.mjs" as MathLib
import "js/history/index.mjs" as HistoryLib
/*!
\qmltype ViewPositionChangeOverlay
\inqmlmodule eu.ad5001.LogarithmPlotter
\brief Overlay used allow the user to drag the canvas' position and change the zoom level.
Provides an overlay over the canvas that detects mouse movements and changes the canvas view position
accordingly by providing new signals.
\sa LogarithmPlotter, LogGraphCanvas, Settings
*/
Item {
id: viewChangeRoot
visible: true
clip: true
/*!
\qmlsignal ViewPositionChangeOverlay::positionChanged(int deltaX, int deltaY)
Emmited when the user dragged the canvas and the view should be refreshed.
The corresponding handler is \c onPositionChanged.
*/
signal positionChanged(int deltaX, int deltaY)
/*!
\qmlsignal ViewPositionChangeOverlay::beginPositionChange()
Emmited when the user starts dragging the canvas.
The corresponding handler is \c onBeginPositionChange.
*/
signal beginPositionChange()
/*!
\qmlsignal ViewPositionChangeOverlay::endPositionChange(int deltaX, int deltaY)
Emmited when the user stops dragging the canvas.
The corresponding handler is \c onEndPositionChange.
*/
signal endPositionChange(int deltaX, int deltaY)
/*!
\qmlproperty var ViewPositionChangeOverlay::canvas
LogGraphCanvas instance.
*/
property var canvas
/*!
\qmlproperty var ViewPositionChangeOverlay::settingsInstance
Settings instance.
*/
property var settingsInstance
/*!
\qmlproperty int ViewPositionChangeOverlay::prevX
The x coordinate (on the mousearea) at the last change of the canvas position.
*/
property int prevX
/*!
\qmlproperty int ViewPositionChangeOverlay::prevY
The y coordinate (on the mousearea) at the last change of the canvas position.
*/
property int prevY
/*!
\qmlproperty double ViewPositionChangeOverlay::baseZoomMultiplier
How much should the zoom be mutliplied/scrolled by for one scroll step (120° on the mouse wheel).
*/
property double baseZoomMultiplier: 0.1
MouseArea {
id: dragArea
anchors.fill: parent
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
property int positionChangeTimer: 0
function updatePosition(deltaX, deltaY) {
const unauthorized = [NaN, Infinity, -Infinity]
const xmin = (Modules.Canvas.px2x(Modules.Canvas.x2px(settingsInstance.xmin)-deltaX))
const ymax = settingsInstance.ymax + deltaY/canvas.yzoom
if(!unauthorized.includes(xmin))
settingsInstance.xmin = xmin
if(!unauthorized.includes(ymax))
settingsInstance.ymax = ymax.toFixed(4)
settingsInstance.changed()
parent.positionChanged(deltaX, deltaY)
}
onPressed: function(mouse) {
prevX = mouse.x
prevY = mouse.y
parent.beginPositionChange()
}
onPositionChanged: function(mouse) {
positionChangeTimer++
if(positionChangeTimer == 3) {
let deltaX = mouse.x - prevX
let deltaY = mouse.y - prevY
updatePosition(deltaX, deltaY)
prevX = mouse.x
prevY = mouse.y
positionChangeTimer = 0
}
}
onReleased: function(mouse) {
let deltaX = mouse.x - prevX
let deltaY = mouse.y - prevY
updatePosition(deltaX, deltaY)
parent.endPositionChange(deltaX, deltaY)
}
onWheel: function(wheel) {
// Scrolling
let scrollSteps = Math.round(wheel.angleDelta.y / 120)
let zoomMultiplier = Math.pow(1+baseZoomMultiplier, Math.abs(scrollSteps))
// Avoid floating-point rounding errors by removing the zoom *after*
let xZoomDelta = (settingsInstance.xzoom*zoomMultiplier - settingsInstance.xzoom)
let yZoomDelta = (settingsInstance.yzoom*zoomMultiplier - settingsInstance.yzoom)
if(scrollSteps < 0) { // Negative scroll
xZoomDelta *= -1
yZoomDelta *= -1
}
let newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(0)
let newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(0)
// Check if we need to have more precision
if(newXZoom < 10)
newXZoom = (settingsInstance.xzoom+xZoomDelta).toFixed(4)
if(newYZoom < 10)
newYZoom = (settingsInstance.yzoom+yZoomDelta).toFixed(4)
if(newXZoom > 0.5)
settingsInstance.xzoom = newXZoom
if(newYZoom > 0.5)
settingsInstance.yzoom = newYZoom
settingsInstance.changed()
}
}
}

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
version="1.1"
id="svg6"
sodipodi:docname="remove.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10" />
<sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="true"
inkscape:zoom="34.458333"
inkscape:cx="12"
inkscape:cy="10.505441"
inkscape:window-width="1920"
inkscape:window-height="1007"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6">
<inkscape:grid
type="xygrid"
id="grid822" />
</sodipodi:namedview>
<path
id="rect2"
style="fill-rule:evenodd;stroke-width:3.16228"
transform="rotate(135)"
d="M -1.4142136,-26.870058 H 1.4142136 V -7.0710678 H -1.4142136 Z"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#000000;fill-rule:evenodd;stroke-width:3.16228"
d="M 20,6 6,20 4,18 18,4 Z"
id="path4"
sodipodi:nodetypes="ccccc" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M14 0v10l2-1.518 2 1.518v-10h4v24h-17c-1.657 0-3-1.343-3-3v-18c0-1.657 1.343-3 3-3h9zm6 20h-14.505c-1.375 0-1.375 2 0 2h14.505v-2z"/></svg>

After

Width:  |  Height:  |  Size: 251 B

View file

@ -0,0 +1 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"><path d="M12 0l-2.138 2.63-3.068-1.441-.787 3.297-3.389.032.722 3.312-3.039 1.5 2.088 2.671-2.088 2.67 3.039 1.499-.722 3.312 3.389.033.787 3.296 3.068-1.441 2.138 2.63 2.139-2.63 3.068 1.441.786-3.296 3.39-.033-.722-3.312 3.038-1.499-2.087-2.67 2.087-2.671-3.038-1.5.722-3.312-3.39-.032-.786-3.297-3.068 1.441-2.139-2.63zm0 15.5c.69 0 1.25.56 1.25 1.25s-.56 1.25-1.25 1.25-1.25-.56-1.25-1.25.56-1.25 1.25-1.25zm1-1.038v-7.462h-2v7.462h2z"/></svg>

After

Width:  |  Height:  |  Size: 550 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M24 13.616v-3.232l-2.869-1.02c-.198-.687-.472-1.342-.811-1.955l1.308-2.751-2.285-2.285-2.751 1.307c-.613-.339-1.269-.613-1.955-.811l-1.021-2.869h-3.232l-1.021 2.869c-.686.198-1.342.471-1.955.811l-2.751-1.308-2.285 2.285 1.308 2.752c-.339.613-.614 1.268-.811 1.955l-2.869 1.02v3.232l2.869 1.02c.197.687.472 1.342.811 1.955l-1.308 2.751 2.285 2.286 2.751-1.308c.613.339 1.269.613 1.955.811l1.021 2.869h3.232l1.021-2.869c.687-.198 1.342-.472 1.955-.811l2.751 1.308 2.285-2.286-1.308-2.751c.339-.613.613-1.268.811-1.955l2.869-1.02zm-12 2.384c-2.209 0-4-1.791-4-4s1.791-4 4-4 4 1.791 4 4-1.791 4-4 4z"/></svg>

After

Width:  |  Height:  |  Size: 696 B

View file

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
version="1.1"
id="svg6"
sodipodi:docname="remove.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10" />
<sodipodi:namedview
id="namedview8"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="true"
inkscape:zoom="34.458333"
inkscape:cx="12"
inkscape:cy="10.505441"
inkscape:window-width="1920"
inkscape:window-height="1007"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6">
<inkscape:grid
type="xygrid"
id="grid822" />
</sodipodi:namedview>
<path
id="rect2"
style="fill-rule:evenodd;stroke-width:3.16228"
transform="rotate(135)"
d="M -1.4142136,-26.870058 H 1.4142136 V -7.0710678 H -1.4142136 Z"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#000000;fill-rule:evenodd;stroke-width:3.16228"
d="M 20,6 6,20 4,18 18,4 Z"
id="path4"
sodipodi:nodetypes="ccccc" />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 19 B

View file

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

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 19 B

View file

@ -1,44 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
sodipodi:docname="Function.svg" xmlns="http://www.w3.org/2000/svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)"> xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs833" /> id="defs833" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="31.678384"
inkscape:cx="15.268708"
inkscape:cy="12.238724"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid1403" />
</sodipodi:namedview>
<metadata <metadata
id="metadata10"> id="metadata10">
<rdf:RDF> <rdf:RDF>
@ -47,8 +20,7 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:date>2021-2023</dc:date>
<dc:date>2021</dc:date>
<dc:creator> <dc:creator>
<cc:Agent> <cc:Agent>
<dc:title>Ad5001</dc:title> <dc:title>Ad5001</dc:title>
@ -56,7 +28,7 @@
</dc:creator> </dc:creator>
<dc:rights> <dc:rights>
<cc:Agent> <cc:Agent>
<dc:title>(C) Ad5001 2021 - Licensed under CC4.0-BY-NC-SA</dc:title> <dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent> </cc:Agent>
</dc:rights> </dc:rights>
<cc:license <cc:license
@ -78,33 +50,46 @@
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<path <text
id="rect1415" xml:space="preserve"
style="fill:#000000;fill-rule:evenodd" style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
d="M 2,9 C 2,5 6,5 6,5 H 7 V 7 H 6 C 6,7 4,7 4,9 v 2 h 2 v 2 H 4 v 5 H 2 V 13 H 0 v -2 h 2 z" x="0.012050295"
sodipodi:nodetypes="ccccccccccccccccc" /> y="17.985596"
<path id="text1"><tspan
id="rect839" id="tspan1"
style="fill:#000000;fill-rule:evenodd;stroke-width:2" x="0.012050295"
d="m 12,3 v 2 c 0,0 -2,0 -2,7 0,7 2,7 2,7 v 2 C 8,21 8,12 8,12 8,12 8,3 12,3 Z" y="17.985596"
sodipodi:nodetypes="ccccccc" /> style="font-size:17.3333px">f</tspan></text>
<path <text
id="rect857" xml:space="preserve"
style="fill:#000000;fill-rule:evenodd;stroke-width:2" style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
d="m 12,10 h 2 l 6,8 h -2 z" x="10.913334"
sodipodi:nodetypes="ccccc" /> y="18.134649"
<path id="text1-3"><tspan
id="rect857-7" id="tspan1-6"
style="fill:#000000;fill-rule:evenodd;stroke-width:2" x="10.913334"
d="m 12,18 h 2 l 6,-8 h -2 z" y="18.134649"
sodipodi:nodetypes="ccccc" /> style="font-size:17.3333px">x</tspan></text>
<path <text
id="rect839-3" xml:space="preserve"
style="fill:#000000;fill-rule:evenodd;stroke-width:2" style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
d="m 20,3 v 2 c 0,0 2,0 2,7 0,7 -2,7 -2,7 v 2 c 4,0 4,-9 4,-9 0,0 0,-9 -4,-9 z" x="6.3066678"
sodipodi:nodetypes="ccccccc" /> y="17.646639"
id="text2"><tspan
id="tspan2"
x="6.3066678"
y="17.646639"
style="font-size:17.3333px">(</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
x="18.306667"
y="17.646639"
id="text2-7"><tspan
id="tspan2-5"
x="18.306667"
y="17.646639"
style="font-size:17.3333px">)</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,47 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
sodipodi:docname="Gain Bode.svg" xmlns="http://www.w3.org/2000/svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)"> xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs1469" /> id="defs1469" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="15.839192"
inkscape:cx="15.763196"
inkscape:cy="7.8365971"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058" />
</sodipodi:namedview>
<metadata <metadata
id="metadata1472"> id="metadata1472">
<rdf:RDF> <rdf:RDF>
@ -50,13 +20,40 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:date>2021-2023</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work> </cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<rect <rect
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
@ -67,15 +64,14 @@
y="17" /> y="17" />
<text <text
xml:space="preserve" xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
x="-0.12666447" x="-0.105"
y="12.134649" y="11.959"
id="text839"><tspan id="text839"><tspan
sodipodi:role="line"
id="tspan837" id="tspan837"
x="-0.12666447" x="-0.105"
y="12.134649" y="11.959"
style="font-size:17.3333px">ω</tspan></text> style="font-size:17px">ω</tspan></text>
<circle <circle
style="fill:#000000;fill-rule:evenodd;stroke-width:2" style="fill:#000000;fill-rule:evenodd;stroke-width:2"
id="path837" id="path837"
@ -86,7 +82,6 @@
id="rect837" id="rect837"
style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035" style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035"
transform="rotate(30)" transform="rotate(30)"
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z" d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z" />
sodipodi:nodetypes="ccccc" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,44 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
sodipodi:docname="Phase Bode.svg" xmlns="http://www.w3.org/2000/svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)"> xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs10" /> id="defs10" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="15.347905"
inkscape:cy="8.3727678"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid19" />
</sodipodi:namedview>
<metadata <metadata
id="metadata13"> id="metadata13">
<rdf:RDF> <rdf:RDF>
@ -47,13 +20,40 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:date>2021-2023</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work> </cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<rect <rect
style="fill:#000000;stroke-width:1.1547" style="fill:#000000;stroke-width:1.1547"
@ -65,8 +65,7 @@
<path <path
id="rect26-3" id="rect26-3"
style="fill:#000000;stroke-width:1.22474" style="fill:#000000;stroke-width:1.22474"
d="m 15,2 v 14 h 2 V 4 h 7 V 2 Z" d="m 15,2 v 14 h 2 V 4 h 7 V 2 Z" />
sodipodi:nodetypes="ccccccc" />
<circle <circle
style="fill:#000000;stroke-width:2.09999" style="fill:#000000;stroke-width:2.09999"
id="path45" id="path45"
@ -75,14 +74,13 @@
r="4" /> r="4" />
<text <text
xml:space="preserve" xml:space="preserve"
style="font-size:18px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none" style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
x="6.4359932" x="6.1339936"
y="11.702" y="11.163"
id="text49"><tspan id="text49"><tspan
sodipodi:role="line"
id="tspan47" id="tspan47"
x="6.4359932" x="6.1339936"
y="11.702" y="11.163"
style="font-size:18px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text> style="font-size:17px;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none">φ</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path style="opacity:1;vector-effect:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0" d="M9.98 18.12a3.5 3.5 0 0 1-3.064 3.855 3.5 3.5 0 0 1-3.887-3.022 3.5 3.5 0 0 1 2.982-3.919 3.5 3.5 0 0 1 3.95 2.94"/><text xml:space="preserve" style="font-style:normal;font-weight:400;font-size:17.3373px;line-height:1.25;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill-opacity:1;stroke:none;stroke-width:1.00023" x="12.067" y="13.923" transform="scale(.99447 1.00556)"><tspan x="12.067" y="13.923" style="font-size:17.3373px;stroke-width:1.00023">A</tspan></text></svg> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="24"
height="24"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1" />
<path
style="opacity:1;vector-effect:none;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
d="M9.98 18.12a3.5 3.5 0 0 1-3.064 3.855 3.5 3.5 0 0 1-3.887-3.022 3.5 3.5 0 0 1 2.982-3.919 3.5 3.5 0 0 1 3.95 2.94"
id="path1" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:400;font-size:17px;line-height:1.25;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill-opacity:1;stroke:none;stroke-width:1.00023"
x="11.964725"
y="13.701941"
transform="scale(0.99447036,1.0055604)"
id="text1"><tspan
x="11.964725"
y="13.701941"
style="font-size:17px;stroke-width:1.00023"
id="tspan1">A</tspan></text>
<metadata
id="metadata1">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:date>2021-2023</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
</svg>

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,45 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
sodipodi:docname="Sequence.svg" xmlns="http://www.w3.org/2000/svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)"> xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs10" /> id="defs10" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="-1.4929284"
inkscape:cy="9.7261905"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
showguides="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid19" />
</sodipodi:namedview>
<metadata <metadata
id="metadata13"> id="metadata13">
<rdf:RDF> <rdf:RDF>
@ -48,56 +20,62 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:date>2021-2023</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<text <text
xml:space="preserve" xml:space="preserve"
style="font-size:17.3333px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle" style="font-size:17.3333px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;-inkscape-font-specification:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="7.483983" x="7.483983"
y="16.134649" y="16.134649"
id="text908"><tspan id="text908"><tspan
sodipodi:role="line"
id="tspan906" id="tspan906"
x="7.483983" x="7.483983"
y="16.134649" y="16.134649"
style="font-size:17.3333px">u</tspan></text> style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">u</tspan></text>
<text <text
xml:space="preserve" xml:space="preserve"
style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:1.00003" style="font-size:17px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle;stroke-width:1.00003;-inkscape-font-specification:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal"
x="16.365566" x="16.365566"
y="18.663307" y="18.663307"
id="text912" id="text912"
transform="scale(1.0000324,0.9999676)"><tspan transform="scale(1.0000324,0.9999676)"><tspan
sodipodi:role="line"
id="tspan910" id="tspan910"
x="16.365566" x="16.365566"
y="18.663307" y="18.663307"
style="font-size:17px;stroke-width:1.00003">n</tspan></text> style="font-size:17px;stroke-width:1.00003;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">n</tspan></text>
<g <text
aria-label="(" xml:space="preserve"
id="text852" style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal"
style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" x="-0.69333196"
transform="matrix(1.0022756,0,0,1.2616817,-0.26079098,-9.0560687)"> y="17.646639"
<path id="text2"><tspan
d="M 2.712,9.86 C 1.536,11.528 0.48,12.956 0.48,15.8 c 0,2.844 1.056,4.272 2.232,5.94 L 3.408,21.26 C 2.328,19.664 1.632,18.368 1.632,15.8 c 0,-2.58 0.696,-3.864 1.776,-5.46 z" id="tspan2"
style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" x="-0.69333196"
id="path854" /> y="17.646639"
</g> style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">(</tspan></text>
<g <text
aria-label="(" xml:space="preserve"
id="text852-3" style="font-style:normal;font-weight:normal;font-size:17.3333px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none;-inkscape-font-specification:sans-serif;font-stretch:normal;font-variant:normal"
style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" x="18.806667"
transform="matrix(-1.0030304,0,0,1.2658306,24.414952,-9.1000412)"> y="17.646639"
<path id="text2-7"><tspan
d="M 2.712,9.86 C 1.536,11.528 0.48,12.956 0.48,15.8 c 0,2.844 1.056,4.272 2.232,5.94 L 3.408,21.26 C 2.328,19.664 1.632,18.368 1.632,15.8 c 0,-2.58 0.696,-3.864 1.776,-5.46 z" id="tspan2-5"
style="font-size:12px;stroke:#000000;stroke-width:0.4396;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" x="18.806667"
id="path854-6" /> y="17.646639"
</g> style="font-size:17.3333px;-inkscape-font-specification:sans-serif;font-family:sans-serif;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal">)</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -1,96 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px"
height="24.0px"
viewBox="0 0 24.0 24.0"
version="1.1"
id="SVGRoot"
sodipodi:docname="Somme gains Bode.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
<defs
id="defs1469" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="63.356768"
inkscape:cx="15.947723"
inkscape:cy="5.6917309"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058" />
</sodipodi:namedview>
<metadata
id="metadata1472">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="rect838"
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
d="M 2,2 H 8 V 3 H 4 L 7,6 4,9 h 4 v 1 H 2 V 9 L 5,6 2,3 Z"
sodipodi:nodetypes="ccccccccccccc" />
<rect
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:14.257;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
id="rect835"
width="14"
height="2"
x="0"
y="17" />
<circle
style="fill:#000000;fill-rule:evenodd;stroke-width:2"
id="path837"
cx="13"
cy="18"
r="4" />
<path
id="rect837"
style="fill:#000000;fill-rule:evenodd;stroke-width:2.10035"
transform="rotate(30)"
d="m 20.686533,-6.169873 2.232051,-0.1339746 -0.06218,13.8923049 -2.23205,0.1339746 z"
sodipodi:nodetypes="ccccc" />
<g
aria-label="G"
id="text846"
style="font-style:normal;font-weight:normal;font-size:12px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none">
<path
d="m 13,2 c 0,0 -3,0 -3,3 0,3 0,5 3,5 2,0 3,0 3,-2 V 6 h -3 v 1 h 2 v 1 c 0,1 -1,1 -2,1 -1,0 -2,0 -2,-4 0,-1 1,-2 2,-2 2,0 2,1 2,1 h 1 c 0,0 0,-2 -3,-2 z"
style="font-size:12px"
id="path848"
sodipodi:nodetypes="sssccccccsssccs" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px"
height="24.0px"
viewBox="0 0 24.0 24.0"
version="1.1"
id="SVGRoot"
sodipodi:docname="Somme gains Bode.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
<defs
id="defs1469" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="22.985246"
inkscape:cy="9.8906279"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058" />
</sodipodi:namedview>
<metadata
id="metadata1472">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="path1414"
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
d="m 19.979376,19.120606 a 3.5,3.5 0 0 1 -3.06333,3.854578 3.5,3.5 0 0 1 -3.886652,-3.022533 3.5,3.5 0 0 1 2.9814,-3.918293 3.5,3.5 0 0 1 3.9495,2.939936" />
<rect
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:16.166;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
id="rect835"
width="18"
height="2"
x="0"
y="18.5" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:16px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
x="-0.5703125"
y="11.875"
id="text839"><tspan
sodipodi:role="line"
id="tspan837"
x="-0.5703125"
y="11.875"
style="font-size:16px">ΣG</tspan></text>
<rect
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:19.0663;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0"
id="rect835-3"
width="25.038315"
height="2"
x="-10.17229"
y="23.748709"
ry="0"
transform="rotate(-60)" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px"
height="24.0px"
viewBox="0 0 24.0 24.0"
version="1.1"
id="SVGRoot"
sodipodi:docname="Somme phases Bode.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)">
<defs
id="defs10" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="15.347905"
inkscape:cy="8.3727678"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true">
<inkscape:grid
type="xygrid"
id="grid19" />
</sodipodi:namedview>
<metadata
id="metadata13">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#000000;stroke-width:1.41421"
id="rect26"
width="12"
height="2"
x="9"
y="18" />
<rect
style="fill:#000000;stroke-width:0.912867"
id="rect26-3"
width="5"
height="2"
x="19"
y="2" />
<rect
style="fill:#000000;stroke-width:1.5"
id="rect43"
width="2"
height="16"
x="19"
y="4" />
<circle
style="fill:#000000;stroke-width:2.09999"
id="path45"
cx="20"
cy="19"
r="3.5" />
<text
xml:space="preserve"
style="font-size:16px;line-height:1.25;font-family:sans-serif;text-align:center;letter-spacing:0px;text-anchor:middle"
x="8.7617188"
y="11.664062"
id="text49"><tspan
sodipodi:role="line"
id="tspan47"
x="8.7617188"
y="11.664062"
style="font-size:16px">Σφ</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,19 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
sodipodi:docname="Text.svg" sodipodi:docname="Text.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)"> inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs1469" /> id="defs1469" />
<sodipodi:namedview <sodipodi:namedview
@ -24,23 +24,38 @@
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="22.4" inkscape:zoom="22.4"
inkscape:cx="13.763421" inkscape:cx="13.772321"
inkscape:cy="16.975675" inkscape:cy="8.4598214"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
inkscape:document-rotation="0" inkscape:document-rotation="0"
showgrid="true" showgrid="true"
inkscape:window-width="1920" inkscape:window-width="1920"
inkscape:window-height="1011" inkscape:window-height="1010"
inkscape:window-x="0" inkscape:window-x="0"
inkscape:window-y="0" inkscape:window-y="0"
inkscape:window-maximized="1"> inkscape:window-maximized="1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid <inkscape:grid
type="xygrid" type="xygrid"
id="grid2039" /> id="grid2039"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
<inkscape:grid <inkscape:grid
type="xygrid" type="xygrid"
id="grid2058" /> id="grid2058"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
</sodipodi:namedview> </sodipodi:namedview>
<metadata <metadata
id="metadata1472"> id="metadata1472">
@ -50,8 +65,37 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:date>2021-2023</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work> </cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
@ -68,10 +112,16 @@
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.670999;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.670999;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 4,3 h 8 V 5 H 9 v 14 h 3 v 2 H 4 V 19 H 7 V 5 H 4 Z" d="m 4,3 h 8 V 5 H 9 v 14 h 3 v 2 H 4 V 19 H 7 V 5 H 4 Z"
sodipodi:nodetypes="ccccccccccccc" /> sodipodi:nodetypes="ccccccccccccc" />
<path <text
id="rect837" xml:space="preserve"
style="fill:#000000;fill-rule:evenodd;stroke-width:2" style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
d="m 16,5 h 2 v 3 h 2 v 2 h -2 v 5 c 0,2 2,2 2,2 v 2 c 0,0 -4,0 -4,-4 V 10 H 14 V 8 h 2 z" x="13.844"
sodipodi:nodetypes="ccccccccccccccc" /> y="17.387978"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
x="13.844"
y="17.387978"
style="font-size:17px">t</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -1,47 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.0px" width="24.0px"
height="24.0px" height="24.0px"
viewBox="0 0 24.0 24.0" viewBox="0 0 24.0 24.0"
version="1.1" version="1.1"
id="SVGRoot" id="SVGRoot"
sodipodi:docname="X Cursor.svg" xmlns="http://www.w3.org/2000/svg"
inkscape:version="1.0.1 (3bc2e813f5, 2021-09-07)"> xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs <defs
id="defs1469" /> id="defs1469" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.4"
inkscape:cx="19.545462"
inkscape:cy="13.163586"
inkscape:document-units="px"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="true"
inkscape:window-width="1829"
inkscape:window-height="916"
inkscape:window-x="19"
inkscape:window-y="31"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2039" />
<inkscape:grid
type="xygrid"
id="grid2058" />
</sodipodi:namedview>
<metadata <metadata
id="metadata1472"> id="metadata1472">
<rdf:RDF> <rdf:RDF>
@ -50,13 +20,40 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:date>2021-2023</dc:date>
<dc:creator>
<cc:Agent>
<dc:title>Ad5001</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>(C) Ad5001 2021-2023 - Licensed under CC4.0-BY-NC-SA</dc:title>
</cc:Agent>
</dc:rights>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc-sa/4.0/" />
</cc:Work> </cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"> id="layer1">
<g <g
aria-label="X" aria-label="X"
@ -68,18 +65,18 @@
id="rect12" id="rect12"
width="2" width="2"
height="24" height="24"
x="17" x="5"
y="0" y="0"
ry="2.14841e-13" /> ry="2.14841e-13" />
<path <text
id="rect835" xml:space="preserve"
style="fill:#000000;fill-rule:evenodd;stroke-width:2" style="font-style:normal;font-weight:normal;font-size:17px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
d="m 5,2 h 2 l 8,14 h -2 z" x="10.915"
sodipodi:nodetypes="ccccc" /> y="13.713"
<path id="text1"><tspan
id="rect835-6" id="tspan1"
style="fill:#000000;fill-rule:evenodd;stroke-width:2" x="10.915"
d="M 15,2 H 13 L 5,16 h 2 z" y="13.713"
sodipodi:nodetypes="ccccc" /> style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17px;font-family:sans-serif;-inkscape-font-specification:sans-serif">X</tspan></text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,16 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library // Loading modules in order
import * as Objects from "./module/objects.mjs"
.import "reference.js" as Reference import * as ExprParser from "./module/expreval.mjs"
.import "tokenizer.js" as TK import * as ObjsAutoload from "./objs/autoload.mjs"
.import "common.js" as Common import * as Latex from "./module/latex.mjs"
import * as History from "./module/history.mjs"
var Input = Common.InputExpression import * as CanvasAPI from "./module/canvas.mjs"
var TokenType = TK.TokenType import * as IOAPI from "./module/io.mjs"
var Token = TK.Token import * as PreferencesAPI from "./module/preferences.mjs"
var Tokenizer = TK.ExpressionTokenizer
var FUNCTIONS_LIST = Reference.FUNCTIONS_LIST
var CONSTANTS_LIST = Reference.CONSTANTS_LIST

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,13 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library import EditedProperty from "editproperty.mjs"
import Objects from "../module/objects.mjs"
.import "editproperty.js" as EP export default class ColorChanged extends EditedProperty {
.import "../objects.js" as Objects
class ColorChanged extends EP.EditedProperty {
// Action used everytime when an object's color is changed // Action used everytime when an object's color is changed
type(){return 'ColorChanged'} type(){return 'ColorChanged'}
@ -40,7 +37,7 @@ class ColorChanged extends EP.EditedProperty {
color(darkVer=false){return darkVer ? 'purple' : 'plum'} color(darkVer=false){return darkVer ? 'purple' : 'plum'}
getReadableString() { getReadableString() {
return qsTr("%1 %2's color changed from %3 to %4.") return qsTranslate("color", "%1 %2's color changed from %3 to %4.")
.arg(Objects.types[this.targetType].displayType()).arg(this.targetName) .arg(Objects.types[this.targetType].displayType()).arg(this.targetName)
.arg(this.previousValue).arg(this.newValue) .arg(this.previousValue).arg(this.newValue)
} }
@ -50,7 +47,7 @@ class ColorChanged extends EP.EditedProperty {
} }
getHTMLString() { getHTMLString() {
return qsTr("%1 %2's color changed from %3 to %4.") return qsTranslate("color", "%1 %2's color changed from %3 to %4.")
.arg(Objects.types[this.targetType].displayType()) .arg(Objects.types[this.targetType].displayType())
.arg('<b style="font-size: 15px;">&nbsp;' + this.targetName + "&nbsp;</b>") .arg('<b style="font-size: 15px;">&nbsp;' + this.targetName + "&nbsp;</b>")
.arg(this.formatColor(this.previousValue)).arg(this.formatColor(this.newValue)) .arg(this.formatColor(this.previousValue)).arg(this.formatColor(this.newValue))

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,16 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library import History from "../module/history.mjs"
import Latex from "../module/latex.mjs"
.import "../math/latex.js" as Latex export class Action {
var themeTextColor;
var imageDepth = 2;
var fontSize = 14;
class Action {
/** /**
* Type of the action. * Type of the action.
* *
@ -48,15 +42,11 @@ class Action {
/** /**
* Undoes the action. * Undoes the action.
*
* @returns {string}
*/ */
undo() {} undo() {}
/** /**
* Redoes the action. * Redoes the action.
*
* @returns {string}
*/ */
redo() {} redo() {}
@ -64,7 +54,7 @@ class Action {
* Export the action to a serializable format. * Export the action to a serializable format.
* NOTE: These arguments will be reinputed in the constructor in this order. * NOTE: These arguments will be reinputed in the constructor in this order.
* *
* @returns {string} * @returns {string[]}
*/ */
export() { export() {
return [this.targetName, this.targetType] return [this.targetName, this.targetType]
@ -86,26 +76,35 @@ class Action {
* @returns {string} * @returns {string}
*/ */
getIconRichText(type) { getIconRichText(type) {
return `<img source="../icons/objects/${type}.svg" style="color: ${themeTextColor};" width=18 height=18></img>` return `<img source="../icons/objects/${type}.svg" style="color: ${History.themeTextColor};" width=18 height=18></img>`
} }
/** /**
* Renders a LaTeX-formatted string to an image and wraps it in an HTML tag in a string. * 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. * @param {string} latexString - Source string of the latex.
* @returns {string} * @returns {Promise<string>}
*/ */
renderLatexAsHtml(latexString) { renderLatexAsHtml(latexString) {
if(!Latex.enabled) if(!Latex.enabled)
throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.") throw new Error("Cannot render an item as LaTeX when LaTeX is disabled.")
let latexInfo = Latex.Renderer.render(latexString, imageDepth*fontSize+4, themeTextColor).split(",") return new Promise(resolve => {
return `<img src="${latexInfo[0]}" width="${parseInt(latexInfo[1])/imageDepth}" height="${parseInt(latexInfo[2])/imageDepth}" style="vertical-align: middle"></img>` let imgDepth = History.imageDepth
Latex.requestAsyncRender(
latexString,
imgDepth * (History.fontSize + 2),
History.themeTextColor
).then((imgData) => {
const { source, width, height } = imgData
resolve(`<img src="${source}" width="${width/imgDepth}" height="${height/imgDepth}" style="vertical-align: middle"/>`)
})
})
} }
/** /**
* Returns a string with the HTML-formated description of the action. * Returns a string with the HTML-formatted description of the action.
* *
* @returns {string} * @returns {string|Promise<string>}
*/ */
getHTMLString() { getHTMLString() {
return this.getReadableString() return this.getReadableString()

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,12 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library import Objects from "../module/objects.mjs"
import { Action } from "common.mjs"
.import "../objects.js" as Objects export default class CreateNewObject extends Action {
.import "common.js" as C
class CreateNewObject extends C.Action {
// Action used for the creation of an object // Action used for the creation of an object
type(){return 'CreateNewObject'} type(){return 'CreateNewObject'}
@ -37,10 +35,6 @@ class CreateNewObject extends C.Action {
undo() { undo() {
Objects.deleteObject(this.targetName) 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() { redo() {
@ -53,11 +47,13 @@ class CreateNewObject extends C.Action {
} }
getReadableString() { 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() { getHTMLString() {
return qsTr("New %1 %2 created.") return qsTranslate("create", "New %1 %2 created.")
.arg(Objects.types[this.targetType].displayType()) .arg(Objects.types[this.targetType].displayType())
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>") .arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,14 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library import Objects from "../module/objects.mjs"
import CreateNewObject from "create.mjs"
.import "../objects.js" as Objects
.import "create.js" as Create
class DeleteObject extends Create.CreateNewObject { export default class DeleteObject extends CreateNewObject {
// Action used at the deletion of an object. Basicly the same thing as creating a new object, except Redo & Undo are reversed. /**
* Action used at the deletion of an object. Basically the same thing as creating a new object, except Redo & Undo are reversed.
*/
type(){return 'DeleteObject'} type(){return 'DeleteObject'}
icon(){return 'delete'} icon(){return 'delete'}
@ -39,11 +39,13 @@ class DeleteObject extends Create.CreateNewObject {
} }
getReadableString() { 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() { getHTMLString() {
return qsTr("%1 %2 deleted.") return qsTranslate("delete", "%1 %2 deleted.")
.arg(Objects.types[this.targetType].displayType()) .arg(Objects.types[this.targetType].displayType())
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>") .arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,15 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.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 export default class EditedProperty extends Action {
.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 // Action used everytime an object's property has been changed
type(){return 'EditedProperty'} type(){return 'EditedProperty'}
@ -34,6 +32,15 @@ class EditedProperty extends C.Action {
return darkVer ? 'darkslateblue' : 'cyan'; 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) { constructor(targetName = "", targetType = "Point", targetProperty = "visible", previousValue = false, newValue = true, valueIsExpressionNeedingImport = false) {
super(targetName, targetType) super(targetName, targetType)
this.targetProperty = targetProperty this.targetProperty = targetProperty
@ -42,10 +49,10 @@ class EditedProperty extends C.Action {
this.newValue = newValue this.newValue = newValue
this.propertyType = Objects.types[targetType].properties()[targetProperty] this.propertyType = Objects.types[targetType].properties()[targetProperty]
if(valueIsExpressionNeedingImport) { if(valueIsExpressionNeedingImport) {
if(typeof this.propertyType == 'object' && this.propertyType.type == "Expression") { if(typeof this.propertyType == 'object' && this.propertyType.type === "Expression") {
this.previousValue = new MathLib.Expression(this.previousValue); this.previousValue = new MathLib.Expression(this.previousValue);
this.newValue = new MathLib.Expression(this.newValue); this.newValue = new MathLib.Expression(this.newValue);
} else if(this.propertyType == "Domain") { } else if(this.propertyType === "Domain") {
this.previousValue = MathLib.parseDomain(this.previousValue); this.previousValue = MathLib.parseDomain(this.previousValue);
this.newValue = MathLib.parseDomain(this.newValue); this.newValue = MathLib.parseDomain(this.newValue);
} else { } else {
@ -70,7 +77,7 @@ class EditedProperty extends C.Action {
export() { export() {
if(this.previousValue instanceof MathLib.Expression) { if(this.previousValue instanceof MathLib.Expression) {
return [this.targetName, this.targetType, this.targetProperty, this.previousValue.toEditableString(), this.newValue.toEditableString(), true] 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] return [this.targetName, this.targetType, this.targetProperty, this.previousValue.name, this.newValue.name, true]
} else { } else {
return [this.targetName, this.targetType, this.targetProperty, this.previousValue, this.newValue, false] return [this.targetName, this.targetType, this.targetProperty, this.previousValue, this.newValue, false]
@ -78,8 +85,9 @@ class EditedProperty extends C.Action {
} }
setReadableValues() { setReadableValues() {
this.prevString = ""; this.prevString = ""
this.nextString = ""; this.nextString = ""
this._renderPromises = []
if(this.propertyType instanceof Object) { if(this.propertyType instanceof Object) {
switch(this.propertyType.type) { switch(this.propertyType.type) {
case "Enum": case "Enum":
@ -110,24 +118,42 @@ class EditedProperty extends C.Action {
// HTML // HTML
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.prevString+'&nbsp;</tt>' this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.prevString+'&nbsp;</tt>'
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.nextString+'&nbsp;</tt>' this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+this.nextString+'&nbsp;</tt>'
if(Latex.enabled && typeof this.propertyType == 'object' && this.propertyType.type == "Expression") { if(Latex.enabled && typeof this.propertyType == 'object' && this.propertyType.type === "Expression") {
this.prevHTML= this.renderLatexAsHtml(this.previousValue.latexMarkup) // Store promises so that querying can wait for them to finish.
this.nextHTML= this.renderLatexAsHtml(this.newValue.latexMarkup) this._renderPromises = [
this.renderLatexAsHtml(this.previousValue.latexMarkup).then(prev => this.prevHTML = prev),
this.renderLatexAsHtml(this.newValue.latexMarkup).then(next => this.nextHTML = prev)
]
} }
} }
getReadableString() { getReadableString() {
return qsTr('%1 of %2 %3 changed from "%4" to "%5".') return qsTranslate("editproperty", '%1 of %2 %3 changed from "%4" to "%5".')
.arg(this.targetPropertyReadable) .arg(this.targetPropertyReadable)
.arg(Objects.types[this.targetType].displayType()) .arg(Objects.types[this.targetType].displayType())
.arg(this.targetName).arg(this.prevString).arg(this.nextString) .arg(this.targetName).arg(this.prevString).arg(this.nextString)
} }
/**
*
* @return {Promise<string>|string}
*/
getHTMLString() { getHTMLString() {
return qsTr('%1 of %2 changed from %3 to %4.') return new Promise(resolve => {
.arg(this.targetPropertyReadable) const translation = qsTranslate("editproperty", '%1 of %2 changed from %3 to %4.')
.arg('<b style="font-size: 15px;">&nbsp;' + this.targetName + '&nbsp;</b>') .arg(this.targetPropertyReadable)
.arg(this.prevHTML) .arg('<b style="font-size: 15px;">&nbsp;' + this.targetName + '&nbsp;</b>')
.arg(this.nextHTML) // Check if we need to wait for LaTeX HTML to be rendered.
if(this.prevHTML !== undefined && this.nextHTML !== undefined)
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
else
Promise.all(this._renderPromises).then((rendered) => {
// Rendered are (potentially) two HTML strings which are defined during rendering
this.prevHTML = this.prevHTML ?? rendered[0]
this.nextHTML = this.prevHTML ?? rendered[1]
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
})
})
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -18,30 +18,27 @@
// This library helps containing actions to be undone or redone (in other words, editing history) // This library helps containing actions to be undone or redone (in other words, editing history)
// Each type of event is repertoried as an action that can be listed for everything that's undoable. // Each type of event is repertoried as an action that can be listed for everything that's undoable.
.pragma library
.import "history/common.js" as Common import { Action as A } from "./common.mjs"
.import "history/create.js" as Create import Create from "./create.mjs"
.import "history/delete.js" as Delete import Delete from "./delete.mjs"
.import "history/editproperty.js" as EP import EP from "./editproperty.mjs"
.import "history/position.js" as Pos import Pos from "./position.mjs"
.import "history/visibility.js" as V import V from "./visibility.mjs"
.import "history/name.js" as Name import Name from "./name.mjs"
.import "history/color.js" as Color import Color from "./color.mjs"
var history = null;
var Action = Common.Action export const Action = A
var CreateNewObject = Create.CreateNewObject export const CreateNewObject = Create
var DeleteObject = Delete.DeleteObject export const DeleteObject = Delete
var EditedProperty = EP.EditedProperty export const EditedProperty = EP
var EditedPosition = Pos.EditedPosition export const EditedPosition = Pos
var EditedVisibility = V.EditedVisibility export const EditedVisibility = V
var NameChanged = Name.NameChanged export const NameChanged = Name
var ColorChanged = Color.ColorChanged export const ColorChanged = Color
var Actions = { export const Actions = {
"Action": Action, "Action": Action,
"CreateNewObject": CreateNewObject, "CreateNewObject": CreateNewObject,
"DeleteObject": DeleteObject, "DeleteObject": DeleteObject,

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,19 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma 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 { export default class NameChanged extends EditedProperty {
// Action used everytime an object's property has been changed // Action used everytime an object's property has been changed
type(){return 'NameChanged'} type(){return 'NameChanged'}
icon(){return 'name'} icon(){return 'name'}
color(darkVer=false){return darkVer ? 'darkorange' : 'orange'} color(darkVer=false){return darkVer ? 'darkorange' : 'orange'}
constructor(targetName = "", targetType = "Point", newName = "") { constructor(targetName = "", targetType = "Point", newName = "") {
@ -45,20 +42,16 @@ class NameChanged extends EP.EditedProperty {
redo() { redo() {
Objects.renameObject(this.previousValue, this.newValue) 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() { getReadableString() {
return qsTr('%1 %2 renamed to %3.') return qsTranslate("name", '%1 %2 renamed to %3.')
.arg(Objects.types[this.targetType].displayType()) .arg(Objects.types[this.targetType].displayType())
.arg(this.targetName).arg(this.newValue) .arg(this.targetName).arg(this.newValue)
} }
getHTMLString() { getHTMLString() {
return qsTr('%1 %2 renamed to %3.') return qsTranslate("name", '%1 %2 renamed to %3.')
.arg(Objects.types[this.targetType].displayType()) .arg(Objects.types[this.targetType].displayType())
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>").arg('<b>'+this.newValue+'</b>') .arg('<b style="font-size: 15px;">' + this.targetName + "</b>").arg('<b>'+this.newValue+'</b>')
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,16 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library import Objects from "../module/objects.mjs"
import Latex from "../module/latex.mjs"
import * as MathLib from "../math/index.mjs"
import { escapeHTML } from "../utils.mjs"
import { Action } from "common.mjs"
import { DrawableObject } from "../objs/common.mjs"
.import "../objects.js" as Objects export default class EditedPosition extends Action {
.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...) // Action used for objects that have a X and Y expression properties (points, texts...)
type(){return 'EditedPosition'} type(){return 'EditedPosition'}
@ -63,13 +61,18 @@ class EditedPosition extends C.Action {
setReadableValues() { setReadableValues() {
this.prevString = `(${this.previousXValue.toString()},${this.previousYValue.toString()})` this.prevString = `(${this.previousXValue.toString()},${this.previousYValue.toString()})`
this.nextString = `(${this.newXValue.toString()},${this.newYValue.toString()})` this.nextString = `(${this.newXValue.toString()},${this.newYValue.toString()})`
this._renderPromises = []
// Render as LaTeX // Render as LaTeX
if(Latex.enabled) { if(Latex.enabled) {
this.prevHTML = this.renderLatexAsHtml(`\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`) const prevMarkup = `\\left(${this.previousXValue.latexMarkup},${this.previousYValue.latexMarkup}\\right)`
this.nextHTML = this.renderLatexAsHtml(`\\left(${this.newXValue.latexMarkup},${this.newYValue.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 { } else {
this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+Utils.escapeHTML(this.prevString)+'&nbsp;</tt>' this.prevHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+escapeHTML(this.prevString)+'&nbsp;</tt>'
this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+Utils.escapeHTML(this.nextString)+'&nbsp;</tt>' this.nextHTML = '<tt style="background: rgba(128,128,128,0.1);">&nbsp;'+escapeHTML(this.nextString)+'&nbsp;</tt>'
} }
} }
@ -81,15 +84,26 @@ class EditedPosition extends C.Action {
} }
getReadableString() { getReadableString() {
return qsTr('Position of %1 %2 set from "%3" to "%4".') return qsTranslate("position", 'Position of %1 %2 set from "%3" to "%4".')
.arg(Objects.types[this.targetType].displayType()) .arg(Objects.types[this.targetType].displayType())
.arg(this.targetName).arg(this.prevString).arg(this.nextString) .arg(this.targetName).arg(this.prevString).arg(this.nextString)
} }
getHTMLString() { getHTMLString() {
return qsTr('Position of %1 set from %2 to %3.') return new Promise(resolve => {
.arg('<b style="font-size: 15px;">&nbsp;' + this.targetName + '&nbsp;</b>') const translation = qsTranslate("position", 'Position of %1 set from %2 to %3.')
.arg(this.prevHTML) .arg('<b style="font-size: 15px;">&nbsp;' + this.targetName + '&nbsp;</b>')
.arg(this.nextHTML) // Check if we need to wait for LaTeX HTML to be rendered.
if(this.prevHTML !== undefined && this.nextHTML !== undefined)
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
else
Promise.all(this._renderPromises).then((rendered) => {
// Rendered are (potentially) two HTML strings which are defined during rendering
this.prevHTML = this.prevHTML ?? rendered[0]
this.nextHTML = this.nextHTML ?? rendered[1]
resolve(translation.arg(this.prevHTML).arg(this.nextHTML))
})
})
} }
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,13 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library import EditedProperty from "editproperty.mjs"
import Objects from "../module/objects.mjs"
.import "editproperty.js" as EP
.import "../objects.js" as Objects
class EditedVisibility extends EP.EditedProperty { export default class EditedVisibility extends EditedProperty {
// Action used when an object's shown or hidden. // Action used when an object's shown or hidden.
type(){return 'EditedVisibility'} type(){return 'EditedVisibility'}
@ -43,13 +41,13 @@ class EditedVisibility extends EP.EditedProperty {
} }
getReadableString() { 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(Objects.types[this.targetType].displayType())
.arg(this.targetName) .arg(this.targetName)
} }
getHTMLString() { 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(Objects.types[this.targetType].displayType())
.arg('<b style="font-size: 15px;">' + this.targetName + "</b>") .arg('<b style="font-size: 15px;">' + this.targetName + "</b>")
} }

View file

@ -0,0 +1,540 @@
/**
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
* http://www.undefined.ch/mparser/index.html
*
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
* https://silentmatt.com/javascript-expression-evaluator/
*
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
*
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
* but don't feel like you have to let me know or ask permission.
*/
import {
Instruction,
IOP3, IOP2, IOP1,
INUMBER, IARRAY,
IVAR, IVARNAME,
IEXPR, IEXPREVAL,
IMEMBER, IFUNCALL,
IENDSTATEMENT,
unaryInstruction, binaryInstruction, ternaryInstruction
} from "./instruction.mjs"
/**
* Simplifies the given instructions
* @param {Instruction[]} tokens
* @param {Record.<string, function(any): any>} unaryOps
* @param {Record.<string, function(any, any): any>} binaryOps
* @param {Record.<string, function(any, any, any): any>} ternaryOps
* @param {Record.<string, any>} values
* @return {Instruction[]}
*/
function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
const nstack = []
const newexpression = []
let n1, n2, n3
let f
for(let i = 0; i < tokens.length; i++) {
let item = tokens[i]
const type = item.type
if(type === INUMBER || type === IVARNAME) {
if(Array.isArray(item.value)) {
nstack.push.apply(nstack, simplify(item.value.map(function(x) {
return new Instruction(INUMBER, x)
}).concat(new Instruction(IARRAY, item.value.length)), unaryOps, binaryOps, ternaryOps, values))
} else {
nstack.push(item)
}
} else if(type === IVAR && values.hasOwnProperty(item.value)) {
item = new Instruction(INUMBER, values[item.value])
nstack.push(item)
} else if(type === IOP2 && nstack.length > 1) {
n2 = nstack.pop()
n1 = nstack.pop()
f = binaryOps[item.value]
item = new Instruction(INUMBER, f(n1.value, n2.value))
nstack.push(item)
} else if(type === IOP3 && nstack.length > 2) {
n3 = nstack.pop()
n2 = nstack.pop()
n1 = nstack.pop()
if(item.value === "?") {
nstack.push(n1.value ? n2.value : n3.value)
} else {
f = ternaryOps[item.value]
item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value))
nstack.push(item)
}
} else if(type === IOP1 && nstack.length > 0) {
n1 = nstack.pop()
f = unaryOps[item.value]
item = new Instruction(INUMBER, f(n1.value))
nstack.push(item)
} else if(type === IEXPR) {
while(nstack.length > 0) {
newexpression.push(nstack.shift())
}
newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values)))
} else if(type === IMEMBER && nstack.length > 0) {
n1 = nstack.pop()
if(item.value in n1.value)
nstack.push(new Instruction(INUMBER, n1.value[item.value]))
else
throw new Error(qsTranslate("error", "Cannot find property %1 of object %2.").arg(item.value).arg(n1))
} else {
while(nstack.length > 0) {
newexpression.push(nstack.shift())
}
newexpression.push(item)
}
}
while(nstack.length > 0) {
newexpression.push(nstack.shift())
}
return newexpression
}
/**
* In the given instructions, replaces variable by expr.
* @param {Instruction[]} tokens
* @param {string} variable
* @param {number} expr
* @return {Instruction[]}
*/
function substitute(tokens, variable, expr) {
const newexpression = []
for(let i = 0; i < tokens.length; i++) {
let item = tokens[i]
const type = item.type
if(type === IVAR && item.value === variable) {
for(let j = 0; j < expr.tokens.length; j++) {
const expritem = expr.tokens[j]
let replitem
if(expritem.type === IOP1) {
replitem = unaryInstruction(expritem.value)
} else if(expritem.type === IOP2) {
replitem = binaryInstruction(expritem.value)
} else if(expritem.type === IOP3) {
replitem = ternaryInstruction(expritem.value)
} else {
replitem = new Instruction(expritem.type, expritem.value)
}
newexpression.push(replitem)
}
} else if(type === IEXPR) {
newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr)))
} else {
newexpression.push(item)
}
}
return newexpression
}
/**
* Evaluates the given instructions for a given Expression with given values.
* @param {Instruction[]} tokens
* @param {ExprEvalExpression} expr
* @param {Record.<string, number>} values
* @return {number}
*/
function evaluate(tokens, expr, values) {
const nstack = []
let n1, n2, n3
let f, args, argCount
if(isExpressionEvaluator(tokens)) {
return resolveExpression(tokens, values)
}
for(let i = 0; i < tokens.length; i++) {
const item = tokens[i]
const type = item.type
if(type === INUMBER || type === IVARNAME) {
nstack.push(item.value)
} else if(type === IOP2) {
n2 = nstack.pop()
n1 = nstack.pop()
if(item.value === "and") {
nstack.push(n1 ? !!evaluate(n2, expr, values) : false)
} else if(item.value === "or") {
nstack.push(n1 ? true : !!evaluate(n2, expr, values))
} else if(item.value === "=") {
f = expr.binaryOps[item.value]
nstack.push(f(n1, evaluate(n2, expr, values), values))
} else {
f = expr.binaryOps[item.value]
nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values)))
}
} else if(type === IOP3) {
n3 = nstack.pop()
n2 = nstack.pop()
n1 = nstack.pop()
if(item.value === "?") {
nstack.push(evaluate(n1 ? n2 : n3, expr, values))
} else {
f = expr.ternaryOps[item.value]
nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values), resolveExpression(n3, values)))
}
} else if(type === IVAR) {
// Check for variable value
if(/^__proto__|prototype|constructor$/.test(item.value)) {
throw new Error("WARNING: Prototype access detected and denied. If you downloaded this file from the internet, this file might be a virus.")
} else if(item.value in expr.functions) {
nstack.push(expr.functions[item.value])
} else if(item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) {
nstack.push(expr.unaryOps[item.value])
} else {
const v = values[item.value]
if(v !== undefined) {
nstack.push(v)
} else {
throw new Error(qsTranslate("error", "Undefined variable %1.").arg(item.value))
}
}
} else if(type === IOP1) {
n1 = nstack.pop()
f = expr.unaryOps[item.value]
nstack.push(f(resolveExpression(n1, values)))
} else if(type === IFUNCALL) {
argCount = item.value
args = []
while(argCount-- > 0) {
args.unshift(resolveExpression(nstack.pop(), values))
}
f = nstack.pop()
if(f.apply && f.call) {
nstack.push(f.apply(undefined, args))
} else if(f.execute) {
// Objects & expressions execution
if(args.length >= 1)
nstack.push(f.execute.apply(f, args))
else
throw new Error(qsTranslate("error", "In order to be executed, object %1 must have at least one argument.").arg(f))
} else {
throw new Error(qsTranslate("error", "%1 cannot be executed.").arg(f))
}
} else if(type === IEXPR) {
nstack.push(createExpressionEvaluator(item, expr))
} else if(type === IEXPREVAL) {
nstack.push(item)
} else if(type === IMEMBER) {
n1 = nstack.pop()
if(item.value in n1)
if(n1[item.value].execute && n1[item.value].cached)
nstack.push(n1[item.value].execute())
else
nstack.push(n1[item.value])
else
throw new Error(qsTranslate("error", "Cannot find property %1 of object %2.").arg(item.value).arg(n1))
} else if(type === IENDSTATEMENT) {
nstack.pop()
} else if(type === IARRAY) {
argCount = item.value
args = []
while(argCount-- > 0) {
args.unshift(nstack.pop())
}
nstack.push(args)
} else {
throw new Error(qsTranslate("error", "Invalid expression."))
}
}
if(nstack.length > 1) {
throw new Error(qsTranslate("error", "Invalid expression (parity)."))
}
// Explicitly return zero to avoid test issues caused by -0
return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values)
}
function createExpressionEvaluator(token, expr) {
if(isExpressionEvaluator(token)) return token
return {
type: IEXPREVAL,
value: function(scope) {
return evaluate(token.value, expr, scope)
}
}
}
function isExpressionEvaluator(n) {
return n && n.type === IEXPREVAL
}
function resolveExpression(n, values) {
return isExpressionEvaluator(n) ? n.value(values) : n
}
/**
* Converts the given instructions to a string
* If toJS is active, can be evaluated with eval, otherwise it can be reparsed by the parser.
* @param {Instruction[]} tokens
* @param {boolean} toJS
* @return {string}
*/
function expressionToString(tokens, toJS) {
let nstack = []
let n1, n2, n3
let f, args, argCount
for(let i = 0; i < tokens.length; i++) {
const item = tokens[i]
const type = item.type
if(type === INUMBER) {
if(typeof item.value === "number" && item.value < 0) {
nstack.push("(" + item.value + ")")
} else if(Array.isArray(item.value)) {
nstack.push("[" + item.value.map(escapeValue).join(", ") + "]")
} else {
nstack.push(escapeValue(item.value))
}
} else if(type === IOP2) {
n2 = nstack.pop()
n1 = nstack.pop()
f = item.value
if(toJS) {
if(f === "^") {
nstack.push("Math.pow(" + n1 + ", " + n2 + ")")
} else if(f === "and") {
nstack.push("(!!" + n1 + " && !!" + n2 + ")")
} else if(f === "or") {
nstack.push("(!!" + n1 + " || !!" + n2 + ")")
} else if(f === "||") {
nstack.push("(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((" + n1 + "),(" + n2 + ")))")
} else if(f === "==") {
nstack.push("(" + n1 + " === " + n2 + ")")
} else if(f === "!=") {
nstack.push("(" + n1 + " !== " + n2 + ")")
} else if(f === "[") {
nstack.push(n1 + "[(" + n2 + ") | 0]")
} else {
nstack.push("(" + n1 + " " + f + " " + n2 + ")")
}
} else {
if(f === "[") {
nstack.push(n1 + "[" + n2 + "]")
} else {
nstack.push("(" + n1 + " " + f + " " + n2 + ")")
}
}
} else if(type === IOP3) {
n3 = nstack.pop()
n2 = nstack.pop()
n1 = nstack.pop()
f = item.value
if(f === "?") {
nstack.push("(" + n1 + " ? " + n2 + " : " + n3 + ")")
} else {
throw new Error(qsTranslate("error", "Invalid expression."))
}
} else if(type === IVAR || type === IVARNAME) {
nstack.push(item.value)
} else if(type === IOP1) {
n1 = nstack.pop()
f = item.value
if(f === "-" || f === "+") {
nstack.push("(" + f + n1 + ")")
} else if(toJS) {
if(f === "not") {
nstack.push("(" + "!" + n1 + ")")
} else if(f === "!") {
nstack.push("fac(" + n1 + ")")
} else {
nstack.push(f + "(" + n1 + ")")
}
} else if(f === "!") {
nstack.push("(" + n1 + "!)")
} else {
nstack.push("(" + f + " " + n1 + ")")
}
} else if(type === IFUNCALL) {
argCount = item.value
args = []
while(argCount-- > 0) {
args.unshift(nstack.pop())
}
f = nstack.pop()
nstack.push(f + "(" + args.join(", ") + ")")
} else if(type === IMEMBER) {
n1 = nstack.pop()
nstack.push(n1 + "." + item.value)
} else if(type === IARRAY) {
argCount = item.value
args = []
while(argCount-- > 0) {
args.unshift(nstack.pop())
}
nstack.push("[" + args.join(", ") + "]")
} else if(type === IEXPR) {
nstack.push("(" + expressionToString(item.value, toJS) + ")")
} else if(type === IENDSTATEMENT) {
} else {
throw new Error(qsTranslate("error", "Invalid expression."))
}
}
if(nstack.length > 1) {
if(toJS) {
nstack = [nstack.join(",")]
} else {
nstack = [nstack.join(";")]
}
}
return String(nstack[0])
}
export function escapeValue(v) {
if(typeof v === "string") {
return JSON.stringify(v).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029")
}
return v
}
/**
* Pushes all symbols from tokens into the symbols array.
* @param {Instruction[]} tokens
* @param {string[]} symbols
* @param {{withMembers: (boolean|undefined)}}options
*/
function getSymbols(tokens, symbols, options) {
options = options || {}
const withMembers = !!options.withMembers
let prevVar = null
for(let i = 0; i < tokens.length; i++) {
const item = tokens[i]
if(item.type === IVAR || item.type === IVARNAME) {
if(!withMembers && !symbols.includes(item.value)) {
symbols.push(item.value)
} else if(prevVar !== null) {
if(!symbols.includes(prevVar)) {
symbols.push(prevVar)
}
prevVar = item.value
} else {
prevVar = item.value
}
} else if(item.type === IMEMBER && withMembers && prevVar !== null) {
prevVar += "." + item.value
} else if(item.type === IEXPR) {
getSymbols(item.value, symbols, options)
} else if(prevVar !== null) {
if(!symbols.includes(prevVar)) {
symbols.push(prevVar)
}
prevVar = null
}
}
if(prevVar !== null && !symbols.includes(prevVar)) {
symbols.push(prevVar)
}
}
export class ExprEvalExpression {
/**
* @param {Instruction[]} tokens
* @param {Parser} parser
*/
constructor(tokens, parser) {
this.tokens = tokens
this.parser = parser
this.unaryOps = parser.unaryOps
this.binaryOps = parser.binaryOps
this.ternaryOps = parser.ternaryOps
this.functions = parser.functions
}
/**
* Simplifies the expression.
* @param {Object<string, number|ExprEvalExpression>|undefined} values
* @returns {ExprEvalExpression}
*/
simplify(values) {
values = values || {}
return new ExprEvalExpression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser)
}
/**
* Creates a new expression where the variable is substituted by the given expression.
* @param {string} variable
* @param {string|ExprEvalExpression} expr
* @returns {ExprEvalExpression}
*/
substitute(variable, expr) {
if(!(expr instanceof ExprEvalExpression)) {
expr = this.parser.parse(String(expr))
}
return new ExprEvalExpression(substitute(this.tokens, variable, expr), this.parser)
}
/**
* Calculates the value of the expression by giving all variables and their corresponding values.
* @param {Object<string, number>} values
* @returns {number}
*/
evaluate(values) {
values = Object.assign({}, values, this.parser.consts)
return evaluate(this.tokens, this, values)
}
/**
* Returns a list of symbols (string of characters) in the expressions.
* Can be functions, constants, or variables.
* @returns {string[]}
*/
symbols(options) {
options = options || {}
const vars = []
getSymbols(this.tokens, vars, options)
return vars
}
toString() {
return expressionToString(this.tokens, false)
}
/**
* Returns the list of symbols (string of characters) which are not defined
* as constants or functions.
* @returns {string[]}
*/
variables(options) {
options = options || {}
const vars = []
getSymbols(this.tokens, vars, options)
const functions = this.functions
const consts = this.parser.consts
return vars.filter((name) => {
return !(name in functions) && !(name in consts)
})
}
/**
* Converts the expression to a JS function.
* @param {string} param - Parsed variables for the function.
* @param {Object.<string, (ExprEvalExpression|string)>} variables - Default variables to provide.
* @returns {function(...any)}
*/
toJSFunction(param, variables) {
const expr = this
const f = new Function(param, "with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return " + expressionToString(this.simplify(variables).tokens, true) + "; }") // eslint-disable-line no-new-func
return function() {
return f.apply(expr, arguments)
}
}
}

View file

@ -0,0 +1,82 @@
/**
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
* http://www.undefined.ch/mparser/index.html
*
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
* https://silentmatt.com/javascript-expression-evaluator/
*
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
*
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
* but don't feel like you have to let me know or ask permission.
*/
export const INUMBER = "INUMBER"
export const IOP1 = "IOP1"
export const IOP2 = "IOP2"
export const IOP3 = "IOP3"
export const IVAR = "IVAR"
export const IVARNAME = "IVARNAME"
export const IFUNCALL = "IFUNCALL"
export const IEXPR = "IEXPR"
export const IEXPREVAL = "IEXPREVAL"
export const IMEMBER = "IMEMBER"
export const IENDSTATEMENT = "IENDSTATEMENT"
export const IARRAY = "IARRAY"
export class Instruction {
/**
*
* @param {string} type
* @param {any} value
*/
constructor(type, value) {
this.type = type
this.value = (value !== undefined && value !== null) ? value : 0
}
toString() {
switch(this.type) {
case INUMBER:
case IOP1:
case IOP2:
case IOP3:
case IVAR:
case IVARNAME:
case IENDSTATEMENT:
return this.value
case IFUNCALL:
return "CALL " + this.value
case IARRAY:
return "ARRAY " + this.value
case IMEMBER:
return "." + this.value
default:
return "Invalid Instruction"
}
}
}
export function unaryInstruction(value) {
return new Instruction(IOP1, value)
}
export function binaryInstruction(value) {
return new Instruction(IOP2, value)
}
export function ternaryInstruction(value) {
return new Instruction(IOP3, value)
}

View file

@ -0,0 +1,172 @@
/**
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
* http://www.undefined.ch/mparser/index.html
*
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
* https://silentmatt.com/javascript-expression-evaluator/
*
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
*
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
* but don't feel like you have to let me know or ask permission.
*/
import * as Polyfill from "./polyfill.mjs"
import { ParserState } from "./parserstate.mjs"
import { TEOF, TokenStream } from "./tokens.mjs"
import { ExprEvalExpression } from "./expression.mjs"
const optionNameMap = {
"+": "add",
"-": "subtract",
"*": "multiply",
"/": "divide",
"%": "remainder",
"^": "power",
"!": "factorial",
"<": "comparison",
">": "comparison",
"<=": "comparison",
">=": "comparison",
"==": "comparison",
"!=": "comparison",
"||": "concatenate",
"and": "logical",
"or": "logical",
"not": "logical",
"?": "conditional",
":": "conditional",
//'=': 'assignment', // Disable assignment
"[": "array"
//'()=': 'fndef' // Diable function definition
}
export class Parser {
constructor(options) {
this.options = options || {}
this.unaryOps = {
sin: Math.sin,
cos: Math.cos,
tan: Math.tan,
asin: Math.asin,
acos: Math.acos,
atan: Math.atan,
sinh: Math.sinh || Polyfill.sinh,
cosh: Math.cosh || Polyfill.cosh,
tanh: Math.tanh || Polyfill.tanh,
asinh: Math.asinh || Polyfill.asinh,
acosh: Math.acosh || Polyfill.acosh,
atanh: Math.atanh || Polyfill.atanh,
sqrt: Math.sqrt,
cbrt: Math.cbrt || Polyfill.cbrt,
log: Math.log,
log2: Math.log2 || Polyfill.log2,
ln: Math.log,
lg: Math.log10 || Polyfill.log10,
log10: Math.log10 || Polyfill.log10,
expm1: Math.expm1 || Polyfill.expm1,
log1p: Math.log1p || Polyfill.log1p,
abs: Math.abs,
ceil: Math.ceil,
floor: Math.floor,
round: Math.round,
trunc: Math.trunc || Polyfill.trunc,
"-": Polyfill.neg,
"+": Number,
exp: Math.exp,
not: Polyfill.not,
length: Polyfill.stringOrArrayLength,
"!": Polyfill.factorial,
sign: Math.sign || Polyfill.sign
}
this.unaryOpsList = Object.keys(this.unaryOps)
this.binaryOps = {
"+": Polyfill.add,
"-": Polyfill.sub,
"*": Polyfill.mul,
"/": Polyfill.div,
"%": Polyfill.mod,
"^": Math.pow,
"||": Polyfill.concat,
"==": Polyfill.equal,
"!=": Polyfill.notEqual,
">": Polyfill.greaterThan,
"<": Polyfill.lessThan,
">=": Polyfill.greaterThanEqual,
"<=": Polyfill.lessThanEqual,
and: Polyfill.andOperator,
or: Polyfill.orOperator,
"in": Polyfill.inOperator,
"=": Polyfill.setVar,
"[": Polyfill.arrayIndex
}
this.ternaryOps = {
"?": Polyfill.condition
}
this.functions = {
random: Polyfill.random,
fac: Polyfill.factorial,
min: Polyfill.min,
max: Polyfill.max,
hypot: Math.hypot || Polyfill.hypot,
pyt: Math.hypot || Polyfill.hypot, // backward compat
pow: Math.pow,
atan2: Math.atan2,
"if": Polyfill.condition,
gamma: Polyfill.gamma,
"Γ": Polyfill.gamma,
roundTo: Polyfill.roundTo,
map: Polyfill.arrayMap,
fold: Polyfill.arrayFold,
filter: Polyfill.arrayFilter,
indexOf: Polyfill.stringOrArrayIndexOf,
join: Polyfill.arrayJoin
}
// These constants will automatically be replaced the MOMENT they are parsed.
// (Original consts from the parser)
this.builtinConsts = {}
// These consts will only be replaced when the expression is evaluated.
this.consts = {}
}
parse(expr) {
const instr = []
const parserState = new ParserState(
this,
new TokenStream(this, expr),
{ allowMemberAccess: this.options.allowMemberAccess }
)
parserState.parseExpression(instr)
parserState.expect(TEOF, QT_TRANSLATE_NOOP("error", "EOF"))
return new ExprEvalExpression(instr, this)
}
evaluate(expr, variables) {
return this.parse(expr).evaluate(variables)
}
isOperatorEnabled(op) {
const optionName = optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op
const operators = this.options.operators || {}
return !(optionName in operators) || !!operators[optionName]
}
}

View file

@ -0,0 +1,398 @@
/**
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
* http://www.undefined.ch/mparser/index.html
*
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
* https://silentmatt.com/javascript-expression-evaluator/
*
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
*
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
* but don't feel like you have to let me know or ask permission.
*/
import { TBRACKET, TCOMMA, TEOF, TNAME, TNUMBER, TOP, TPAREN, TSTRING } from "./tokens.mjs"
import {
Instruction,
IARRAY, IEXPR, IFUNCALL, IMEMBER,
INUMBER, IVAR,
ternaryInstruction, binaryInstruction, unaryInstruction
} from "./instruction.mjs"
const COMPARISON_OPERATORS = ["==", "!=", "<", "<=", ">=", ">", "in"]
const ADD_SUB_OPERATORS = ["+", "-", "||"]
const TERM_OPERATORS = ["*", "/", "%"]
export class ParserState {
/**
*
* @param {Parser} parser
* @param {TokenStream} tokenStream
* @param {{[operators]: Object.<string, boolean>, [allowMemberAccess]: boolean}} options
*/
constructor(parser, tokenStream, options) {
this.parser = parser
this.tokens = tokenStream
this.current = null
this.nextToken = null
this.next()
this.savedCurrent = null
this.savedNextToken = null
this.allowMemberAccess = options.allowMemberAccess !== false
}
/**
* Queries the next token for parsing.
* @return {Token}
*/
next() {
this.current = this.nextToken
this.nextToken = this.tokens.next()
return this.nextToken
}
/**
* Checks if a given Token matches a condition (called if function, one of if array, and exact match otherwise)
* @param {Token} token
* @param {Array|function(Token): boolean|string|number|boolean} [value]
* @return {boolean}
*/
tokenMatches(token, value) {
if(typeof value === "undefined") {
return true
} else if(Array.isArray(value)) {
return value.includes(token.value)
} else if(typeof value === "function") {
return value(token)
} else {
return token.value === value
}
}
/**
* Saves the current state (current and next token) to be restored later.
*/
save() {
this.savedCurrent = this.current
this.savedNextToken = this.nextToken
this.tokens.save()
}
/**
* Restores a previous state (current and next token) from last save.
*/
restore() {
this.tokens.restore()
this.current = this.savedCurrent
this.nextToken = this.savedNextToken
}
/**
* Checks if the next token matches the given type and value, and if so, consume the current token.
* Returns true if the check matches.
* @param {string} type
* @param {any} [value]
* @return {boolean}
*/
accept(type, value) {
if(this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) {
this.next()
return true
}
return false
}
/**
* Throws an error if the next token does not match the given type and value. Otherwise, consumes the current token.
* @param {string} type
* @param {any} [value]
*/
expect(type, value) {
if(!this.accept(type, value)) {
throw new Error(qsTranslate("error", "Parse error [position %1]: %2")
.arg(this.tokens.pos)
.arg(qsTranslate("error", "Expected %1").arg(value || type)))
}
}
/**
* Converts enough Tokens to form an expression atom (generally the next part of the expression) into an instruction
* and pushes it to the instruction list.
* Throws an error if an unexpected token gets parsed.
* @param {Instruction[]} instr
*/
parseAtom(instr) {
const prefixOperators = this.tokens.unaryOpsList
if(this.accept(TNAME) || this.accept(TOP, prefixOperators)) {
instr.push(new Instruction(IVAR, this.current.value))
} else if(this.accept(TNUMBER)) {
instr.push(new Instruction(INUMBER, this.current.value))
} else if(this.accept(TSTRING)) {
instr.push(new Instruction(INUMBER, this.current.value))
} else if(this.accept(TPAREN, "(")) {
this.parseExpression(instr)
this.expect(TPAREN, ")")
} else if(this.accept(TBRACKET, "[")) {
if(this.accept(TBRACKET, "]")) {
instr.push(new Instruction(IARRAY, 0))
} else {
const argCount = this.parseArrayList(instr)
instr.push(new Instruction(IARRAY, argCount))
}
} else {
throw new Error(qsTranslate("error", "Unexpected %1").arg(this.nextToken))
}
}
/**
* Consumes the next tokens to compile a general expression which should return a value, and compiles
* the instructions into the list.
* @param {Instruction[]} instr
*/
parseExpression(instr) {
const exprInstr = []
this.parseConditionalExpression(exprInstr)
instr.push(...exprInstr)
}
/**
* Parses an array indice, and return the number of arguments found at the end.
* @param {Instruction[]} instr
* @return {number}
*/
parseArrayList(instr) {
let argCount = 0
while(!this.accept(TBRACKET, "]")) {
this.parseExpression(instr)
++argCount
while(this.accept(TCOMMA)) {
this.parseExpression(instr)
++argCount
}
}
return argCount
}
/**
* Parses a tertiary statement (<condition> ? <value if true> : <value if false>) and pushes it into the instruction
* list.
* @param {Instruction[]} instr
*/
parseConditionalExpression(instr) {
this.parseOrExpression(instr)
while(this.accept(TOP, "?")) {
const trueBranch = []
const falseBranch = []
this.parseConditionalExpression(trueBranch)
this.expect(TOP, ":")
this.parseConditionalExpression(falseBranch)
instr.push(new Instruction(IEXPR, trueBranch))
instr.push(new Instruction(IEXPR, falseBranch))
instr.push(ternaryInstruction("?"))
}
}
/**
* Parses a binary or statement (<condition 1> or <condition 2>) and pushes it into the instruction list.
* @param {Instruction[]} instr
*/
parseOrExpression(instr) {
this.parseAndExpression(instr)
while(this.accept(TOP, "or")) {
const falseBranch = []
this.parseAndExpression(falseBranch)
instr.push(new Instruction(IEXPR, falseBranch))
instr.push(binaryInstruction("or"))
}
}
/**
* Parses a binary and statement (<condition 1> and <condition 2>) and pushes it into the instruction list.
* @param {Instruction[]} instr
*/
parseAndExpression(instr) {
this.parseComparison(instr)
while(this.accept(TOP, "and")) {
const trueBranch = []
this.parseComparison(trueBranch)
instr.push(new Instruction(IEXPR, trueBranch))
instr.push(binaryInstruction("and"))
}
}
/**
* Parses a binary equality statement (<condition 1> == <condition 2> and so on) and pushes it into the instruction list.
* @param {Instruction[]} instr
*/
parseComparison(instr) {
this.parseAddSub(instr)
while(this.accept(TOP, COMPARISON_OPERATORS)) {
const op = this.current
this.parseAddSub(instr)
instr.push(binaryInstruction(op.value))
}
}
/**
* Parses add, minus and concat operations and pushes them into the instruction list.
* @param {Instruction[]} instr
*/
parseAddSub(instr) {
this.parseTerm(instr)
while(this.accept(TOP, ADD_SUB_OPERATORS)) {
const op = this.current
this.parseTerm(instr)
instr.push(binaryInstruction(op.value))
}
}
/**
* Parses times, divide and modulo operations and pushes them into the instruction list.
* @param {Instruction[]} instr
*/
parseTerm(instr) {
this.parseFactor(instr)
while(this.accept(TOP, TERM_OPERATORS)) {
const op = this.current
this.parseFactor(instr)
instr.push(binaryInstruction(op.value))
}
}
/**
* Parses prefix operations (+, -, but also functions like sin or cos which don't need parentheses)
* @param {Instruction[]} instr
*/
parseFactor(instr) {
const prefixOperators = this.tokens.unaryOpsList
this.save()
if(this.accept(TOP, prefixOperators)) {
if(this.current.value !== "-" && this.current.value !== "+") {
if(this.nextToken.type === TPAREN && this.nextToken.value === "(") {
this.restore()
this.parseExponential(instr)
return
} else if(this.nextToken.type === TCOMMA || this.nextToken.type === TEOF || (this.nextToken.type === TPAREN && this.nextToken.value === ")")) {
this.restore()
this.parseAtom(instr)
return
}
}
const op = this.current
this.parseFactor(instr)
instr.push(unaryInstruction(op.value))
} else {
this.parseExponential(instr)
}
}
/**
*
* @param {Instruction[]} instr
*/
parseExponential(instr) {
this.parsePostfixExpression(instr)
while(this.accept(TOP, "^")) {
this.parseFactor(instr)
instr.push(binaryInstruction("^"))
}
}
/**
* Parses factorial '!' (after the expression to apply it to).
* @param {Instruction[]} instr
*/
parsePostfixExpression(instr) {
this.parseFunctionCall(instr)
while(this.accept(TOP, "!")) {
instr.push(unaryInstruction("!"))
}
}
/**
* Parse a function (name + parentheses + arguments).
* @param {Instruction[]} instr
*/
parseFunctionCall(instr) {
const prefixOperators = this.tokens.unaryOpsList
if(this.accept(TOP, prefixOperators)) {
const op = this.current
this.parseAtom(instr)
instr.push(unaryInstruction(op.value))
} else {
this.parseMemberExpression(instr)
while(this.accept(TPAREN, "(")) {
if(this.accept(TPAREN, ")")) {
instr.push(new Instruction(IFUNCALL, 0))
} else {
const argCount = this.parseArgumentList(instr)
instr.push(new Instruction(IFUNCALL, argCount))
}
}
}
}
/**
* Parses a list of arguments, return their quantity.
* @param {Instruction[]} instr
* @return {number}
*/
parseArgumentList(instr) {
let argCount = 0
while(!this.accept(TPAREN, ")")) {
this.parseExpression(instr)
++argCount
while(this.accept(TCOMMA)) {
this.parseExpression(instr)
++argCount
}
}
return argCount
}
parseMemberExpression(instr) {
this.parseAtom(instr)
while(this.accept(TOP, ".") || this.accept(TBRACKET, "[")) {
const op = this.current
if(op.value === ".") {
if(!this.allowMemberAccess) {
throw new Error(qsTranslate("error", "Unexpected \".\": member access is not permitted"))
}
this.expect(TNAME)
instr.push(new Instruction(IMEMBER, this.current.value))
} else if(op.value === "[") {
if(!this.tokens.isOperatorEnabled("[")) {
throw new Error(qsTranslate("error", "Unexpected \"[]\": arrays are disabled."))
}
this.parseExpression(instr)
this.expect(TBRACKET, "]")
instr.push(binaryInstruction("["))
} else {
throw new Error(qsTranslate("error", "Unexpected symbol: %1.").arg(op.value))
}
}
}
}

View file

@ -0,0 +1,371 @@
/**
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
* http://www.undefined.ch/mparser/index.html
*
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
* https://silentmatt.com/javascript-expression-evaluator/
*
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
*
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
* but don't feel like you have to let me know or ask permission.
*/
export function add(a, b) {
return Number(a) + Number(b)
}
export function sub(a, b) {
return a - b
}
export function mul(a, b) {
return a * b
}
export function div(a, b) {
return a / b
}
export function mod(a, b) {
return a % b
}
export function concat(a, b) {
if(Array.isArray(a) && Array.isArray(b)) {
return a.concat(b)
}
return "" + a + b
}
export function equal(a, b) {
return a === b
}
export function notEqual(a, b) {
return a !== b
}
export function greaterThan(a, b) {
return a > b
}
export function lessThan(a, b) {
return a < b
}
export function greaterThanEqual(a, b) {
return a >= b
}
export function lessThanEqual(a, b) {
return a <= b
}
export function andOperator(a, b) {
return Boolean(a && b)
}
export function orOperator(a, b) {
return Boolean(a || b)
}
export function inOperator(a, b) {
return b.includes(a)
}
export function sinh(a) {
return ((Math.exp(a) - Math.exp(-a)) / 2)
}
export function cosh(a) {
return ((Math.exp(a) + Math.exp(-a)) / 2)
}
export function tanh(a) {
if(a === Infinity) return 1
if(a === -Infinity) return -1
return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a))
}
export function asinh(a) {
if(a === -Infinity) return a
return Math.log(a + Math.sqrt((a * a) + 1))
}
export function acosh(a) {
return Math.log(a + Math.sqrt((a * a) - 1))
}
export function atanh(a) {
return (Math.log((1 + a) / (1 - a)) / 2)
}
export function log10(a) {
return Math.log(a) * Math.LOG10E
}
export function neg(a) {
return -a
}
export function not(a) {
return !a
}
export function trunc(a) {
return a < 0 ? Math.ceil(a) : Math.floor(a)
}
export function random(a) {
return Math.random() * (a || 1)
}
export function factorial(a) { // a!
return gamma(a + 1)
}
export function isInteger(value) {
return isFinite(value) && (value === Math.round(value))
}
const GAMMA_G = 4.7421875
const GAMMA_P = [
0.99999999999999709182,
57.156235665862923517, -59.597960355475491248,
14.136097974741747174, -0.49191381609762019978,
0.33994649984811888699e-4,
0.46523628927048575665e-4, -0.98374475304879564677e-4,
0.15808870322491248884e-3, -0.21026444172410488319e-3,
0.21743961811521264320e-3, -0.16431810653676389022e-3,
0.84418223983852743293e-4, -0.26190838401581408670e-4,
0.36899182659531622704e-5
]
// Gamma function from math.js
export function gamma(n) {
let t, x
if(isInteger(n)) {
if(n <= 0) {
return isFinite(n) ? Infinity : NaN
}
if(n > 171) {
return Infinity // Will overflow
}
let value = n - 2
let res = n - 1
while(value > 1) {
res *= value
value--
}
if(res === 0) {
res = 1 // 0! is per definition 1
}
return res
}
if(n < 0.5) {
return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n))
}
if(n >= 171.35) {
return Infinity // will overflow
}
if(n > 85.0) { // Extended Stirling Approx
const twoN = n * n
const threeN = twoN * n
const fourN = threeN * n
const fiveN = fourN * n
return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) *
(1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) -
(571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) +
(5246819 / (75246796800 * fiveN * n)))
}
--n
x = GAMMA_P[0]
for(let i = 1; i < GAMMA_P.length; ++i) {
x += GAMMA_P[i] / (n + i)
}
t = n + GAMMA_G + 0.5
return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x
}
export function stringOrArrayLength(s) {
if(Array.isArray(s)) {
return s.length
}
return String(s).length
}
export function hypot() {
let sum = 0
let larg = 0
for(let i = 0; i < arguments.length; i++) {
const arg = Math.abs(arguments[i])
let div
if(larg < arg) {
div = larg / arg
sum = (sum * div * div) + 1
larg = arg
} else if(arg > 0) {
div = arg / larg
sum += div * div
} else {
sum += arg
}
}
return larg === Infinity ? Infinity : larg * Math.sqrt(sum)
}
export function condition(cond, yep, nope) {
return cond ? yep : nope
}
/**
* Decimal adjustment of a number.
* From @escopecz.
*
* @param {number} value - The number.
* @param {Integer} exp - The exponent (the 10 logarithm of the adjustment base).
* @return {number} - The adjusted value.
*/
export function roundTo(value, exp) {
// If the exp is undefined or zero...
if(typeof exp === "undefined" || +exp === 0) {
return Math.round(value)
}
value = +value
exp = -(+exp)
// If the value is not a number or the exp is not an integer...
if(isNaN(value) || !(typeof exp === "number" && exp % 1 === 0)) {
return NaN
}
// Shift
value = value.toString().split("e")
value = Math.round(+(value[0] + "e" + (value[1] ? (+value[1] - exp) : -exp)))
// Shift back
value = value.toString().split("e")
return +(value[0] + "e" + (value[1] ? (+value[1] + exp) : exp))
}
export function setVar(name, value, variables) {
if(variables) variables[name] = value
return value
}
export function arrayIndex(array, index) {
return array[index | 0]
}
export function max(array) {
if(arguments.length === 1 && Array.isArray(array)) {
return Math.max.apply(Math, array)
} else if(arguments.length >= 1) {
return Math.max.apply(Math, arguments)
} else {
throw new EvalError(qsTranslate("error", "Function %1 must have at least one argument.").arg("max"))
}
}
export function min(array) {
if(arguments.length === 1 && Array.isArray(array)) {
return Math.min.apply(Math, array)
} else if(arguments.length >= 1) {
return Math.min.apply(Math, arguments)
} else {
throw new EvalError(qsTranslate("error", "Function %1 must have at least one argument.").arg("min"))
}
}
export function arrayMap(f, a) {
if(typeof f !== "function") {
throw new EvalError(qsTranslate("error", "First argument to map is not a function."))
}
if(!Array.isArray(a)) {
throw new EvalError(qsTranslate("error", "Second argument to map is not an array."))
}
return a.map(function(x, i) {
return f(x, i)
})
}
export function arrayFold(f, init, a) {
if(typeof f !== "function") {
throw new EvalError(qsTranslate("error", "First argument to fold is not a function."))
}
if(!Array.isArray(a)) {
throw new EvalError(qsTranslate("error", "Second argument to fold is not an array."))
}
return a.reduce(function(acc, x, i) {
return f(acc, x, i)
}, init)
}
export function arrayFilter(f, a) {
if(typeof f !== "function") {
throw new EvalError(qsTranslate("error", "First argument to filter is not a function."))
}
if(!Array.isArray(a)) {
throw new EvalError(qsTranslate("error", "Second argument to filter is not an array."))
}
return a.filter(function(x, i) {
return f(x, i)
})
}
export function stringOrArrayIndexOf(target, s) {
if(!(Array.isArray(s) || typeof s === "string")) {
throw new Error(qsTranslate("error", "Second argument to indexOf is not a string or array."))
}
return s.indexOf(target)
}
export function arrayJoin(sep, a) {
if(!Array.isArray(a)) {
throw new Error(qsTranslate("error", "Second argument to join is not an array."))
}
return a.join(sep)
}
export function sign(x) {
return ((x > 0) - (x < 0)) || +x
}
const ONE_THIRD = 1 / 3
export function cbrt(x) {
return x < 0 ? -Math.pow(-x, ONE_THIRD) : Math.pow(x, ONE_THIRD)
}
export function expm1(x) {
return Math.exp(x) - 1
}
export function log1p(x) {
return Math.log(1 + x)
}
export function log2(x) {
return Math.log(x) / Math.LN2
}

View file

@ -0,0 +1,575 @@
/**
* Based on ndef.parser, by Raphael Graf <r@undefined.ch>
* http://www.undefined.ch/mparser/index.html
*
* Ported to JavaScript and modified by Matthew Crumley <email@matthewcrumley.com>
* https://silentmatt.com/javascript-expression-evaluator/
*
* Ported to QMLJS with modifications done accordingly done by Ad5001 <mail@ad5001.eu> (https://ad5001.eu)
*
* Copyright (c) 2015 Matthew Crumley, 2021-2024 Ad5001
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
* to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
* but don't feel like you have to let me know or ask permission.
*/
export const TEOF = "TEOF"
export const TOP = "TOP"
export const TNUMBER = "TNUMBER"
export const TSTRING = "TSTRING"
export const TPAREN = "TPAREN"
export const TBRACKET = "TBRACKET"
export const TCOMMA = "TCOMMA"
export const TNAME = "TNAME"
// Additional variable characters.
export const ADDITIONAL_VARCHARS = [
"α", "β", "γ", "δ", "ε", "ζ", "η",
"π", "θ", "κ", "λ", "μ", "ξ", "ρ",
"ς", "σ", "τ", "φ", "χ", "ψ", "ω",
"Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ",
"Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ",
"ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
"ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
"⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
"₄", "₅", "₆", "₇", "₈", "₉", "₀",
"∞", "π"
]
export class Token {
/**
*
* @param {string} type - Type of the token (see above).
* @param {any} value - Value of the token.
* @param {number} index - Index in the string of the token.
*/
constructor(type, value, index) {
this.type = type
this.value = value
this.index = index
}
toString() {
return this.type + ": " + this.value
}
}
const unicodeCodePointPattern = /^[0-9a-f]{4}$/i
export class TokenStream {
/**
*
* @param {Parser} parser
* @param {string} expression
*/
constructor(parser, expression) {
this.pos = 0
this.current = null
this.unaryOps = parser.unaryOps
this.unaryOpsList = parser.unaryOpsList
this.binaryOps = parser.binaryOps
this.ternaryOps = parser.ternaryOps
this.builtinConsts = parser.builtinConsts
this.expression = expression
this.savedPosition = 0
this.savedCurrent = null
this.options = parser.options
this.parser = parser
}
/**
*
* @param {string} type - Type of the token (see above).
* @param {any} value - Value of the token.
* @param {number} [pos] - Index in the string of the token.
*/
newToken(type, value, pos) {
return new Token(type, value, pos != null ? pos : this.pos)
}
/**
* Saves the current position and token into the object.
*/
save() {
this.savedPosition = this.pos
this.savedCurrent = this.current
}
/**
* Restored the saved position and token into the current.
*/
restore() {
this.pos = this.savedPosition
this.current = this.savedCurrent
}
/**
* Consumes the character at the current position and advance it
* until it makes a valid token, and returns it.
* @returns {Token}
*/
next() {
if(this.pos >= this.expression.length) {
return this.newToken(TEOF, "EOF")
}
if(this.isWhitespace()) {
return this.next()
} else if(this.isRadixInteger() ||
this.isNumber() ||
this.isOperator() ||
this.isString() ||
this.isParen() ||
this.isBracket() ||
this.isComma() ||
this.isNamedOp() ||
this.isConst() ||
this.isName()) {
return this.current
} else {
this.parseError(qsTranslate("error", "Unknown character \"%1\".").arg(this.expression.charAt(this.pos)))
}
}
/**
* Checks if the character at the current position starts a string, and if so, consumes it as the current token
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isString() {
const startPos = this.pos
const quote = this.expression.charAt(startPos)
let r = false
if(quote === "'" || quote === "\"") {
let index = this.expression.indexOf(quote, startPos + 1)
while(index >= 0 && this.pos < this.expression.length) {
this.pos = index + 1
if(this.expression.charAt(index - 1) !== "\\") {
const rawString = this.expression.substring(startPos + 1, index)
this.current = this.newToken(TSTRING, this.unescape(rawString), startPos)
r = true
break
}
index = this.expression.indexOf(quote, index + 1)
}
}
return r
}
/**
* Checks if the character at the current pos is a parenthesis, and if so consumes it into current
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isParen() {
const c = this.expression.charAt(this.pos)
if(c === "(" || c === ")") {
this.current = this.newToken(TPAREN, c)
this.pos++
return true
}
return false
}
/**
* Checks if the character at the current pos is a bracket, and if so consumes it into current
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isBracket() {
const c = this.expression.charAt(this.pos)
if((c === "[" || c === "]") && this.isOperatorEnabled("[")) {
this.current = this.newToken(TBRACKET, c)
this.pos++
return true
}
return false
}
/**
* Checks if the character at the current pos is a comma, and if so consumes it into current
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isComma() {
const c = this.expression.charAt(this.pos)
if(c === ",") {
this.current = this.newToken(TCOMMA, ",")
this.pos++
return true
}
return false
}
/**
* Checks if the current character is an identifier and makes a const, and if so, consumes it as the current token
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isConst() {
const startPos = this.pos
let i = startPos
for(; i < this.expression.length; i++) {
const c = this.expression.charAt(i)
if(c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
if(i === this.pos || (c !== "_" && c !== "." && (c < "0" || c > "9"))) {
break
}
}
}
if(i > startPos) {
const str = this.expression.substring(startPos, i)
if(str in this.builtinConsts) {
this.current = this.newToken(TNUMBER, this.builtinConsts[str])
this.pos += str.length
return true
}
}
return false
}
/**
* Checks if the current character is an identifier and makes a function or an operator, and if so, consumes it as the current token
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isNamedOp() {
const startPos = this.pos
let i = startPos
for(; i < this.expression.length; i++) {
const c = this.expression.charAt(i)
if(c.toUpperCase() === c.toLowerCase()) {
if(i === this.pos || (c !== "_" && (c < "0" || c > "9"))) {
break
}
}
}
if(i > startPos) {
const str = this.expression.substring(startPos, i)
if(this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) {
this.current = this.newToken(TOP, str)
this.pos += str.length
return true
}
}
return false
}
/**
* Checks if the current character is an identifier and makes a variable, and if so, consumes it as the current token
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isName() {
const startPos = this.pos
let i = startPos
let hasLetter = false
for(; i < this.expression.length; i++) {
const c = this.expression.charAt(i)
if(c.toUpperCase() === c.toLowerCase() && !ADDITIONAL_VARCHARS.includes(c)) {
if(i === this.pos && (c === "$" || c === "_")) {
if(c === "_") {
hasLetter = true
}
} else if(i === this.pos || !hasLetter || (c !== "_" && (c < "0" || c > "9"))) {
break
}
} else {
hasLetter = true
}
}
if(hasLetter) {
const str = this.expression.substring(startPos, i)
this.current = this.newToken(TNAME, str)
this.pos += str.length
return true
}
return false
}
/**
* Checks if the character at the current position is a whitespace, and if so, consumes all consecutive whitespaces
* and returns true. Otherwise, returns false.
* @returns {boolean}
*
*/
isWhitespace() {
let r = false
let c = this.expression.charAt(this.pos)
while(c === " " || c === "\t" || c === "\n" || c === "\r") {
r = true
this.pos++
if(this.pos >= this.expression.length) {
break
}
c = this.expression.charAt(this.pos)
}
return r
}
/**
* Checks if the current character is a zero, and checks whether it forms a radix number, and if so, consumes it as the current token
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isRadixInteger() {
let pos = this.pos
if(pos >= this.expression.length - 2 || this.expression.charAt(pos) !== "0") {
return false
}
++pos
let radix
let validDigit
if(this.expression.charAt(pos) === "x") {
radix = 16
validDigit = /^[0-9a-f]$/i
pos++
} else if(this.expression.charAt(pos) === "b") {
radix = 2
validDigit = /^[01]$/i
pos++
} else {
return false
}
let valid = false
const startPos = pos
while(pos < this.expression.length) {
const c = this.expression.charAt(pos)
if(validDigit.test(c)) {
pos++
valid = true
} else {
break
}
}
if(valid) {
this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix))
this.pos = pos
}
return valid
}
/**
* Checks if the current character is a digit, and checks whether it forms a number, and if so, consumes it as the current token
* and returns true. Otherwise, returns false.
* @returns {boolean}
*/
isNumber() {
const startPos = this.pos
let valid = false
let pos = startPos
let resetPos = startPos
let foundDot = false
let foundDigits = false
let c
// Check for digit with dot.
while(pos < this.expression.length) {
c = this.expression.charAt(pos)
if((c >= "0" && c <= "9") || (!foundDot && c === ".")) {
if(c === ".") {
foundDot = true
} else {
foundDigits = true
}
pos++
valid = foundDigits
} else {
break
}
}
if(valid) {
resetPos = pos
}
// Check for e exponents.
if(c === "e" || c === "E") {
pos++
let acceptSign = true
let validExponent = false
while(pos < this.expression.length) {
c = this.expression.charAt(pos)
if(acceptSign && (c === "+" || c === "-")) {
acceptSign = false
} else if(c >= "0" && c <= "9") {
validExponent = true
acceptSign = false
} else {
break
}
pos++
}
if(!validExponent) {
pos = resetPos
}
}
// Use parseFloat now that we've identified the number.
if(valid) {
this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos)))
this.pos = pos
} else {
this.pos = resetPos
}
return valid
}
/**
* Checks if the current character is an operator, checks whether it's enabled and if so, consumes it as the current token
* and returns true. Otherwise, returns false.
* @return {boolean}
*/
isOperator() {
const startPos = this.pos
const c = this.expression.charAt(this.pos)
if(c === "+" || c === "-" || c === "*" || c === "/" || c === "%" || c === "^" || c === "?" || c === ":" || c === ".") {
this.current = this.newToken(TOP, c)
} else if(c === "∙" || c === "•") {
this.current = this.newToken(TOP, "*")
} else if(c === ">") {
if(this.expression.charAt(this.pos + 1) === "=") {
this.current = this.newToken(TOP, ">=")
this.pos++
} else {
this.current = this.newToken(TOP, ">")
}
} else if(c === "<") {
if(this.expression.charAt(this.pos + 1) === "=") {
this.current = this.newToken(TOP, "<=")
this.pos++
} else {
this.current = this.newToken(TOP, "<")
}
} else if(c === "|") {
if(this.expression.charAt(this.pos + 1) === "|") {
this.current = this.newToken(TOP, "||")
this.pos++
} else {
return false
}
} else if(c === "=") {
if(this.expression.charAt(this.pos + 1) === "=") {
this.current = this.newToken(TOP, "==")
this.pos++
} else {
this.current = this.newToken(TOP, c)
}
} else if(c === "!") {
if(this.expression.charAt(this.pos + 1) === "=") {
this.current = this.newToken(TOP, "!=")
this.pos++
} else {
this.current = this.newToken(TOP, c)
}
} else {
return false
}
this.pos++
if(this.isOperatorEnabled(this.current.value)) {
return true
} else {
this.pos = startPos
return false
}
}
/**
* Replaces a backslash and a character by its unescaped value.
* @param {string} v - string to un escape.
*/
unescape(v) {
let index = v.indexOf("\\")
if(index < 0) {
return v
}
let buffer = v.substring(0, index)
while(index >= 0) {
const c = v.charAt(++index)
switch(c) {
case "'":
buffer += "'"
break
case "\"":
buffer += "\""
break
case "\\":
buffer += "\\"
break
case "/":
buffer += "/"
break
case "b":
buffer += "\b"
break
case "f":
buffer += "\f"
break
case "n":
buffer += "\n"
break
case "r":
buffer += "\r"
break
case "t":
buffer += "\t"
break
case "u":
// interpret the following 4 characters as the hex of the unicode code point
const codePoint = v.substring(index + 1, index + 5)
if(!unicodeCodePointPattern.test(codePoint)) {
this.parseError(qsTranslate("error", "Illegal escape sequence: %1.").arg("\\u" + codePoint))
}
buffer += String.fromCharCode(parseInt(codePoint, 16))
index += 4
break
default:
throw this.parseError(qsTranslate("error", "Illegal escape sequence: %1.").arg("\\" + c))
}
++index
const backslash = v.indexOf("\\", index)
buffer += v.substring(index, backslash < 0 ? v.length : backslash)
index = backslash
}
return buffer
}
/**
* Shorthand for the parser's method to check if an operator is enabled.
* @param {string} op
* @return {boolean}
*/
isOperatorEnabled(op) {
return this.parser.isOperatorEnabled(op)
}
/**
* Throws a translated error.
* @param {string} msg
*/
parseError(msg) {
throw new Error(qsTranslate("error", "Parse error [position %1]: %2").arg(this.pos).arg(msg))
}
}

View file

@ -0,0 +1,74 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Type polyfills for IDEs.
// Never directly imported.
Modules = Modules || {}
/** @type {function(string, string): string} */
qsTranslate = qsTranslate || function(category, string) { throw new Error('qsTranslate not implemented.'); }
/** @type {function(string): string} */
qsTr = qsTr || function(string) { throw new Error('qsTr not implemented.'); }
/** @type {function(string, string): string} */
QT_TRANSLATE_NOOP = QT_TRANSLATE_NOOP || function(string, string) { throw new Error('QT_TRANSLATE_NOOP not implemented.'); }
/** @type {function(string): string} */
QT_TR_NOOP = QT_TR_NOOP || function(string) { throw new Error('QT_TR_NOOP not implemented.'); }
/** @type {function(string|boolean|int): string} */
String.prototype.arg = String.prototype.arg || function(parameter) { throw new Error('arg not implemented.'); }
const Qt = {
/**
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @returns {{x, width, y, height}}
*/
rect: function(x, y, width, height) {
return {x: x, y: y, width: width, height: height};
}
}
/** Typehints for Helper. */
const Helper = {
/** @type {function(string): boolean} */
getSettingBool: (setting) => true,
/** @type {function(string): int} */
getSettingInt: (setting) => 0,
/** @type {function(string): string} */
getSetting: (setting) => '',
/** @type {function(string, boolean)} */
setSettingBool: (setting, value) => {},
/** @type {function(string, int)} */
setSettingInt: (setting, value) => 0,
/** @type {function(string, string)} */
setSetting: (setting, value) => '',
/** @type {function(string, string)} */
write: (filename, data) => {},
/** @type {function(string): string} */
load: (filename) => '',
}
const Latex = {
/** @type {function(string, number, string): string} */
render: (latex_markup, font_size, color) => '',
/** @type {function(string, number, string): string} */
findPrerendered: (latex_markup, font_size, color) => '',
/** @type {function(): boolean} */
checkLatexInstallation: () => true,
}

View file

@ -1,56 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "../expr-eval.js" as ExprEval
.import "../utils.js" as Utils
.import "latex.js" as Latex
const DERIVATION_PRECISION = 0.1
var evalVariables = { // Variables not provided by expr-eval.js, needs to be provided manually
"pi": Math.PI,
"π": Math.PI,
"inf": Infinity,
"infinity": Infinity,
"Infinity": Infinity,
"∞": Infinity,
"e": Math.E,
"E": Math.E
}
var currentVars = {}
var currentObjectsByName = {} // Mirror of currentObjectsByName in objects.js
const parser = new ExprEval.Parser()
parser.consts = Object.assign({}, parser.consts, evalVariables)
// Function definition
parser.functions.integral = function(a, b, f, variable) {
// https://en.wikipedia.org/wiki/Simpson%27s_rule
// Simpler, faster than tokenizing the expression
f = parser.parse(f).toJSFunction(variable, currentVars)
return (b-a)/6*(f(a)+4*f((a+b)/2)+f(b))
}
parser.functions.derivative = function(f, variable, x) {
f = parser.parse(f).toJSFunction(variable, currentVars)
return (f(x+DERIVATION_PRECISION/2)-f(x-DERIVATION_PRECISION/2))/DERIVATION_PRECISION
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,21 +16,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library import { Expression, executeExpression } from "expression.mjs"
.import "expression.js" as Expr
/** /**
* Main abstract domain class * Main abstract domain class
* It doesn't represent any kind of domain and is meant to be extended. * It doesn't represent any kind of domain and is meant to be extended.
*/ */
class Domain { export class Domain {
constructor() {} constructor() {}
/** /**
* Checks whether x is included in the domain. * Checks whether x is included in the domain.
* @param {number} x - The x value. * @param {number} x - The x value.
* @return {bool} true if included, false otherwise. * @return {boolean} true if included, false otherwise.
*/ */
includes(x) { return false } includes(x) { return false }
@ -72,11 +70,13 @@ class Domain {
case "RP": case "RP":
case "R+": case "R+":
case "ℝ⁺": case "ℝ⁺":
case "+":
return Domain.RP return Domain.RP
break; break;
case "RM": case "RM":
case "R-": case "R-":
case "ℝ⁻": case "ℝ⁻":
case "-":
return Domain.RM return Domain.RM
break; break;
case "RPE": case "RPE":
@ -85,6 +85,8 @@ class Domain {
case "R*+": case "R*+":
case "*⁺": case "*⁺":
case "ℝ⁺*": case "ℝ⁺*":
case "*+":
case "+*":
return Domain.RPE return Domain.RPE
break; break;
case "RME": case "RME":
@ -93,16 +95,21 @@ class Domain {
case "R*-": case "R*-":
case "ℝ⁻*": case "ℝ⁻*":
case "*⁻": case "*⁻":
case "-*":
case "*-":
return Domain.RME return Domain.RME
break; break;
case "": case "":
case "N": case "N":
case "ZP": case "ZP":
case "Z+":
case "ℤ⁺": case "ℤ⁺":
case "+":
return Domain.N return Domain.N
break; break;
case "NLOG": case "NLOG":
case "ℕˡᵒᵍ": case "ℕˡᵒᵍ":
case "LOG":
return Domain.NLog return Domain.NLog
break; break;
case "NE": case "NE":
@ -111,12 +118,15 @@ class Domain {
case "N+": case "N+":
case "*": case "*":
case "ℕ⁺": case "ℕ⁺":
case "+":
case "ZPE": case "ZPE":
case "ZEP": case "ZEP":
case "Z+*": case "Z+*":
case "Z*+": case "Z*+":
case "ℤ⁺*": case "ℤ⁺*":
case "*⁺": case "*⁺":
case "+*":
case "*+":
return Domain.NE return Domain.NE
break; break;
case "Z": case "Z":
@ -126,6 +136,7 @@ class Domain {
case "ZM": case "ZM":
case "Z-": case "Z-":
case "ℤ⁻": case "ℤ⁻":
case "-":
return Domain.ZM return Domain.ZM
break; break;
case "ZME": case "ZME":
@ -134,6 +145,8 @@ class Domain {
case "Z*-": case "Z*-":
case "ℤ⁻*": case "ℤ⁻*":
case "*⁻": case "*⁻":
case "-*":
case "*-":
return Domain.ZME return Domain.ZME
break; break;
case "ZE": case "ZE":
@ -151,7 +164,7 @@ class Domain {
/** /**
* Represents an empty set. * Represents an empty set.
*/ */
class EmptySet extends Domain { export class EmptySet extends Domain {
constructor() { constructor() {
super() super()
this.displayName = "∅" this.displayName = "∅"
@ -172,12 +185,12 @@ class EmptySet extends Domain {
/** /**
* Domain classes for ranges (e.g ]0;3[, [1;2[ ...) * Domain classes for ranges (e.g ]0;3[, [1;2[ ...)
*/ */
class Range extends Domain { export class Range extends Domain {
constructor(begin, end, openBegin, openEnd) { constructor(begin, end, openBegin, openEnd) {
super() super()
if(typeof begin == 'number' || typeof begin == 'string') begin = new Expr.Expression(begin.toString()) if(typeof begin == 'number' || typeof begin == 'string') begin = new Expression(begin.toString())
this.begin = begin 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.end = end
this.openBegin = openBegin this.openBegin = openBegin
this.openEnd = openEnd this.openEnd = openEnd
@ -186,7 +199,8 @@ class Range extends Domain {
} }
includes(x) { 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.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) && return ((this.openBegin && x > this.begin.execute()) || (!this.openBegin && x >= this.begin.execute())) &&
((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute())) ((this.openEnd && x < this.end.execute()) || (!this.openEnd && x <= this.end.execute()))
} }
@ -214,9 +228,9 @@ class Range extends Domain {
} }
static import(frm) { static import(frm) {
var openBegin = frm.trim().charAt(0) == "]" let openBegin = frm.trim().charAt(0) === "]"
var openEnd = frm.trim().charAt(frm.length -1) == "[" let openEnd = frm.trim().charAt(frm.length -1) === "["
var [begin, end] = frm.substr(1, frm.length-2).split(";") let [begin, end] = frm.substr(1, frm.length-2).split(";")
return new Range(begin.trim(), end.trim(), openBegin, openEnd) return new Range(begin.trim(), end.trim(), openBegin, openEnd)
} }
} }
@ -224,17 +238,16 @@ class Range extends Domain {
/** /**
* Domain classes for special domains (N, Z, ...) * Domain classes for special domains (N, Z, ...)
*/ */
class SpecialDomain extends Domain { export class SpecialDomain extends Domain {
/** /**
* @constructs SpecialDomain * @constructs SpecialDomain
* @param {string} displayName * @param {string} displayName
* @param {function} isValid - function returning true when number is in domain false when it isn't. * @param {function} isValid - function returning true when number is in domain false when it isn't.
* @param {function} next - function provides the next positive value in the domain after the one given. * @param {function} next - function provides the next positive value in the domain after the one given.
* @param {function} previous - function provides the previous positive value in the domain before the one given. * @param {function} previous - function provides the previous positive value in the domain before the one given.
* @param {bool} moveSupported - Only true if next and previous functions are valid. * @param {boolean} moveSupported - Only true if next and previous functions are valid.
* @param items
*/ */
constructor(displayName, isValid, next = x => true, previous = x => true, constructor(displayName, isValid, next = () => true, previous = () => true,
moveSupported = true) { moveSupported = true) {
super() super()
this.displayName = displayName this.displayName = displayName
@ -245,17 +258,20 @@ class SpecialDomain extends Domain {
} }
includes(x) { 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) return this.isValid(x)
} }
next(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) return this.nextValue(x)
} }
previous(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) return this.prevValue(x)
} }
@ -285,14 +301,14 @@ class SpecialDomain extends Domain {
/** /**
* Domain classes for sets (e.g {0;3}, {0;1;2;pi} ...) * Domain classes for sets (e.g {0;3}, {0;1;2;pi} ...)
*/ */
class DomainSet extends SpecialDomain { export class DomainSet extends SpecialDomain {
constructor(values) { constructor(values) {
super('', x => true, x => x, true) super('', x => true, x => x, true)
var newVals = {} let newVals = {}
this.executedValues = [] this.executedValues = []
for(var value of values) { for(let value of values) {
var expr = new Expr.Expression(value.toString()) let expr = new Expression(value.toString())
var ex = expr.execute() let ex = expr.execute()
newVals[ex] = expr newVals[ex] = expr
this.executedValues.push(ex) this.executedValues.push(ex)
} }
@ -303,30 +319,33 @@ class DomainSet extends SpecialDomain {
} }
includes(x) { includes(x) {
if(typeof x == 'string') x = Expr.executeExpression(x) if(x instanceof Expression) x = x.execute()
for(var value of this.values) if(typeof x == 'string') x = executeExpression(x)
if(x == value.execute()) return true for(let value of this.values)
if(x === value.execute()) return true
return false return false
} }
next(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)
if(x < this.executedValues[0]) return this.executedValues[0] if(x < this.executedValues[0]) return this.executedValues[0]
for(var i = 1; i < this.values.length; i++) { for(let i = 1; i < this.values.length; i++) {
var prevValue = this.executedValues[i-1] let prevValue = this.executedValues[i-1]
var value = this.executedValues[i] let value = this.executedValues[i]
if(x >= prevValue && x < value) return value if(x >= prevValue && x < value) return value
} }
return null return null
} }
previous(x) { previous(x) {
if(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[this.executedValues.length-1]) if(x > this.executedValues[this.executedValues.length-1])
return this.executedValues[this.executedValues.length-1] return this.executedValues[this.executedValues.length-1]
for(var i = 1; i < this.values.length; i++) { for(let i = 1; i < this.values.length; i++) {
var prevValue = this.executedValues[i-1] let prevValue = this.executedValues[i-1]
var value = this.executedValues[i] let value = this.executedValues[i]
if(x > prevValue && x <= value) return prevValue if(x > prevValue && x <= value) return prevValue
} }
return null return null
@ -339,56 +358,56 @@ class DomainSet extends SpecialDomain {
union(domain) { union(domain) {
if(domain instanceof EmptySet) return this if(domain instanceof EmptySet) return this
if(domain instanceof DomainSet) { if(domain instanceof DomainSet) {
var newValues = [] let newValues = []
var values = this.values.concat(domain.values).filter(function(val){ let values = this.values.concat(domain.values).filter(function(val){
newValues.push(val.execute()) newValues.push(val.execute())
return newValues.indexOf(val.execute()) == newValues.length - 1 return newValues.indexOf(val.execute()) === newValues.length - 1
}) })
return new DomainSet(values) return new DomainSet(values)
} }
var notIncludedValues = [] let notIncludedValues = []
for(var value in this.values) { for(let i = 0; i < this.values.length; i++) {
var value = this.executedValues[i] let value = this.executedValues[i]
if(domain instanceof Range) { if(domain instanceof Range) {
if(domain.begin.execute() == value && domain.openBegin) { if(domain.begin.execute() === value && domain.openBegin) {
domain.openBegin = false domain.openBegin = false
} }
if(domain.end.execute() == value && domain.openEnd) { if(domain.end.execute() === value && domain.openEnd) {
domain.openEnd = false domain.openEnd = false
} }
} }
if(!domain.includes(value)) if(!domain.includes(value))
notIncludedValues.push(this.values[i].toEditableString()) notIncludedValues.push(this.values[i].toEditableString())
} }
if(notIncludedValues.length == 0) return domain if(notIncludedValues.length === 0) return domain
return new UnionDomain(domain, new DomainSet(notIncludedValues)) return new UnionDomain(domain, new DomainSet(notIncludedValues))
} }
intersection(domain) { intersection(domain) {
if(domain instanceof EmptySet) return domain if(domain instanceof EmptySet) return domain
if(domain instanceof DomainSet) { if(domain instanceof DomainSet) {
var domValues = domain.values.map(expr => expr.execute()) let domValues = domain.values.map(expr => expr.execute())
this.values = this.values.filter(function(val){ this.values = this.values.filter(function(val){
return domValues.indexOf(val.execute()) >= 0 return domValues.indexOf(val.execute()) >= 0
}) })
return this return this
} }
var includedValues = [] let includedValues = []
for(var i in this.values) { for(let i in this.values) {
var value = this.executedValues[i] let value = this.executedValues[i]
if(domain instanceof Range) { if(domain instanceof Range) {
if(domain.begin.execute() == value && !domain.openBegin) { if(domain.begin.execute() === value && !domain.openBegin) {
domain.openBegin = false domain.openBegin = false
} }
if(domain.end.execute() == value && !domain.openEnd) { if(domain.end.execute() === value && !domain.openEnd) {
domain.openEnd = false domain.openEnd = false
} }
} }
if(domain.includes(value)) if(domain.includes(value))
includedValues.push(this.values[i].toEditableString()) includedValues.push(this.values[i].toEditableString())
} }
if(includedValues.length == 0) return new EmptySet() if(includedValues.length === 0) return new EmptySet()
if(includedValues.length == this.values.length) return this if(includedValues.length === this.values.length) return this
return new IntersectionDomain(domain, new DomainSet(includedValues)) return new IntersectionDomain(domain, new DomainSet(includedValues))
} }
@ -400,7 +419,7 @@ class DomainSet extends SpecialDomain {
/** /**
* Domain representing the union between two domains. * Domain representing the union between two domains.
*/ */
class UnionDomain extends Domain { export class UnionDomain extends Domain {
constructor(dom1, dom2) { constructor(dom1, dom2) {
super() super()
this.dom1 = dom1 this.dom1 = dom1
@ -435,10 +454,10 @@ class UnionDomain extends Domain {
} }
static import(frm) { static import(frm) {
var domains = frm.trim().split("") let domains = frm.trim().split("")
if(domains.length == 1) domains = frm.trim().split("U") // Fallback if(domains.length === 1) domains = frm.trim().split("U") // Fallback
var dom1 = parseDomain(domains.pop()) let dom2 = parseDomain(domains.pop())
var dom2 = parseDomain(domains.join('')) let dom1 = parseDomain(domains.join(''))
return dom1.union(dom2) return dom1.union(dom2)
} }
} }
@ -446,7 +465,7 @@ class UnionDomain extends Domain {
/** /**
* Domain representing the intersection between two domains. * Domain representing the intersection between two domains.
*/ */
class IntersectionDomain extends Domain { export class IntersectionDomain extends Domain {
constructor(dom1, dom2) { constructor(dom1, dom2) {
super() super()
this.dom1 = dom1 this.dom1 = dom1
@ -481,9 +500,9 @@ class IntersectionDomain extends Domain {
} }
static import(frm) { static import(frm) {
var domains = frm.trim().split("∩") let domains = frm.trim().split("∩")
var dom1 = parseDomain(domains.pop()) let dom1 = parseDomain(domains.pop())
var dom2 = parseDomain(domains.join('∩')) let dom2 = parseDomain(domains.join('∩'))
return dom1.intersection(dom2) return dom1.intersection(dom2)
} }
} }
@ -491,7 +510,7 @@ class IntersectionDomain extends Domain {
/** /**
* Domain representing the minus between two domains. * Domain representing the minus between two domains.
*/ */
class MinusDomain extends Domain { export class MinusDomain extends Domain {
constructor(dom1, dom2) { constructor(dom1, dom2) {
super() super()
this.dom1 = dom1 this.dom1 = dom1
@ -509,10 +528,10 @@ class MinusDomain extends Domain {
} }
static import(frm) { static import(frm) {
var domains = frm.trim().split("") let domains = frm.trim().split("")
if(domains.length == 1) domains = frm.trim().split("\\") // Fallback if(domains.length === 1) domains = frm.trim().split("\\") // Fallback
var dom1 = parseDomain(domains.shift()) let dom1 = parseDomain(domains.shift())
var dom2 = parseDomain(domains.join('')) let dom2 = parseDomain(domains.join(''))
return new MinusDomain(dom1, dom2) return new MinusDomain(dom1, dom2)
} }
} }
@ -536,53 +555,53 @@ Domain.RPE.latexMarkup = "\\mathbb{R}^{+*}"
Domain.RME = new Range(-Infinity,0,true,true) Domain.RME = new Range(-Infinity,0,true,true)
Domain.RME.displayName = "ℝ⁻*" Domain.RME.displayName = "ℝ⁻*"
Domain.RME.latexMarkup = "\\mathbb{R}^{+*}" Domain.RME.latexMarkup = "\\mathbb{R}^{+*}"
Domain.N = new SpecialDomain('', x => x%1==0 && x >= 0, Domain.N = new SpecialDomain('', x => x%1===0 && x >= 0,
x => Math.max(Math.floor(x)+1, 0), x => Math.max(Math.floor(x)+1, 0),
x => Math.max(Math.ceil(x)-1, 0)) x => Math.max(Math.ceil(x)-1, 0))
Domain.N.latexMarkup = "\\mathbb{N}" Domain.N.latexMarkup = "\\mathbb{N}"
Domain.NE = new SpecialDomain('*', x => x%1==0 && x > 0, Domain.NE = new SpecialDomain('*', x => x%1===0 && x > 0,
x => Math.max(Math.floor(x)+1, 1), x => Math.max(Math.floor(x)+1, 1),
x => Math.max(Math.ceil(x)-1, 1)) x => Math.max(Math.ceil(x)-1, 1))
Domain.NE.latexMarkup = "\\mathbb{N}^{*}" Domain.NE.latexMarkup = "\\mathbb{N}^{*}"
Domain.Z = new SpecialDomain('', x => x%1==0, x => Math.floor(x)+1, x => Math.ceil(x)-1) Domain.Z = new SpecialDomain('', x => x%1===0, x => Math.floor(x)+1, x => Math.ceil(x)-1)
Domain.Z.latexMarkup = "\\mathbb{Z}" Domain.Z.latexMarkup = "\\mathbb{Z}"
Domain.ZE = new SpecialDomain('*', x => x%1==0 && x != 0, Domain.ZE = new SpecialDomain('*', x => x%1===0 && x !== 0,
x => Math.floor(x)+1 == 0 ? Math.floor(x)+2 : Math.floor(x)+1, x => Math.floor(x)+1 === 0 ? Math.floor(x)+2 : Math.floor(x)+1,
x => Math.ceil(x)-1 == 0 ? Math.ceil(x)-2 : Math.ceil(x)-1) x => Math.ceil(x)-1 === 0 ? Math.ceil(x)-2 : Math.ceil(x)-1)
Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}" Domain.ZE.latexMarkup = "\\mathbb{Z}^{*}"
Domain.ZM = new SpecialDomain('ℤ⁻', x => x%1==0 && x <= 0, Domain.ZM = new SpecialDomain('ℤ⁻', x => x%1===0 && x <= 0,
x => Math.min(Math.floor(x)+1, 0), x => Math.min(Math.floor(x)+1, 0),
x => Math.min(Math.ceil(x)-1, 0)) x => Math.min(Math.ceil(x)-1, 0))
Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}" Domain.ZM.latexMarkup = "\\mathbb{Z}^{-}"
Domain.ZME = new SpecialDomain('ℤ⁻*', x => x%1==0 && x < 0, Domain.ZME = new SpecialDomain('ℤ⁻*', x => x%1===0 && x < 0,
x => Math.min(Math.floor(x)+1, -1), x => Math.min(Math.floor(x)+1, -1),
x => Math.min(Math.ceil(x)-1, -1)) x => Math.min(Math.ceil(x)-1, -1))
Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}" Domain.ZME.latexMarkup = "\\mathbb{Z}^{-*}"
Domain.NLog = new SpecialDomain('ℕˡᵒᵍ', Domain.NLog = new SpecialDomain('ℕˡᵒᵍ',
x => x/Math.pow(10, x.toString().length-1) % 1 == 0 && x > 0, x => x/Math.pow(10, x.toString().length-1) % 1 === 0 && x > 0,
function(x) { function(x) {
var x10pow = Math.pow(10, x.toString().length-1) let x10pow = Math.pow(10, x.toString().length-1)
return Math.max(1, (Math.floor(x/x10pow)+1)*x10pow) return Math.max(1, (Math.floor(x/x10pow)+1)*x10pow)
}, },
function(x) { function(x) {
var x10pow = Math.pow(10, x.toString().length-1) let x10pow = Math.pow(10, x.toString().length-1)
return Math.max(1, (Math.ceil(x/x10pow)-1)*x10pow) return Math.max(1, (Math.ceil(x/x10pow)-1)*x10pow)
}) })
Domain.NLog.latexMarkup = "\\mathbb{N}^{log}" Domain.NLog.latexMarkup = "\\mathbb{N}^{log}"
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}) * e.g (N [-1;0[) (Z \ {0;3})
* @param {string} domain - string of the domain to be parsed. * @param {string} domain - string of the domain to be parsed.
* @returns {Domain} Parsed domain. * @returns {Domain} Parsed domain.
*/ */
function parseDomain(domain) { export function parseDomain(domain) {
if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain) if(!domain.includes(')') && !domain.includes('(')) return parseDomainSimple(domain)
var domStr let domStr
while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) { while((domStr = /\(([^)(]+)\)/.exec(domain)) !== null) {
var dom = parseDomainSimple(domStr[1].trim()); let dom = parseDomainSimple(domStr[1].trim());
domain = domain.replace(domStr[0], 'D' + refedDomains.length) domain = domain.replace(domStr[0], 'D' + refedDomains.length)
refedDomains.push(dom) refedDomains.push(dom)
} }
@ -590,20 +609,20 @@ function parseDomain(domain) {
} }
/** /**
* Parses a domain, without parenthesises. * Parses a domain, without parentheses.
* e.g N [-1;0[, Z \ {0;3}, N+*... * e.g N [-1;0[, Z \ {0;3}, N+*...
* @param {string} domain - string of the domain to be parsed. * @param {string} domain - string of the domain to be parsed.
* @returns {Domain} Parsed domain. * @returns {Domain} Parsed domain.
*/ */
function parseDomainSimple(domain) { export function parseDomainSimple(domain) {
domain = domain.trim() domain = domain.trim()
if(domain.includes("U") || domain.includes("")) return UnionDomain.import(domain) if(domain.includes("U") || domain.includes("")) return UnionDomain.import(domain)
if(domain.includes("∩")) return IntersectionDomain.import(domain) if(domain.includes("∩")) return IntersectionDomain.import(domain)
if(domain.includes("") || domain.includes("\\")) return MinusDomain.import(domain) if(domain.includes("") || domain.includes("\\")) return MinusDomain.import(domain)
if(domain.charAt(0) == "{" && domain.charAt(domain.length -1) == "}") return DomainSet.import(domain) if(domain.charAt(0) === "{" && domain.charAt(domain.length -1) === "}") return DomainSet.import(domain)
if(domain.includes("]") || domain.includes("[")) return Range.import(domain) if(domain.includes("]") || domain.includes("[")) return Range.import(domain)
if(["R", "", "N", "", "Z", ""].some(str => domain.toUpperCase().includes(str))) if(["R", "", "N", "", "Z", ""].some(str => domain.toUpperCase().includes(str)))
return Domain.import(domain) return Domain.import(domain)
if(domain[0] == 'D') return refedDomains[parseInt(domain.substr(1))] if(domain[0] === 'D') return refedDomains[parseInt(domain.substr(1))]
return new EmptySet() return new EmptySet()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,71 +16,72 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library
.import "common.js" as C import * as Utils from "../utils.mjs"
.import "latex.js" as Latex import Latex from "../module/latex.mjs"
.import "../utils.js" as Utils import ExprParser from "../module/expreval.mjs"
import Objects from "../module/objects.mjs"
/** /**
* Represents any kind of x-based or non variable based expression. * Represents any kind of x-based or non variable based expression.
*/ */
class Expression { export class Expression {
constructor(expr) { constructor(expr) {
this.expr = expr if(typeof expr === "string") {
this.calc = C.parser.parse(expr).simplify() this.expr = Utils.exponentsToExpression(expr)
this.calc = ExprParser.parse(this.expr).simplify()
} else {
// Passed an expression here directly.
this.calc = expr.simplify()
this.expr = expr.toString()
}
this.cached = this.isConstant() this.cached = this.isConstant()
this.cachedValue = this.cached && this.allRequirementsFullfilled() ? this.calc.evaluate(C.currentObjectsByName) : null this.cachedValue = null
if(this.cached && this.allRequirementsFullfilled())
this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
this.latexMarkup = Latex.expression(this.calc.tokens) this.latexMarkup = Latex.expression(this.calc.tokens)
} }
variables() {
return this.calc.variables()
}
isConstant() { isConstant() {
let vars = this.calc.variables() let vars = this.calc.variables()
return !vars.includes("x") && !vars.includes("n") return !vars.includes("x") && !vars.includes("n")
} }
requiredObjects() { requiredObjects() {
return this.calc.variables().filter(objName => objName != "x" && objName != "n") return this.calc.variables().filter(objName => objName !== "x" && objName !== "n")
} }
allRequirementsFullfilled() { allRequirementsFullfilled() {
return this.requiredObjects().every(objName => objName in C.currentObjectsByName) return this.requiredObjects().every(objName => objName in Objects.currentObjectsByName)
} }
undefinedVariables() { undefinedVariables() {
return this.requiredObjects().filter(objName => !(objName in C.currentObjectsByName)) return this.requiredObjects().filter(objName => !(objName in Objects.currentObjectsByName))
} }
recache() { recache() {
if(this.cached) if(this.cached)
this.cachedValue = this.calc.evaluate(C.currentObjectsByName) this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
} }
execute(x = 1) { execute(x = 1) {
if(this.cached) { if(this.cached) {
if(this.cachedValue == null) if(this.cachedValue == null)
this.cachedValue = this.calc.evaluate(C.currentObjectsByName) this.cachedValue = this.calc.evaluate(Objects.currentObjectsByName)
return this.cachedValue return this.cachedValue
} }
C.currentVars = Object.assign({'x': x}, C.currentObjectsByName) ExprParser.currentVars = Object.assign({'x': x}, Objects.currentObjectsByName)
return this.calc.evaluate(C.currentVars) return this.calc.evaluate(ExprParser.currentVars)
} }
simplify(x) { simplify(x) {
var expr = this.calc.substitute('x', x).simplify() let expr = this.calc.substitute('x', x).simplify()
if(expr.evaluate() == 0) return '0' if(expr.evaluate() === 0) expr = '0'
var str = Utils.makeExpressionReadable(expr.toString()); return new Expression(expr)
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() { toEditableString() {
@ -89,11 +90,17 @@ class Expression {
toString(forceSign=false) { toString(forceSign=false) {
let str = Utils.makeExpressionReadable(this.calc.toString()) let str = Utils.makeExpressionReadable(this.calc.toString())
if(str[0] != '-' && forceSign) str = '+' + str if(str !== undefined && str.match(/^\d*\.\d+$/)) {
if(str.split('.')[1].split('0').length > 7) {
// Likely rounding error
str = parseFloat(str.substring(0, str.length-1)).toString();
}
}
if(str[0] !== '-' && forceSign) str = '+' + str
return str return str
} }
} }
function executeExpression(expr){ export function executeExpression(expr){
return (new Expression(expr.toString())).execute() return (new Expression(expr.toString())).execute()
} }

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,27 +16,25 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library
.import "common.js" as C import * as Expr from "./expression.mjs"
.import "point.js" as P import * as Seq from "./sequence.mjs"
.import "text.js" as T import * as Dom from "./domain.mjs"
.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) export const Expression = Expr.Expression
C.registerObject(F.Function) export const executeExpression = Expr.executeExpression
C.registerObject(GB.GainBode) export const Sequence = Seq.Sequence
C.registerObject(PB.PhaseBode)
C.registerObject(SGB.SommeGainsBode) // Domains
C.registerObject(SPB.SommePhasesBode) export const Domain = Dom.Domain
C.registerObject(X.XCursor) export const EmptySet = Dom.EmptySet
C.registerObject(S.Sequence) export const Range = Dom.Range
C.registerObject(R.RepartitionFunction) 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

View file

@ -1,270 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "../expr-eval.js" as ExprEval
/**
* true if latex has been enabled by the user, false otherwise.
*/
var enabled = false
/**
* 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":
return '\\frac{d' + args[0].substr(1, args[0].length-2).replace(new RegExp(args[1].substr(1, args[1].length-2), 'g'), 'x') + '}{dx}';
break;
case "integral":
return '\\int\\limits_{' + args[0] + '}^{' + args[1] + '}' + args[2].substr(1, args[2].length-2) + ' d' + args[3].substr(1, args[3].length-2);
break;
case "sqrt":
return '\\sqrt\\left(' + args.join(', ') + '\\right)';
break;
case "abs":
return '\\left|' + args.join(', ') + '\\right|';
break;
case "floor":
return '\\left\\lfloor' + args.join(', ') + '\\right\\rfloor';
break;
case "ceil":
return '\\left\\lceil' + args.join(', ') + '\\right\\rceil';
break;
default:
return '\\mathrm{' + f + '}\\left(' + args.join(', ') + '\\right)';
break;
}
}
/**
* Creates a latex variable from a variable.
*
* @param {string} vari - variable to convert
* @returns {string}
*/
function variable(vari) {
let unicodechars = ["α","β","γ","δ","ε","ζ","η",
"π","θ","κ","λ","μ","ξ","ρ",
"ς","σ","τ","φ","χ","ψ","ω",
"Γ","Δ","Θ","Λ","Ξ","Π","Σ",
"Φ","Ψ","Ω","ₐ","ₑ","ₒ","ₓ",
"ₕ","ₖ","ₗ","ₘ","ₙ","ₚ","ₛ",
"ₜ","¹","²","³","⁴","⁵","⁶",
"⁷","⁸","⁹","⁰","₁","₂","₃",
"₄","₅","₆","₇","₈","₉","₀",
"pi"]
let equivalchars = ["\\alpha","\\beta","\\gamma","\\delta","\\epsilon","\\zeta","\\eta",
"\\pi","\\theta","\\kappa","\\lambda","\\mu","\\xi","\\rho",
"\\sigma","\\sigma","\\tau","\\phi","\\chi","\\psi","\\omega",
"\\Gamma","\\Delta","\\Theta","\\Lambda","\\Xi","\\Pi","\\Sigma",
"\\Phy","\\Psi","\\Omega","{}_{a}","{}_{e}","{}_{o}","{}_{x}",
"{}_{h}","{}_{k}","{}_{l}","{}_{m}","{}_{n}","{}_{p}","{}_{s}",
"{}_{t}","{}^{1}","{}^{2}","{}^{3}","{}^{4}","{}^{5}","{}^{6}",
"{}^{7}","{}^{8}","{}^{9}","{}^{0}","{}_{1}","{}_{2}","{}_{3}",
"{}_{4}","{}_{5}","{}_{6}","{}_{7}","{}_{8}","{}_{9}","{}_{0}",
"\\pi"]
for(let i = 0; i < unicodechars.length; i++) {
//console.log(vari, unicodechars[i], equivalchars[i]);
if(vari.includes(unicodechars[i]))
vari = vari.replace(new RegExp(unicodechars[i], 'g'), equivalchars[i])
}
return vari;
}
/**
* Converts expr-eval tokens to a latex string.
*
* @param {Array} tokens - expr-eval tokens list
* @returns {string}
*/
function expression(tokens) {
var nstack = [];
var n1, n2, n3;
var f, args, argCount;
for (var i = 0; i < tokens.length; i++) {
var item = tokens[i];
var type = item.type;
switch(type) {
case ExprEval.INUMBER:
if(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]);
}

View file

@ -1,6 +1,6 @@
/** /**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions. * LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001 * Copyright (C) 2021-2024 Ad5001
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,18 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
.pragma library import * as Expr from "expression.mjs"
import * as Utils from "../utils.mjs"
.import "common.js" as C import Latex from "../module/latex.mjs"
.import "expression.js" as Expr import Objects from "../module/objects.mjs"
.import "../utils.js" as Utils import ExprParser from "../module/expreval.mjs"
.import "../math/latex.js" as Latex
/** /**
* Represents mathematical object for sequences. * Represents mathematical object for sequences.
*/ */
class Sequence extends Expr.Expression { export class Sequence extends Expr.Expression {
constructor(name, baseValues = {}, valuePlus = 1, expr = "") { constructor(name, baseValues = {}, valuePlus = 1, expr = "") {
// u[n+valuePlus] = expr // u[n+valuePlus] = expr
super(expr) super(expr)
@ -35,9 +33,9 @@ class Sequence extends Expr.Expression {
this.baseValues = baseValues this.baseValues = baseValues
this.calcValues = Object.assign({}, baseValues) this.calcValues = Object.assign({}, baseValues)
this.latexValues = Object.assign({}, baseValues) this.latexValues = Object.assign({}, baseValues)
for(var n in this.calcValues) for(let n in this.calcValues)
if(['string', 'number'].includes(typeof this.calcValues[n])) { 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.latexValues[n] = Latex.expression(parsed.tokens)
this.calcValues[n] = parsed.evaluate() this.calcValues[n] = parsed.evaluate()
} }
@ -45,7 +43,7 @@ class Sequence extends Expr.Expression {
} }
isConstant() { isConstant() {
return this.expr.indexOf("n") == -1 return this.expr.indexOf("n") === -1
} }
execute(n = 1) { execute(n = 1) {
@ -56,28 +54,31 @@ class Sequence extends Expr.Expression {
} }
simplify(n = 1) { simplify(n = 1) {
if(n in this.calcValues) if(!(n in this.calcValues))
return Utils.makeExpressionReadable(this.calcValues[n].toString()) this.cache(n)
this.cache(n) return this.calcValues[n].toString()
return Utils.makeExpressionReadable(this.calcValues[n].toString())
} }
cache(n = 1) { cache(n = 1) {
var str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString()) let str = Utils.simplifyExpression(this.calc.substitute('n', n-this.valuePlus).toString())
var expr = C.parser.parse(str).simplify() let expr = ExprParser.parse(str).simplify()
C.currentVars = Object.assign( // 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) {'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(ExprParser.currentVars)
this.calcValues[n] = expr.evaluate(C.currentVars)
} }
toString(forceSign=false) { toString(forceSign=false) {
var str = Utils.makeExpressionReadable(this.calc.toString()) let str = Utils.makeExpressionReadable(this.calc.toString())
if(str[0] != '-' && forceSign) str = '+' + str if(str[0] !== '-' && forceSign) str = '+' + str
var subtxt = this.valuePlus == 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus) let subtxt = this.valuePlus === 0 ? 'ₙ' : Utils.textsub('n+' + this.valuePlus)
var ret = `${this.name}${subtxt} = ${str}${this.baseValues.length == 0 ? '' : "\n"}` let ret = `${this.name}${subtxt} = ${str}${this.baseValues.length === 0 ? '' : "\n"}`
ret += Object.keys(this.baseValues).map( ret += Object.keys(this.baseValues).map(
n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}` n => `${this.name}${Utils.textsub(n)} = ${this.baseValues[n]}`
).join('; ') ).join('; ')
@ -85,10 +86,10 @@ class Sequence extends Expr.Expression {
} }
toLatexString(forceSign=false) { toLatexString(forceSign=false) {
var str = this.latexMarkup let str = this.latexMarkup
if(str[0] != '-' && forceSign) str = '+' + str if(str[0] !== '-' && forceSign) str = '+' + str
var subtxt = '_{n' + (this.valuePlus == 0 ? '' : '+' + this.valuePlus) + '}' let subtxt = '_{n' + (this.valuePlus === 0 ? '' : '+' + this.valuePlus) + '}'
var ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length == 0 ? '' : "\n"}\\\\` let ret = `\\begin{array}{l}${Latex.variable(this.name)}${subtxt} = ${str}${this.latexValues.length === 0 ? '' : "\n"}\\\\`
ret += Object.keys(this.latexValues).map( ret += Object.keys(this.latexValues).map(
n => `${this.name}_{${n}} = ${this.latexValues[n]}` n => `${this.name}_{${n}} = ${this.latexValues[n]}`
).join('; ') + "\\end{array}" ).join('; ') + "\\end{array}"

View file

@ -0,0 +1,594 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Module } from "./common.mjs"
import { FUNCTION, Interface, CanvasInterface, DialogInterface } from "./interface.mjs"
import { textsup } from "../utils.mjs"
import { Expression } from "../math/index.mjs"
import Latex from "./latex.mjs"
import Objects from "./objects.mjs"
import History from "./history.mjs"
class CanvasAPI extends Module {
constructor() {
super("Canvas", {
canvas: CanvasInterface,
drawingErrorDialog: DialogInterface
})
/** @type {CanvasInterface} */
this._canvas = null
/** @type {CanvasRenderingContext2D} */
this._ctx = null
/**
* @type {{show(string, string, string)}}
* @private
*/
this._drawingErrorDialog = null
/**
*
* @type {Object.<string, {expression: Expression, value: number, maxDraw: number}>}
*/
this.axesSteps = {
x: {
expression: null,
value: -1,
maxDraw: -1
},
y: {
expression: null,
value: -1,
maxDraw: -1
}
}
}
/**
* Initialize the module.
* @param {CanvasInterface} canvas
* @param {{show(string, string, string)}} drawingErrorDialog
*/
initialize({ canvas, drawingErrorDialog }) {
super.initialize({ canvas, drawingErrorDialog })
this._canvas = canvas
this._drawingErrorDialog = drawingErrorDialog
}
get width() {
if(!this.initialized) throw new Error("Attempting width before initialize!")
return this._canvas.width
}
get height() {
if(!this.initialized) throw new Error("Attempting height before initialize!")
return this._canvas.height
}
/**
* Minimum x of the diagram, provided from settings.
* @returns {number}
*/
get xmin() {
if(!this.initialized) throw new Error("Attempting xmin before initialize!")
return this._canvas.xmin
}
/**
* Zoom on the x-axis of the diagram, provided from settings.
* @returns {number}
*/
get xzoom() {
if(!this.initialized) throw new Error("Attempting xzoom before initialize!")
return this._canvas.xzoom
}
/**
* Maximum y of the diagram, provided from settings.
* @returns {number}
*/
get ymax() {
if(!this.initialized) throw new Error("Attempting ymax before initialize!")
return this._canvas.ymax
}
/**
* Zoom on the y-axis of the diagram, provided from settings.
* @returns {number}
*/
get yzoom() {
if(!this.initialized) throw new Error("Attempting yzoom before initialize!")
return this._canvas.yzoom
}
/**
* Label used on the x-axis, provided from settings.
* @returns {string}
*/
get xlabel() {
if(!this.initialized) throw new Error("Attempting xlabel before initialize!")
return this._canvas.xlabel
}
/**
* Label used on the y-axis, provided from settings.
* @returns {string}
*/
get ylabel() {
if(!this.initialized) throw new Error("Attempting ylabel before initialize!")
return this._canvas.ylabel
}
/**
* Width of lines that will be drawn into the canvas, provided from settings.
* @returns {number}
*/
get linewidth() {
if(!this.initialized) throw new Error("Attempting linewidth before initialize!")
return this._canvas.linewidth
}
/**
* Font size of the text that will be drawn into the canvas, provided from settings.
* @returns {number}
*/
get textsize() {
if(!this.initialized) throw new Error("Attempting textsize before initialize!")
return this._canvas.textsize
}
/**
* True if the canvas should be in logarithmic mode, false otherwise.
* @returns {boolean}
*/
get logscalex() {
if(!this.initialized) throw new Error("Attempting logscalex before initialize!")
return this._canvas.logscalex
}
/**
* True if the x graduation should be shown, false otherwise.
* @returns {boolean}
*/
get showxgrad() {
if(!this.initialized) throw new Error("Attempting showxgrad before initialize!")
return this._canvas.showxgrad
}
/**
* True if the y graduation should be shown, false otherwise.
* @returns {boolean}
*/
get showygrad() {
if(!this.initialized) throw new Error("Attempting showygrad before initialize!")
return this._canvas.showygrad
}
/**
* Max power of the logarithmic scaled on the x axis in logarithmic mode.
* @returns {number}
*/
get maxgradx() {
if(!this.initialized) throw new Error("Attempting maxgradx before initialize!")
return Math.min(
309, // 10e309 = Infinity (beyond this land be dragons)
Math.max(
Math.ceil(Math.abs(Math.log10(this.xmin))),
Math.ceil(Math.abs(Math.log10(this.px2x(this.width))))
)
)
}
//
// Methods to draw the canvas
//
requestPaint() {
if(!this.initialized) throw new Error("Attempting requestPaint before initialize!")
this._canvas.requestPaint()
}
/**
* Redraws the entire canvas
*/
redraw() {
if(!this.initialized) throw new Error("Attempting redraw before initialize!")
this._ctx = this._canvas.getContext("2d")
this._computeAxes()
this._reset()
this._drawGrid()
this._drawAxes()
this._drawLabels()
this._ctx.lineWidth = this.linewidth
for(let objType in Objects.currentObjects) {
for(let obj of Objects.currentObjects[objType]) {
this._ctx.strokeStyle = obj.color
this._ctx.fillStyle = obj.color
if(obj.visible)
try {
obj.draw(this)
} catch(e) {
// Drawing throws an error. Generally, it's due to a new modification (or the opening of a file)
console.error(e)
console.log(e.stack)
this._drawingErrorDialog.showDialog(objType, obj.name, e.message)
History.undo()
}
}
}
this._ctx.lineWidth = 1
}
/**
* Calculates information for drawing gradations for axes.
* @private
*/
_computeAxes() {
let exprY = new Expression(`x*(${this._canvas.yaxisstep})`)
let y1 = exprY.execute(1)
let exprX = new Expression(`x*(${this._canvas.xaxisstep})`)
let x1 = exprX.execute(1)
this.axesSteps = {
x: {
expression: exprX,
value: x1,
maxDraw: Math.ceil(Math.max(Math.abs(this.xmin), Math.abs(this.px2x(this.width))) / x1)
},
y: {
expression: exprY,
value: y1,
maxDraw: Math.ceil(Math.max(Math.abs(this.ymax), Math.abs(this.px2y(this.height))) / y1)
}
}
}
/**
* Resets the canvas to a blank one with default setting.
* @private
*/
_reset() {
// Reset
this._ctx.fillStyle = "#FFFFFF"
this._ctx.strokeStyle = "#000000"
this._ctx.font = `${this.textsize}px sans-serif`
this._ctx.fillRect(0, 0, this.width, this.height)
}
/**
* Draws the grid.
* @private
*/
_drawGrid() {
this._ctx.strokeStyle = "#C0C0C0"
if(this.logscalex) {
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow++) {
for(let xmulti = 1; xmulti < 10; xmulti++) {
this.drawXLine(Math.pow(10, xpow) * xmulti)
}
}
} else {
for(let x = 0; x < this.axesSteps.x.maxDraw; x += 1) {
this.drawXLine(x * this.axesSteps.x.value)
this.drawXLine(-x * this.axesSteps.x.value)
}
}
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
this.drawYLine(y * this.axesSteps.y.value)
this.drawYLine(-y * this.axesSteps.y.value)
}
}
/**
* Draws the graph axes.
* @private
*/
_drawAxes() {
this._ctx.strokeStyle = "#000000"
let axisypos = this.logscalex ? 1 : 0
this.drawXLine(axisypos)
this.drawYLine(0)
let axisypx = this.x2px(axisypos) // X coordinate of Y axis
let axisxpx = this.y2px(0) // Y coordinate of X axis
// Drawing arrows
this.drawLine(axisypx, 0, axisypx - 10, 10)
this.drawLine(axisypx, 0, axisypx + 10, 10)
this.drawLine(this.width, axisxpx, this.width - 10, axisxpx - 10)
this.drawLine(this.width, axisxpx, this.width - 10, axisxpx + 10)
}
/**
* Resets the canvas to a blank one with default setting.
* @private
*/
_drawLabels() {
let axisypx = this.x2px(this.logscalex ? 1 : 0) // X coordinate of Y axis
let axisxpx = this.y2px(0) // Y coordinate of X axis
// Labels
this._ctx.fillStyle = "#000000"
this._ctx.font = `${this.textsize}px sans-serif`
this._ctx.fillText(this.ylabel, axisypx + 10, 24)
let textWidth = this._ctx.measureText(this.xlabel).width
this._ctx.fillText(this.xlabel, this.width - 14 - textWidth, axisxpx - 5)
// Axis graduation labels
this._ctx.font = `${this.textsize - 4}px sans-serif`
let txtMinus = this._ctx.measureText("-").width
if(this.showxgrad) {
if(this.logscalex) {
for(let xpow = -this.maxgradx; xpow <= this.maxgradx; xpow += 1) {
textWidth = this._ctx.measureText("10" + textsup(xpow)).width
if(xpow !== 0)
this.drawVisibleText("10" + textsup(xpow), this.x2px(Math.pow(10, xpow)) - textWidth / 2, axisxpx + 16 + (6 * (xpow === 1)))
}
} else {
for(let x = 1; x < this.axesSteps.x.maxDraw; x += 1) {
let drawX = x * this.axesSteps.x.value
let txtX = this.axesSteps.x.expression.simplify(x).toString().replace(/^\((.+)\)$/, "$1")
let textHeight = this.measureText(txtX).height
this.drawVisibleText(txtX, this.x2px(drawX) - 4, axisxpx + this.textsize / 2 + textHeight)
this.drawVisibleText("-" + txtX, this.x2px(-drawX) - 4, axisxpx + this.textsize / 2 + textHeight)
}
}
}
if(this.showygrad) {
for(let y = 0; y < this.axesSteps.y.maxDraw; y += 1) {
let drawY = y * this.axesSteps.y.value
let txtY = this.axesSteps.y.expression.simplify(y).toString().replace(/^\((.+)\)$/, "$1")
textWidth = this._ctx.measureText(txtY).width
this.drawVisibleText(txtY, axisypx - 6 - textWidth, this.y2px(drawY) + 4 + (10 * (y === 0)))
if(y !== 0)
this.drawVisibleText("-" + txtY, axisypx - 6 - textWidth - txtMinus, this.y2px(-drawY) + 4)
}
}
this._ctx.fillStyle = "#FFFFFF"
}
//
// Public functions
//
/**
* Draws an horizontal line at x plot coordinate.
* @param {number} x
*/
drawXLine(x) {
if(this.isVisible(x, this.ymax)) {
this.drawLine(this.x2px(x), 0, this.x2px(x), this.height)
}
}
/**
* Draws an vertical line at y plot coordinate
* @param {number} y
* @private
*/
drawYLine(y) {
if(this.isVisible(this.xmin, y)) {
this.drawLine(0, this.y2px(y), this.width, this.y2px(y))
}
}
/**
* Writes multiline text onto the canvas.
* NOTE: The x and y properties here are relative to the canvas, not the plot.
* @param {string} text
* @param {number} x
* @param {number} y
*/
drawVisibleText(text, x, y) {
if(x > 0 && x < this.width && y > 0 && y < this.height) {
text.toString().split("\n").forEach((txt, i) => {
this._ctx.fillText(txt, x, y + (this.textsize * i))
})
}
}
/**
* Draws an image onto the canvas.
* NOTE: The x, y width and height properties here are relative to the canvas, not the plot.
* @param {CanvasImageSource} image
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
*/
drawVisibleImage(image, x, y, width, height) {
this._canvas.markDirty(Qt.rect(x, y, width, height))
this._ctx.drawImage(image, x, y, width, height)
}
/**
* Measures the width and height of a multiline text that would be drawn onto the canvas.
* @param {string} text
* @returns {{width: number, height: number}}
*/
measureText(text) {
let theight = 0
let twidth = 0
let defaultHeight = this.textsize * 1.2 // Approximate but good enough!
for(let txt of text.split("\n")) {
theight += defaultHeight
if(this._ctx.measureText(txt).width > twidth) twidth = this._ctx.measureText(txt).width
}
return { "width": twidth, "height": theight }
}
/**
* Converts an x coordinate to its relative position on the canvas.
* It supports both logarithmic and non-logarithmic scale depending on the currently selected mode.
* @param {number} x
* @returns {number}
*/
x2px(x) {
if(this.logscalex) {
const logxmin = Math.log(this.xmin)
return (Math.log(x) - logxmin) * this.xzoom
} else
return (x - this.xmin) * this.xzoom
}
/**
* Converts an y coordinate to it's relative position on the canvas.
* The y-axis not supporting logarithmic scale, it only supports linear conversion.
* @param {number} y
* @returns {number}
*/
y2px(y) {
return (this.ymax - y) * this.yzoom
}
/**
* Converts an x px position on the canvas to it's corresponding coordinate on the plot.
* It supports both logarithmic and non-logarithmic scale depending on the currently selected mode.
* @param {number} px
* @returns {number}
*/
px2x(px) {
if(this.logscalex) {
return Math.exp(px / this.xzoom + Math.log(this.xmin))
} else
return (px / this.xzoom + this.xmin)
}
/**
* Converts an x px position on the canvas to it's corresponding coordinate on the plot.
* It supports both logarithmic and non logarithmic scale depending on the currently selected mode.
* @param {number} px
* @returns {number}
*/
px2y(px) {
return -(px / this.yzoom - this.ymax)
}
/**
* Checks whether a plot point (x, y) is visible or not on the canvas.
* @param {number} x
* @param {number} y
* @returns {boolean}
*/
isVisible(x, y) {
return (this.x2px(x) >= 0 && this.x2px(x) <= this.width) && (this.y2px(y) >= 0 && this.y2px(y) <= this.height)
}
/**
* Draws a line from plot point (x1, y1) to plot point (x2, y2).
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
*/
drawLine(x1, y1, x2, y2) {
this._ctx.beginPath()
this._ctx.moveTo(x1, y1)
this._ctx.lineTo(x2, y2)
this._ctx.stroke()
}
/**
* Draws a dashed line from plot point (x1, y1) to plot point (x2, y2).
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
* @param {number} dashPxSize
*/
drawDashedLine(x1, y1, x2, y2, dashPxSize = 6) {
this._ctx.setLineDash([dashPxSize / 2, dashPxSize])
this.drawLine(x1, y1, x2, y2)
this._ctx.setLineDash([])
}
/**
* Renders latex markup ltxText to an image and loads it. Returns a dictionary with three values: source, width and height.
* @param {string} ltxText
* @param {string} color
* @param {function(LatexRenderResult|{width: number, height: number, source: string})} callback
*/
renderLatexImage(ltxText, color, callback) {
const onRendered = (imgData) => {
if(!this._canvas.isImageLoaded(imgData.source) && !this._canvas.isImageLoading(imgData.source)) {
// Wait until the image is loaded to callback.
this._canvas.loadImage(imgData.source)
this._canvas.imageLoaders[imgData.source] = [callback, imgData]
} else {
// Callback directly
callback(imgData)
}
}
const prerendered = Latex.findPrerendered(ltxText, this.textsize, color)
if(prerendered !== null)
onRendered(prerendered)
else
Latex.requestAsyncRender(ltxText, this.textsize, color).then(onRendered)
}
//
// Context methods
//
get font() {
return this._ctx.font
}
set font(value) {
return this._ctx.font = value
}
/**
* Draws an act on the canvas centered on a point.
* @param {number} x
* @param {number} y
* @param {number} radius
* @param {number} startAngle
* @param {number} endAngle
* @param {boolean} counterclockwise
*/
arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
this._ctx.beginPath()
this._ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise)
this._ctx.stroke()
}
/**
* Draws a filled circle centered on a point.
* @param {number} x
* @param {number} y
* @param {number} radius
*/
disc(x, y, radius) {
this._ctx.beginPath()
this._ctx.arc(x, y, radius, 0, 2 * Math.PI)
this._ctx.fill()
}
/**
* Draws a filled rectangle onto the canvas.
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
*/
fillRect(x, y, w, h) {
this._ctx.fillRect(x, y, w, h)
}
}
/** @type {CanvasAPI} */
Modules.Canvas = Modules.Canvas || new CanvasAPI()
export default Modules.Canvas

View file

@ -0,0 +1,55 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Interface } from "./interface.mjs"
/**
* Base class for global APIs in runtime.
*/
export class Module {
/**
*
* @param {string} name - Name of the API
* @param {Object.<string, (Interface|string)>} initializationParameters - List of parameters for the initialize function.
*/
constructor(name, initializationParameters = {}) {
console.log(`Loading module ${name}...`)
this.__name = name
this.__initializationParameters = initializationParameters
this.initialized = false
}
/**
* Checks if all requirements are defined.
* @param {Object.<string, any>} options
*/
initialize(options) {
if(this.initialized)
throw new Error(`Cannot reinitialize module ${this.__name}.`)
for(const [name, value] of Object.entries(this.__initializationParameters)) {
if(!options.hasOwnProperty(name))
throw new Error(`Option '${name}' of initialize of module ${this.__name} does not exist.`)
if(typeof value === "function" && value.prototype instanceof Interface)
Interface.check_implementation(value, options[name])
else if(typeof value !== typeof options[name])
throw new Error(`Option '${name}' of initialize of module ${this.__name} is not a '${value}' (${typeof options[name]}).`)
}
this.initialized = true
}
}

View file

@ -0,0 +1,114 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Module } from "./common.mjs"
import { Parser } from "../lib/expr-eval/parser.mjs"
const evalVariables = {
// Variables not provided by expr-eval.js, needs to be provided manually
"pi": Math.PI,
"PI": Math.PI,
"π": Math.PI,
"inf": Infinity,
"infinity": Infinity,
"Infinity": Infinity,
"∞": Infinity,
"e": Math.E,
"E": Math.E,
"true": true,
"false": false
}
export class ExprParserAPI extends Module {
constructor() {
super("ExprParser")
this.currentVars = {}
this._parser = new Parser()
this._parser.consts = Object.assign({}, this._parser.consts, evalVariables)
this._parser.functions.integral = this.integral.bind(this)
this._parser.functions.derivative = this.derivative.bind(this)
}
/**
* Parses arguments for a function, returns the corresponding JS function if it exists.
* Throws either usage error otherwise.
* @param {array} args - Arguments of the function, either [ ExecutableObject ] or [ string, variable ].
* @param {string} usage1 - Usage for executable object.
* @param {string} usage2 - Usage for string function.
* @return {function} JS function to call.
*/
parseArgumentsForFunction(args, usage1, usage2) {
let f, target, variable
if(args.length === 1) {
// Parse object
f = args[0]
if(typeof f !== "object" || !f.execute)
throw EvalError(qsTranslate("usage", "Usage:\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, b, ...args) {
let usage1 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: ExecutableObject>)")
let usage2 = qsTranslate("usage", "integral(<from: number>, <to: number>, <f: string>, <variable: string>)")
let f = this.parseArgumentsForFunction(args, usage1, usage2)
if(a == null || b == null)
throw EvalError(qsTranslate("usage", "Usage:\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(<f: ExecutableObject>, <x: number>)")
let usage2 = qsTranslate("usage", "derivative(<f: string>, <variable: string>, <x: number>)")
let x = args.pop()
let f = this.parseArgumentsForFunction(args, usage1, usage2)
if(x == null)
throw EvalError(qsTranslate("usage", "Usage:\n%1\n%2").arg(usage1).arg(usage2))
let derivative_precision = x / 10
return (f(x + derivative_precision / 2) - f(x - derivative_precision / 2)) / derivative_precision
}
}
/** @type {ExprParserAPI} */
Modules.ExprParser = Modules.ExprParser || new ExprParserAPI()
export default Modules.ExprParser

View file

@ -0,0 +1,82 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Module } from "./common.mjs"
import Latex from "./latex.mjs"
import { HistoryInterface, NUMBER, STRING } from "./interface.mjs"
class HistoryAPI extends Module {
constructor() {
super("History", {
historyObj: HistoryInterface,
themeTextColor: STRING,
imageDepth: NUMBER,
fontSize: NUMBER
})
// History QML object
this.history = null
this.themeTextColor = "#FF0000"
this.imageDepth = 2
this.fontSize = 28
}
initialize({ historyObj, themeTextColor, imageDepth, fontSize }) {
super.initialize({ historyObj, themeTextColor, imageDepth, fontSize })
console.log("Initializing history...")
this.history = historyObj
this.themeTextColor = themeTextColor
this.imageDepth = imageDepth
this.fontSize = fontSize
}
undo() {
if(!this.initialized) throw new Error("Attempting undo before initialize!")
this.history.undo()
}
redo() {
if(!this.initialized) throw new Error("Attempting redo before initialize!")
this.history.redo()
}
clear() {
if(!this.initialized) throw new Error("Attempting clear before initialize!")
this.history.clear()
}
addToHistory(action) {
if(!this.initialized) throw new Error("Attempting addToHistory before initialize!")
this.history.addToHistory(action)
}
unserialize(...data) {
if(!this.initialized) throw new Error("Attempting unserialize before initialize!")
this.history.unserialize(...data)
}
serialize() {
if(!this.initialized) throw new Error("Attempting serialize before initialize!")
return this.history.serialize()
}
}
/** @type {HistoryAPI} */
Modules.History = Modules.History || new HistoryAPI()
export default Modules.History

View file

@ -0,0 +1,130 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const NUMBER = 0
export const STRING = "string"
export const BOOLEAN = true
export const OBJECT = {}
export const FUNCTION = () => {
}
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
* @return {boolean}
*/
static check_implementation(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") {
console.log(classToCheck[property], value)
if(!classToCheck.hasOwnProperty(property))
// 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.check_implementation(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}'.`)
}
}
/**
* Decorator to automatically check if a class conforms to the current interface.
* @param {object} class_
*/
implement(class_) {
Interface.check_implementation(this, class_)
return class_
}
}
export class SettingsInterface extends Interface {
constructor() {
super()
this.width = NUMBER
this.height = NUMBER
this.xmin = NUMBER
this.ymax = NUMBER
this.xzoom = NUMBER
this.yzoom = NUMBER
this.xaxisstep = STRING
this.yaxisstep = STRING
this.xlabel = STRING
this.ylabel = STRING
this.linewidth = NUMBER
this.textsize = NUMBER
this.logscalex = BOOLEAN
this.showxgrad = BOOLEAN
this.showygrad = BOOLEAN
}
}
export class CanvasInterface extends SettingsInterface {
constructor() {
super()
this.imageLoaders = OBJECT
/** @type {function(string): CanvasRenderingContext2D} */
this.getContext = FUNCTION
/** @type {function(rect)} */
this.markDirty = FUNCTION
/** @type {function(string)} */
this.loadImage = FUNCTION
/** @type {function()} */
this.requestPaint = FUNCTION
}
}
export class RootInterface extends Interface {
constructor() {
super()
this.width = NUMBER
this.height = NUMBER
this.updateObjectsLists = FUNCTION
}
}
export class DialogInterface extends Interface {
constructor() {
super()
this.show = FUNCTION
}
}
export class HistoryInterface extends Interface {
constructor() {
super()
this.undo = FUNCTION
this.redo = FUNCTION
this.clear = FUNCTION
this.addToHistory = FUNCTION
this.unserialize = FUNCTION
this.serialize = FUNCTION
}
}

View file

@ -0,0 +1,181 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Module } from "./common.mjs"
import Objects from "./objects.mjs"
import History from "./history.mjs"
import Canvas from "./canvas.mjs"
import { DialogInterface, FUNCTION, Interface, RootInterface, SettingsInterface } from "./interface.mjs"
class IOAPI extends Module {
constructor() {
super("IO", {
alert: DialogInterface,
root: RootInterface,
settings: SettingsInterface
})
/**
* Path of the currently opened file. Empty if no file is opened.
* @type {string}
*/
this.saveFileName = ""
}
/**
* Initializes module with QML elements.
* @param {RootInterface} root
* @param {SettingsInterface} settings
* @param {{show: function(string)}} alert
*/
initialize({ root, settings, alert }) {
super.initialize({ root, settings, alert })
this.rootElement = root
this.settings = settings
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"
this.saveFilename = filename
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": this.settings.xzoom,
"yzoom": this.settings.yzoom,
"xmin": this.settings.xmin,
"ymax": this.settings.ymax,
"xaxisstep": this.settings.xaxisstep,
"yaxisstep": this.settings.yaxisstep,
"xaxislabel": this.settings.xlabel,
"yaxislabel": this.settings.ylabel,
"logscalex": this.settings.logscalex,
"linewidth": this.settings.linewidth,
"showxgrad": this.settings.showxgrad,
"showygrad": this.settings.showygrad,
"textsize": this.settings.textsize,
"history": 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()))
History.history.saved = true
}
/**
* Loads the diagram from a certain \c filename.
* @param {string} filename
*/
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
this.settings.saveFilename = filename
this.settings.xzoom = parseFloat(data["xzoom"]) || 100
this.settings.yzoom = parseFloat(data["yzoom"]) || 10
this.settings.xmin = parseFloat(data["xmin"]) || 5 / 10
this.settings.ymax = parseFloat(data["ymax"]) || 24
this.settings.xaxisstep = data["xaxisstep"] || "4"
this.settings.yaxisstep = data["yaxisstep"] || "4"
this.settings.xlabel = data["xaxislabel"] || ""
this.settings.ylabel = data["yaxislabel"] || ""
this.settings.logscalex = data["logscalex"] === true
if("showxgrad" in data)
this.settings.showxgrad = data["showxgrad"]
if("showygrad" in data)
this.settings.textsize = data["showygrad"]
if("linewidth" in data)
this.settings.linewidth = data["linewidth"]
if("textsize" in data)
this.settings.textsize = data["textsize"]
this.rootElement.height = parseFloat(data["height"]) || 500
this.rootElement.width = parseFloat(data["width"]) || 1000
// Importing objects
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"])
// Refreshing sidebar
this.rootElement.updateObjectsLists()
} 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
}
Canvas.redraw()
this.alert.show(qsTranslate("io", "Loaded file '%1'.").arg(basename))
History.history.saved = true
}
}
/** @type {IOAPI} */
Modules.IO = Modules.IO || new IOAPI()
export default Modules.IO

View file

@ -0,0 +1,329 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Module } from "./common.mjs"
import * as Instruction from "../lib/expr-eval/instruction.mjs"
import { escapeValue } from "../lib/expr-eval/expression.mjs"
const unicodechars = ["α", "β", "γ", "δ", "ε", "ζ", "η",
"π", "θ", "κ", "λ", "μ", "ξ", "ρ",
"ς", "σ", "τ", "φ", "χ", "ψ", "ω",
"Γ", "Δ", "Θ", "Λ", "Ξ", "Π", "Σ",
"Φ", "Ψ", "Ω", "ₐ", "ₑ", "ₒ", "ₓ",
"ₕ", "ₖ", "ₗ", "ₘ", "ₙ", "ₚ", "ₛ",
"ₜ", "¹", "²", "³", "⁴", "⁵", "⁶",
"⁷", "⁸", "⁹", "⁰", "₁", "₂", "₃",
"₄", "₅", "₆", "₇", "₈", "₉", "₀",
"pi", "∞"]
const equivalchars = ["\\alpha", "\\beta", "\\gamma", "\\delta", "\\epsilon", "\\zeta", "\\eta",
"\\pi", "\\theta", "\\kappa", "\\lambda", "\\mu", "\\xi", "\\rho",
"\\sigma", "\\sigma", "\\tau", "\\phi", "\\chi", "\\psi", "\\omega",
"\\Gamma", "\\Delta", "\\Theta", "\\Lambda", "\\Xi", "\\Pi", "\\Sigma",
"\\Phy", "\\Psi", "\\Omega", "{}_{a}", "{}_{e}", "{}_{o}", "{}_{x}",
"{}_{h}", "{}_{k}", "{}_{l}", "{}_{m}", "{}_{n}", "{}_{p}", "{}_{s}",
"{}_{t}", "{}^{1}", "{}^{2}", "{}^{3}", "{}^{4}", "{}^{5}", "{}^{6}",
"{}^{7}", "{}^{8}", "{}^{9}", "{}^{0}", "{}_{1}", "{}_{2}", "{}_{3}",
"{}_{4}", "{}_{5}", "{}_{6}", "{}_{7}", "{}_{8}", "{}_{9}", "{}_{0}",
"\\pi", "\\infty"]
/**
* Class 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 {
constructor() {
super("Latex")
/**
* true if latex has been enabled by the user, false otherwise.
*/
this.enabled = Helper.getSettingBool("enable_latex")
}
/**
* Prepares and renders a latex string into a png file.
*
* @param {string} markup - LaTeX markup to render.
* @param {number} fontSize - Font size (in pt) to render.
* @param {color} color - Color of the text to render.
* @returns {LatexRenderResult}
*/
renderSync(markup, fontSize, color) {
let args = Latex.render(markup, fontSize, color).split(",")
return new LatexRenderResult(...args)
}
/**
* 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 {color} color - Color of the text to render.
* @returns {LatexRenderResult|null}
*/
findPrerendered(markup, fontSize, color) {
const data = 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 {color} color - Color of the text to render.
* @returns {Promise<LatexRenderResult>}
*/
requestAsyncRender(markup, fontSize, color) {
return new Promise(resolve => {
resolve(this.renderSync(markup, fontSize, color))
})
}
/**
* 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()
if(elem[0] !== "(" && elem[elem.length - 1] !== ")" && contents.some(x => elem.indexOf(x) > 0))
return this.par(elem)
if(elem[0] === "(" && elem[elem.length - 1] === ")")
return elem.substr(1, elem.length - 2)
return elem
}
/**
* Creates a latex expression for a function.
*
* @param {string} f - Function to convert
* @param {(number,string)[]} args - Arguments of the function
* @returns {string}
*/
functionToLatex(f, args) {
switch(f) {
case "derivative":
if(args.length === 3)
return "\\frac{d" + args[0].substr(1, args[0].length - 2).replace(new RegExp(args[1].substr(1, args[1].length - 2), "g"), "x") + "}{dx}"
else
return "\\frac{d" + args[0] + "}{dx}(x)"
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"
case "sqrt":
return "\\sqrt\\left(" + args.join(", ") + "\\right)"
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.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 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: // 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 " + 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(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")
}
}
if(nstack.length > 1) {
nstack = [nstack.join(";")]
}
return String(nstack[0])
}
}
/** @type {LatexAPI} */
Modules.Latex = Modules.Latex || new LatexAPI()
export default Modules.Latex

View file

@ -0,0 +1,135 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Module } from './common.mjs'
import { textsub } from '../utils.mjs'
class ObjectsAPI extends Module {
constructor() {
super('Objects')
this.types = {}
/**
* List of objects for each type of object.
* @type {Object.<string,DrawableObject[]>}
*/
this.currentObjects = {}
/**
* List of objects matched by their name.
* @type {Object.<string,DrawableObject>}
*/
this.currentObjectsByName = {}
}
/**
* 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) {
let obj = this.currentObjectsByName[oldName]
delete this.currentObjectsByName[oldName]
this.currentObjectsByName[newName] = obj
obj.name = newName
}
/**
* Deletes an object by its given name.
* @param {string} objName - Current name of the object.
*/
deleteObject(objName) {
let obj = this.currentObjectsByName[objName]
if(obj !== undefined) {
this.currentObjects[obj.type].splice(this.currentObjects[obj.type].indexOf(obj),1)
obj.delete()
delete this.currentObjectsByName[objName]
}
}
/**
* Gets a list of all names of a certain object type.
* @param {string} objType - Type of the object to query. Can be ExecutableObject for all ExecutableObjects.
* @returns {string[]} List of names of the objects.
*/
getObjectsName(objType) {
if(objType === "ExecutableObject") {
// NOTE: QMLJS does not support flatMap.
// return getExecutableTypes().flatMap(elemType => currentObjects[elemType].map(obj => obj.name))
let types = this.getExecutableTypes()
let elementNames = ['']
for(let elemType of types)
elementNames = elementNames.concat(this.currentObjects[elemType].map(obj => obj.name))
return elementNames
}
if(this.currentObjects[objType] === undefined) return []
return this.currentObjects[objType].map(obj => obj.name)
}
/**
* Returns a list of all object types which are executable objects.
* @return {string[]} List of all object types which are executable objects.
*/
getExecutableTypes() {
return Object.keys(this.currentObjects).filter(objType => this.types[objType].executable())
}
/**
* Creates and register an object in the database.
* @param {string} objType - Type of the object to create.
* @param {string[]} args - List of arguments for the objects (can be empty).
* @return {DrawableObject<objType>} Newly created object.
*/
createNewRegisteredObject(objType, args= []) {
if(Object.keys(this.types).indexOf(objType) === -1) return null // Object type does not exist.
let newobj = new this.types[objType](...args)
if(Object.keys(this.currentObjects).indexOf(objType) === -1) {
this.currentObjects[objType] = []
}
this.currentObjects[objType].push(newobj)
this.currentObjectsByName[newobj.name] = newobj
return newobj
}
}
/** @type {ObjectsAPI} */
Modules.Objects = Modules.Objects || new ObjectsAPI()
export default Modules.Objects

View file

@ -0,0 +1,37 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Module} from "./common.mjs"
import General from "../preferences/general.mjs"
import Editor from "../preferences/expression.mjs"
import DefaultGraph from "../preferences/default.mjs"
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 const API = Modules.Preferences

View file

@ -1,96 +0,0 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2022 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "utils.js" as Utils
.import "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
}

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