Compare commits

..

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

353 changed files with 20477 additions and 36724 deletions

34
.gitignore vendored
View file

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

4
.gitmodules vendored
View file

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

View file

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

View file

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

View file

@ -1,25 +1,20 @@
/**
"""
* 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/>.
*/
{
"recursive": true,
"require": [
"esm",
"./test/hooks.mjs"
]
}
"""
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

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

View file

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

View file

@ -0,0 +1,155 @@
"""
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from time import time
from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import Qt, QTranslator, QLocale
from PySide6.QtGui import QIcon
from tempfile import TemporaryDirectory
from os import getcwd, chdir, environ, path, remove, close
from platform import release as os_release
from sys import platform, argv, version as sys_version, exit
from sys import path as sys_path
start_time = time()
# Create the temporary directory for saving copied screenshots and latex files
tempdir = TemporaryDirectory()
tmpfile = path.join(tempdir.name, 'graph.png')
pwd = getcwd()
chdir(path.dirname(path.realpath(__file__)))
if path.realpath(path.join(getcwd(), "..")) not in sys_path:
sys_path.append(path.realpath(path.join(getcwd(), "..")))
from LogarithmPlotter import __VERSION__
from LogarithmPlotter.util import config, native
from LogarithmPlotter.util.update import check_for_updates
from LogarithmPlotter.util.helper import Helper
from LogarithmPlotter.util.latex import Latex
config.init()
def get_linux_theme():
des = {
"KDE": "Fusion",
"gnome": "Basic",
"lxqt": "Fusion",
"mate": "Fusion",
}
if "XDG_SESSION_DESKTOP" in environ:
return des[environ["XDG_SESSION_DESKTOP"]] if environ["XDG_SESSION_DESKTOP"] in des else "Fusion"
else:
# Android
return "Material"
def run():
if not 'QT_QUICK_CONTROLS_STYLE' in environ:
environ["QT_QUICK_CONTROLS_STYLE"] = {
"linux": get_linux_theme(),
"freebsd": get_linux_theme(),
"win32": "Universal" if os_release == "10" else "Fusion",
"cygwin": "Fusion",
"darwin": "macOS"
}[platform]
dep_time = time()
print("Loaded dependencies in " + str((dep_time - start_time)*1000) + "ms.")
icon_fallbacks = QIcon.fallbackSearchPaths();
base_icon_path = path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "icons")
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "common")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "objects")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "history")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings")))
icon_fallbacks.append(path.realpath(path.join(base_icon_path, "settings", "custom")))
QIcon.setFallbackSearchPaths(icon_fallbacks);
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication(argv)
app.setApplicationName("LogarithmPlotter")
app.setOrganizationName("Ad5001")
app.styleHints().setShowShortcutsInContextMenus(True)
app.setWindowIcon(QIcon(path.realpath(path.join(getcwd(), "logarithmplotter.svg"))))
# Installing translators
translator = QTranslator()
# Check if lang is forced.
forcedlang = [p for p in argv if p[:7]=="--lang="]
locale = QLocale(forcedlang[0][7:]) if len(forcedlang) > 0 else QLocale()
if (translator.load(locale, "lp", "_", path.realpath(path.join(getcwd(), "i18n")))):
app.installTranslator(translator);
# Installing macOS file handler.
macOSFileOpenHandler = None
if platform == "darwin":
macOSFileOpenHandler = native.MacOSFileOpenHandler()
app.installEventFilter(macOSFileOpenHandler)
engine = QQmlApplicationEngine()
global tmpfile
helper = Helper(pwd, tmpfile)
latex = Latex(tempdir)
engine.rootContext().setContextProperty("Helper", helper)
engine.rootContext().setContextProperty("Latex", latex)
engine.rootContext().setContextProperty("TestBuild", "--test-build" in argv)
engine.rootContext().setContextProperty("StartTime", dep_time)
app.translate("About","About LogarithmPlotter") # FOR SOME REASON, if this isn't included, Qt refuses to load the QML file.
engine.addImportPath(path.realpath(path.join(getcwd(), "qml")))
engine.load(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
if not engine.rootObjects():
print("No root object", path.realpath(path.join(getcwd(), "qml")))
print(path.realpath(path.join(getcwd(), "qml", "eu", "ad5001", "LogarithmPlotter", "LogarithmPlotter.qml")))
exit(-1)
# Open the current diagram
chdir(pwd)
if len(argv) > 0 and path.exists(argv[-1]) and argv[-1].split('.')[-1] in ['lpf']:
engine.rootObjects()[0].loadDiagram(argv[-1])
chdir(path.dirname(path.realpath(__file__)))
if platform == "darwin":
macOSFileOpenHandler.init_graphics(engine.rootObjects()[0])
# Check for LaTeX installation if LaTeX support is enabled
if config.getSetting("enable_latex"):
latex.check_latex_install()
# Check for updates
if config.getSetting("check_for_updates"):
check_for_updates(__VERSION__, engine.rootObjects()[0])
exit_code = app.exec()
tempdir.cleanup()
config.save()
exit(exit_code)
if __name__ == "__main__":
run()

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before After
Before After

View file

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

View file

@ -0,0 +1,221 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQml
import QtQuick.Window
import "../js/objects.js" as Objects
import "../js/historylib.js" as HistoryLib
import "../js/history/common.js" as HistoryCommon
/*!
\qmltype History
\inqmlmodule eu.ad5001.LogarithmPlotter.History
\brief QObject holding persistantly for undo & redo stacks.
\sa HistoryBrowser, historylib
*/
Item {
// Using a QtObject is necessary in order to have proper property propagation in QML
id: historyObj
/*!
\qmlproperty int History::undoCount
Count of undo actions.
*/
property int undoCount: 0
/*!
\qmlproperty int History::redoCount
Count of redo actions.
*/
property int redoCount: 0
/*!
\qmlproperty var History::undoStack
Stack of undo actions.
*/
property var undoStack: []
/*!
\qmlproperty var History::redoStack
Stack of redo actions.
*/
property var redoStack: []
/*!
\qmlproperty bool History::saved
true when no modification was done to the current working file, false otherwise.
*/
property bool saved: true
/*!
\qmlmethod void History::clear()
Clears both undo and redo stacks completly.
*/
function clear() {
undoCount = 0
redoCount = 0
undoStack = []
redoStack = []
}
/*!
\qmlmethod var History::serialize()
Serializes history into JSON-able content.
*/
function serialize() {
let undoSt = [], redoSt = [];
for(let i = 0; i < undoCount; i++)
undoSt.push([
undoStack[i].type(),
undoStack[i].export()
]);
for(let i = 0; i < redoCount; i++)
redoSt.push([
redoStack[i].type(),
redoStack[i].export()
]);
return [undoSt, redoSt]
}
/*!
\qmlmethod void History::unserialize(var undoSt, var redoSt)
Unserializes both \c undoSt stack and \c redoSt stack from serialized content.
*/
function unserialize(undoSt, redoSt) {
clear();
for(let i = 0; i < undoSt.length; i++)
undoStack.push(new HistoryLib.Actions[undoSt[i][0]](...undoSt[i][1]))
for(let i = 0; i < redoSt.length; i++)
redoStack.push(new HistoryLib.Actions[redoSt[i][0]](...redoSt[i][1]))
undoCount = undoSt.length;
redoCount = redoSt.length;
objectLists.update()
}
/*!
\qmlmethod void History::addToHistory(var action)
Adds an instance of historylib.Action to history.
*/
function addToHistory(action) {
if(action instanceof HistoryLib.Action) {
console.log("Added new entry to history: " + action.getReadableString())
undoStack.push(action)
undoCount++;
if(Helper.getSettingBool("reset_redo_stack")) {
redoStack = []
redoCount = 0
}
saved = false
}
}
/*!
\qmlmethod void History::undo(bool updateObjectList = true)
Undoes the historylib.Action at the top of the undo stack and pushes it to the top of the redo stack.
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
*/
function undo(updateObjectList = true) {
if(undoStack.length > 0) {
var action = undoStack.pop()
action.undo()
if(updateObjectList)
objectLists.update()
redoStack.push(action)
undoCount--;
redoCount++;
saved = false
}
}
/*!
\qmlmethod void History::redo(bool updateObjectList = true)
Redoes the historylib.Action at the top of the redo stack and pushes it to the top of the undo stack.
By default, will update the graph and the object list. This behavior can be disabled by setting the \c updateObjectList to false.
*/
function redo(updateObjectList = true) {
if(redoStack.length > 0) {
var action = redoStack.pop()
action.redo()
if(updateObjectList)
objectLists.update()
undoStack.push(action)
undoCount++;
redoCount--;
saved = false
}
}
/*!
\qmlmethod void History::undoMultipleDefered(int toUndoCount)
Undoes several historylib.Action at the top of the undo stack and pushes them to the top of the redo stack.
It undoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
*/
function undoMultipleDefered(toUndoCount) {
undoTimer.toUndoCount = toUndoCount;
undoTimer.start()
if(toUndoCount > 0)
saved = false
}
/*!
\qmlmethod void History::redoMultipleDefered(int toRedoCount)
Redoes several historylib.Action at the top of the redo stack and pushes them to the top of the undo stack.
It redoes them deferedly to avoid overwhelming the computer while creating a cool short accelerated summary of all changes.
*/
function redoMultipleDefered(toRedoCount) {
redoTimer.toRedoCount = toRedoCount;
redoTimer.start()
if(toRedoCount > 0)
saved = false
}
Timer {
id: undoTimer
interval: 5; running: false; repeat: true
property int toUndoCount: 0
onTriggered: {
if(toUndoCount > 0) {
historyObj.undo(toUndoCount % 4 == 1) // Only redraw once every 4 changes.
toUndoCount--;
} else {
running = false;
}
}
}
Timer {
id: redoTimer
interval: 5; running: false; repeat: true
property int toRedoCount: 0
onTriggered: {
if(toRedoCount > 0) {
historyObj.redo(toRedoCount % 4 == 1) // Only redraw once every 4 changes.
toRedoCount--;
} else {
running = false;
}
}
}
Component.onCompleted: {
HistoryLib.history = historyObj
HistoryCommon.themeTextColor = sysPalette.windowText
HistoryCommon.imageDepth = Screen.devicePixelRatio
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001
* 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
@ -102,7 +102,7 @@ BaseDialog {
wrapMode: Text.WordWrap
textFormat: Text.RichText
font.pixelSize: 13
text: "Copyright © 2021-2025 Ad5001 &lt;mail@ad5001.eu&gt;<br>
text: "Copyright © 2021-2024 Ad5001 &lt;mail@ad5001.eu&gt;<br>
<br>
This 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>

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001
* 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

View file

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

View file

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

View file

@ -0,0 +1,236 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import "../js/math/latex.js" as Latex
/*!
\qmltype GreetScreen
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
\brief Overlay displayed when LogarithmPlotter is launched for the first time or when it was just updated.
It contains several settings as well as an easy access to the changelog
\sa LogarithmPlotter, Settings, AppMenuBar, Changelog
*/
Popup {
id: greetingPopup
x: (parent.width-width)/2
y: Math.max(20, (parent.height-height)/2)
width: Math.max(welcome.width+70, checkForUpdatesSetting.width, resetRedoStackSetting.width)+20
height: Math.min(parent.height-40, 700)
modal: true
focus: true
clip: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
ScrollView {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottomMargin: bottomButtons.height + 20
clip: true
Column {
width: greetingPopup.width - 25
spacing: 10
clip: true
topPadding: 35
Row {
id: welcome
height: logo.height
spacing: 10
anchors.horizontalCenter: parent.horizontalCenter
Image {
id: logo
source: "../icons/logarithmplotter.svg"
sourceSize.width: 48
sourceSize.height: 48
width: 48
height: 48
}
Label {
id: welcomeText
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 32
text: qsTr("Welcome to LogarithmPlotter")
}
}
Label {
id: versionText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: implicitWidth
font.pixelSize: 18
font.italic: true
text: qsTr("Version %1").arg(Helper.getVersion())
}
Label {
id: helpText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
font.pixelSize: 14
width: parent.width - 50
text: qsTr("Take a few seconds to configure LogarithmPlotter.\nThese settings can be changed at any time from the \"Settings\" menu.")
}
CheckBox {
id: checkForUpdatesSetting
anchors.left: parent.left
checked: Helper.getSettingBool("check_for_updates")
text: qsTr('Check for updates on startup (requires online connectivity)')
onClicked: {
Helper.setSettingBool("check_for_updates", checked)
// Set in the menu bar
appMenu.settingsMenu.children[0].checked = checked
}
}
CheckBox {
id: resetRedoStackSetting
anchors.left: parent.left
checked: Helper.getSettingBool("reset_redo_stack")
text: qsTr('Reset redo stack when a new action is added to history')
onClicked: {
Helper.setSettingBool("reset_redo_stack", checked)
appMenu.settingsMenu.children[1].checked = checked
}
}
CheckBox {
id: enableLatexSetting
anchors.left: parent.left
checked: Helper.getSettingBool("enable_latex")
text: qsTr('Enable LaTeX rendering')
onClicked: {
Helper.setSettingBool("enable_latex", checked)
appMenu.settingsMenu.children[2].checked = checked
}
}
CheckBox {
id: autocloseFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("expression_editor.autoclose")
text: qsTr('Automatically close parenthesises and brackets in expressions')
onClicked: {
Helper.setSettingBool("expression_editor.autoclose", checked)
appMenu.settingsMenu.children[3].children[0].checked = checked
}
}
CheckBox {
id: colorizeFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("expression_editor.colorize")
text: qsTr('Enable syntax highlighting for expressions')
onClicked: {
Helper.setSettingBool("expression_editor.colorize", checked)
appMenu.settingsMenu.children[3].children[1].checked = checked
}
}
CheckBox {
id: autocompleteFormulaSetting
anchors.left: parent.left
checked: Helper.getSettingBool("autocompletion.enabled")
text: qsTr('Enable autocompletion interface in expression editor')
onClicked: {
Helper.setSettingBool("autocompletion.enabled", checked)
appMenu.settingsMenu.children[3].children[2].checked = checked
}
}
Row {
anchors.left: parent.left
anchors.leftMargin: 10
spacing: 10
Label {
id: colorSchemeLabel
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
text: qsTr("Color scheme:")
}
ComboBox {
model: ["Breeze Light", "Breeze Dark", "Solarized", "Github Light", "Github Dark", "Nord", "Monokai"]
currentIndex: Helper.getSettingInt("expression_editor.color_scheme")
onActivated: function(index) {
Helper.setSettingInt("expression_editor.color_scheme", index)
}
}
}
}
}
Rectangle {
id: bottomSeparator
opacity: 0.3
color: sysPalette.windowText
width: parent.width * 2 / 3
height: 1
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: bottomButtons.top
anchors.bottomMargin: 9
}
Row {
id: bottomButtons
anchors.bottom: parent.bottom
anchors.bottomMargin: 7
spacing: 10
anchors.horizontalCenter: parent.horizontalCenter
Button {
id: userManualBtn
text: qsTr("User manual")
font.pixelSize: 18
onClicked: Qt.openUrlExternally("https://git.ad5001.eu/Ad5001/LogarithmPlotter/wiki/_Sidebar")
}
Button {
id: changelogBtn
text: qsTr("Changelog")
font.pixelSize: 18
onClicked: changelog.open()
}
Button {
id: doneBtn
text: qsTr("Done")
font.pixelSize: 18
onClicked: greetingPopup.close()
}
}
Component.onCompleted: if(Helper.getSetting("last_install_greet") != Helper.getVersion()) {
greetingPopup.open()
}
onClosed: Helper.setSetting("last_install_greet", Helper.getVersion())
}

View file

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

View file

@ -0,0 +1,334 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2024 Ad5001
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Dialogs
import QtQuick.Controls
/*!
\qmltype ThanksTo
\inqmlmodule eu.ad5001.LogarithmPlotter.Popup
\brief Thanks to popup of LogarithmPlotter.
\sa LogarithmPlotter
*/
BaseDialog {
id: about
title: qsTr("Thanks and Contributions - LogarithmPlotter")
width: 450
minimumHeight: 710
Column {
anchors {
top: parent.top;
left: parent.left;
bottom: parent.bottom;
right: parent.right;
topMargin: margin;
leftMargin: margin;
bottomMargin: margin;
rightMargin: margin;
}
spacing: 10
ListView {
id: librariesListView
anchors.left: parent.left
width: parent.width
//height: parent.height
implicitHeight: contentItem.childrenRect.height
interactive: false
model: ListModel {
Component.onCompleted: {
append({
libName: 'expr-eval',
license: 'MIT',
licenseLink: 'https://raw.githubusercontent.com/silentmatt/expr-eval/master/LICENSE.txt',
linkName: qsTr('Source code'),
link: 'https://github.com/silentmatt/expr-eval',
authors: [{
authorLine: qsTr('Original library by Raphael Graf'),
email: 'r@undefined.ch',
website: 'https://web.archive.org/web/20111023001618/http://www.undefined.ch/mparser/index.html',
websiteName: qsTr('Source')
}, {
authorLine: qsTr('Ported to Javascript by Matthew Crumley'),
email: 'email@matthewcrumley.com',
website: 'https://silentmatt.com/',
websiteName: qsTr('Website')
}, {
authorLine: qsTr('Ported to QMLJS by Ad5001'),
email: 'mail@ad5001.eu',
website: 'https://ad5001.eu/',
websiteName: qsTr('Website')
}]
})
}
}
header: Label {
id: librariesUsedHeader
wrapMode: Text.WordWrap
font.pixelSize: 25
text: qsTr("Libraries included")
height: implicitHeight + 10
}
delegate: Column {
id: libClmn
width: librariesListView.width
spacing: 10
Item {
height: libraryHeader.height
width: parent.width
Label {
id: libraryHeader
anchors.left: parent.left
wrapMode: Text.WordWrap
font.pixelSize: 18
text: libName
}
Row {
anchors.right: parent.right
height: parent.height
spacing: 10
Button {
height: parent.height
text: license
icon.name: 'license'
onClicked: Qt.openUrlExternally(licenseLink)
}
Button {
height: parent.height
text: linkName
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally(link)
}
}
}
ListView {
id: libAuthors
anchors.left: parent.left
anchors.leftMargin: 10
model: authors
width: parent.width - 10
implicitHeight: contentItem.childrenRect.height
interactive: false
delegate: Item {
id: libAuthor
width: librariesListView.width - 10
height: 50
Label {
id: libAuthorName
anchors.left: parent.left
anchors.right: buttons.left
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 14
text: authorLine
}
Row {
id: buttons
anchors.right: parent.right
height: parent.height
spacing: 10
Button {
anchors.verticalCenter: parent.verticalCenter
text: websiteName
icon.name: 'web-browser'
height: parent.height - 10
onClicked: Qt.openUrlExternally(website)
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: qsTr('Email')
icon.name: 'email'
height: parent.height - 10
onClicked: Qt.openUrlExternally('mailto:' + email)
}
}
}
}
Rectangle {
id: libSeparator
opacity: 0.3
color: sysPalette.windowText
width: parent.width
height: 1
}
}
}
ListView {
id: translationsListView
anchors.left: parent.left
width: parent.width
implicitHeight: contentItem.childrenRect.height
interactive: false
spacing: 3
model: ListModel {
Component.onCompleted: {
append({
tranName: '🇬🇧 ' + qsTr('English'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/en/',
authors: [{
authorLine: 'Ad5001',
email: 'mail@ad5001.eu',
website: 'https://ad5001.eu',
websiteName: qsTr('Website')
}]
})
append({
tranName: '🇫🇷 ' + qsTr('French'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/fr/',
authors: [{
authorLine: 'Ad5001',
website: 'https://ad5001.eu',
websiteName: qsTr('Website')
}]
})
append({
tranName: '🇩🇪 ' + qsTr('German'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/de/',
authors: [{
authorLine: 'Ad5001',
website: 'https://ad5001.eu',
websiteName: qsTr('Website')
}]
})
append({
tranName: '🇭🇺 ' + qsTr('Hungarian'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/hu/',
authors: [{
authorLine: 'Óvári',
website: 'https://github.com/ovari',
websiteName: qsTr('Github')
}]
})
append({
tranName: '🇳🇴 ' + qsTr('Norwegian'),
link: 'https://hosted.weblate.org/projects/logarithmplotter/logarithmplotter/no/',
authors: [{
authorLine: 'Allan Nordhøy',
website: 'https://github.com/comradekingu',
websiteName: qsTr('Github')
}]
})
}
}
header: Label {
id: translationsHeader
wrapMode: Text.WordWrap
font.pixelSize: 25
text: qsTr("Translations included")
height: implicitHeight + 10
}
delegate: Column {
id: tranClmn
width: translationsListView.width
Item {
width: parent.width
height: translationHeader.height + 10
Label {
id: translationHeader
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 18
text: tranName
}
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: 30
spacing: 10
Button {
height: parent.height
text: qsTr('Improve')
icon.name: 'web-browser'
onClicked: Qt.openUrlExternally(link)
}
}
}
ListView {
id: tranAuthors
anchors.left: parent.left
anchors.leftMargin: 10
model: authors
width: parent.width - 10
implicitHeight: contentItem.childrenRect.height
interactive: false
delegate: Item {
id: tranAuthor
width: tranAuthors.width
height: 40
Label {
id: tranAuthorName
anchors.left: parent.left
anchors.right: buttons.left
anchors.verticalCenter: parent.verticalCenter
wrapMode: Text.WordWrap
font.pixelSize: 14
text: authorLine
}
Row {
id: buttons
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: 30
spacing: 10
Button {
text: websiteName
icon.name: 'web-browser'
height: parent.height
onClicked: Qt.openUrlExternally(website)
}
}
}
}
}
}
}
}

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001
* 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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/**
* LogarithmPlotter - 2D plotter software to make BODE plots, sequences and distribution functions.
* Copyright (C) 2021-2025 Ad5001
* 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

View file

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

View file

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

View file

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

View file

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

View file

Before

Width:  |  Height:  |  Size: 370 B

After

Width:  |  Height:  |  Size: 370 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 289 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 620 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 261 B

After

Width:  |  Height:  |  Size: 261 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 273 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 696 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 270 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

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

View file

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

View file

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

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

View file

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

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before After
Before After

View file

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

View file

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

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Before After
Before After

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 675 B

After

Width:  |  Height:  |  Size: 675 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 414 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

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